blob: 1ccf7229cc87321cbcbc85a078124216fa704921 [file] [log] [blame]
Winson Chung655332c2016-10-31 13:14:28 -07001/*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server.wm;
18
19import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
20import static android.util.TypedValue.COMPLEX_UNIT_DIP;
Jorim Jaggiad5d2842016-11-01 18:22:53 -070021import static android.view.Display.DEFAULT_DISPLAY;
Winson Chung655332c2016-10-31 13:14:28 -070022
23import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
24import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
25
26import android.animation.ValueAnimator;
27import android.content.res.Resources;
28import android.graphics.Point;
29import android.graphics.Rect;
30import android.os.Handler;
31import android.os.IBinder;
32import android.os.RemoteException;
33import android.util.DisplayMetrics;
34import android.util.Log;
35import android.util.Size;
36import android.util.Slog;
37import android.util.TypedValue;
Winson Chung14fefc22016-11-02 10:02:29 -070038import android.view.DisplayInfo;
Winson Chung655332c2016-10-31 13:14:28 -070039import android.view.Gravity;
40import android.view.IPinnedStackController;
41import android.view.IPinnedStackListener;
42
43import com.android.internal.os.BackgroundThread;
44import com.android.internal.policy.PipMotionHelper;
45import com.android.internal.policy.PipSnapAlgorithm;
46
47import java.io.PrintWriter;
48
49/**
50 * Holds the common state of the pinned stack between the system and SystemUI.
51 */
52class PinnedStackController {
53
54 private static final String TAG = TAG_WITH_CLASS_NAME ? "PinnedStackController" : TAG_WM;
55
56 private final WindowManagerService mService;
57 private final DisplayContent mDisplayContent;
58 private final Handler mHandler = new Handler();
59
60 private IPinnedStackListener mPinnedStackListener;
61 private final PinnedStackListenerDeathHandler mPinnedStackListenerDeathHandler =
62 new PinnedStackListenerDeathHandler();
63
64 private final PinnedStackControllerCallback mCallbacks = new PinnedStackControllerCallback();
65 private final PipSnapAlgorithm mSnapAlgorithm;
66 private final PipMotionHelper mMotionHelper;
67
68 // States that affect how the PIP can be manipulated
69 private boolean mInInteractiveMode;
70 private boolean mIsImeShowing;
71 private int mImeHeight;
Winson Chung655332c2016-10-31 13:14:28 -070072 private ValueAnimator mBoundsAnimator = null;
73
Winson Chung14fefc22016-11-02 10:02:29 -070074 // Used to calculate stack bounds across rotations
75 private final DisplayInfo mDisplayInfo = new DisplayInfo();
76
Winson Chung655332c2016-10-31 13:14:28 -070077 // The size and position information that describes where the pinned stack will go by default.
78 private int mDefaultStackGravity;
79 private Size mDefaultStackSize;
80 private Point mScreenEdgeInsets;
81
82 // Temp vars for calculation
83 private final DisplayMetrics mTmpMetrics = new DisplayMetrics();
84 private final Rect mTmpInsets = new Rect();
Jorim Jaggiad5d2842016-11-01 18:22:53 -070085 private final Rect mTmpRect = new Rect();
Winson Chung655332c2016-10-31 13:14:28 -070086
87 /**
88 * The callback object passed to listeners for them to notify the controller of state changes.
89 */
90 private class PinnedStackControllerCallback extends IPinnedStackController.Stub {
91
92 @Override
93 public void setInInteractiveMode(final boolean inInteractiveMode) {
94 mHandler.post(() -> {
95 // Cancel any existing animations on the PIP once the user starts dragging it
96 if (mBoundsAnimator != null && inInteractiveMode) {
97 mBoundsAnimator.cancel();
98 }
99 mInInteractiveMode = inInteractiveMode;
Winson Chung655332c2016-10-31 13:14:28 -0700100 });
101 }
Winson Chungdff5c082016-11-02 17:28:03 -0700102
103 @Override
104 public void setSnapToEdge(final boolean snapToEdge) {
105 mHandler.post(() -> {
106 mSnapAlgorithm.setSnapToEdge(snapToEdge);
107 });
108 }
Winson Chung655332c2016-10-31 13:14:28 -0700109 }
110
111 /**
112 * Handler for the case where the listener dies.
113 */
114 private class PinnedStackListenerDeathHandler implements IBinder.DeathRecipient {
115
116 @Override
117 public void binderDied() {
118 // Clean up the state if the listener dies
119 mInInteractiveMode = false;
120 mPinnedStackListener = null;
121 }
122 }
123
124 PinnedStackController(WindowManagerService service, DisplayContent displayContent) {
125 mService = service;
126 mDisplayContent = displayContent;
127 mSnapAlgorithm = new PipSnapAlgorithm(service.mContext);
128 mMotionHelper = new PipMotionHelper(BackgroundThread.getHandler());
Winson Chung14fefc22016-11-02 10:02:29 -0700129 mDisplayInfo.copyFrom(mDisplayContent.getDisplayInfo());
Winson Chung655332c2016-10-31 13:14:28 -0700130 reloadResources();
131 }
132
133 void onConfigurationChanged() {
134 reloadResources();
135 }
136
137 /**
138 * Reloads all the resources for the current configuration.
139 */
140 void reloadResources() {
141 final Resources res = mService.mContext.getResources();
142 final Size defaultSizeDp = Size.parseSize(res.getString(
143 com.android.internal.R.string.config_defaultPictureInPictureSize));
144 final Size screenEdgeInsetsDp = Size.parseSize(res.getString(
145 com.android.internal.R.string.config_defaultPictureInPictureScreenEdgeInsets));
146 mDefaultStackGravity = res.getInteger(
147 com.android.internal.R.integer.config_defaultPictureInPictureGravity);
148 mDisplayContent.getDisplay().getRealMetrics(mTmpMetrics);
149 mDefaultStackSize = new Size(dpToPx(defaultSizeDp.getWidth(), mTmpMetrics),
150 dpToPx(defaultSizeDp.getHeight(), mTmpMetrics));
151 mScreenEdgeInsets = new Point(dpToPx(screenEdgeInsetsDp.getWidth(), mTmpMetrics),
152 dpToPx(screenEdgeInsetsDp.getHeight(), mTmpMetrics));
153 }
154
155 /**
156 * Registers a pinned stack listener.
157 */
158 void registerPinnedStackListener(IPinnedStackListener listener) {
159 try {
160 listener.asBinder().linkToDeath(mPinnedStackListenerDeathHandler, 0);
161 listener.onListenerRegistered(mCallbacks);
162 mPinnedStackListener = listener;
163 notifyBoundsChanged(mIsImeShowing);
164 } catch (RemoteException e) {
165 Log.e(TAG, "Failed to register pinned stack listener", e);
166 }
167 }
168
169 /**
170 * @return the default bounds to show the PIP when there is no active PIP.
171 */
172 Rect getDefaultBounds() {
Winson Chung655332c2016-10-31 13:14:28 -0700173 final Rect insetBounds = new Rect();
Winson Chung14fefc22016-11-02 10:02:29 -0700174 getInsetBounds(insetBounds);
Winson Chung655332c2016-10-31 13:14:28 -0700175
176 final Rect defaultBounds = new Rect();
177 Gravity.apply(mDefaultStackGravity, mDefaultStackSize.getWidth(),
178 mDefaultStackSize.getHeight(), insetBounds, 0, 0, defaultBounds);
179 return defaultBounds;
180 }
181
182 /**
183 * @return the movement bounds for the given {@param stackBounds} and the current state of the
184 * controller.
185 */
186 Rect getMovementBounds(Rect stackBounds) {
Winson Chung14fefc22016-11-02 10:02:29 -0700187 return getMovementBounds(stackBounds, true /* adjustForIme */);
188 }
189
190 /**
191 * @return the movement bounds for the given {@param stackBounds} and the current state of the
192 * controller.
193 */
194 Rect getMovementBounds(Rect stackBounds, boolean adjustForIme) {
Winson Chung655332c2016-10-31 13:14:28 -0700195 final Rect movementBounds = new Rect();
Winson Chung14fefc22016-11-02 10:02:29 -0700196 getInsetBounds(movementBounds);
Winson Chung655332c2016-10-31 13:14:28 -0700197
198 // Adjust the right/bottom to ensure the stack bounds never goes offscreen
199 movementBounds.right = Math.max(movementBounds.left, movementBounds.right -
200 stackBounds.width());
201 movementBounds.bottom = Math.max(movementBounds.top, movementBounds.bottom -
202 stackBounds.height());
203
Winson Chung14fefc22016-11-02 10:02:29 -0700204 // Apply the movement bounds adjustments based on the current state
205 if (adjustForIme) {
206 if (mIsImeShowing) {
207 movementBounds.bottom -= mImeHeight;
208 }
Winson Chung655332c2016-10-31 13:14:28 -0700209 }
Winson Chung655332c2016-10-31 13:14:28 -0700210 return movementBounds;
211 }
212
213 /**
Winson Chung14fefc22016-11-02 10:02:29 -0700214 * @return the repositioned PIP bounds given it's pre-change bounds, and the new display info.
Winson Chung655332c2016-10-31 13:14:28 -0700215 */
Winson Chung14fefc22016-11-02 10:02:29 -0700216 Rect onDisplayChanged(Rect preChangeStackBounds, DisplayInfo displayInfo) {
217 final Rect postChangeStackBounds = new Rect(preChangeStackBounds);
218 if (!mDisplayInfo.equals(displayInfo)) {
219 // Calculate the snap fraction of the current stack along the old movement bounds, and
220 // then update the stack bounds to the same fraction along the rotated movement bounds.
221 final Rect preChangeMovementBounds = getMovementBounds(preChangeStackBounds);
222 final float snapFraction = mSnapAlgorithm.getSnapFraction(preChangeStackBounds,
223 preChangeMovementBounds);
224 mDisplayInfo.copyFrom(displayInfo);
225
226 final Rect postChangeMovementBounds = getMovementBounds(preChangeStackBounds,
227 false /* adjustForIme */);
228 mSnapAlgorithm.applySnapFraction(postChangeStackBounds, postChangeMovementBounds,
229 snapFraction);
230 }
231 return postChangeStackBounds;
Winson Chung655332c2016-10-31 13:14:28 -0700232 }
233
234 /**
235 * Sets the Ime state and height.
236 */
237 void setAdjustedForIme(boolean adjustedForIme, int imeHeight) {
238 // Return early if there is no state change
239 if (mIsImeShowing == adjustedForIme && mImeHeight == imeHeight) {
240 return;
241 }
242
243 final Rect stackBounds = new Rect();
244 mService.getStackBounds(PINNED_STACK_ID, stackBounds);
245 final Rect prevMovementBounds = getMovementBounds(stackBounds);
Winson Chung655332c2016-10-31 13:14:28 -0700246 mIsImeShowing = adjustedForIme;
247 mImeHeight = imeHeight;
248 if (mInInteractiveMode) {
249 // If the user is currently interacting with the PIP and the ime state changes, then
250 // don't adjust the bounds and defer that to after the interaction
251 notifyBoundsChanged(adjustedForIme /* adjustedForIme */);
252 } else {
253 // Otherwise, we can move the PIP to a sane location to ensure that it does not block
254 // the user from interacting with the IME
Winson Chung14fefc22016-11-02 10:02:29 -0700255 final Rect movementBounds = getMovementBounds(stackBounds);
256 final Rect toBounds = new Rect(stackBounds);
257 if (adjustedForIme) {
258 // IME visible
259 toBounds.offset(0, Math.min(0, movementBounds.bottom - stackBounds.top));
Winson Chung655332c2016-10-31 13:14:28 -0700260 } else {
Winson Chung14fefc22016-11-02 10:02:29 -0700261 // IME hidden
262 if (stackBounds.top == prevMovementBounds.bottom) {
263 // If the PIP is resting on top of the IME, then adjust it with the hiding IME
264 toBounds.offsetTo(toBounds.left, movementBounds.bottom);
265 }
Winson Chung655332c2016-10-31 13:14:28 -0700266 }
267 if (!toBounds.equals(stackBounds)) {
268 if (mBoundsAnimator != null) {
269 mBoundsAnimator.cancel();
270 }
271 mBoundsAnimator = mMotionHelper.createAnimationToBounds(stackBounds, toBounds);
272 mBoundsAnimator.start();
273 }
274 }
275 }
276
277 /**
Winson Chung655332c2016-10-31 13:14:28 -0700278 * Sends a broadcast that the PIP movement bounds have changed.
279 */
280 private void notifyBoundsChanged(boolean adjustedForIme) {
281 if (mPinnedStackListener != null) {
282 try {
283 mPinnedStackListener.onBoundsChanged(adjustedForIme);
284 } catch (RemoteException e) {
285 Slog.e(TAG_WM, "Error delivering bounds changed event.", e);
286 }
287 }
288 }
289
290 /**
291 * @return the bounds on the screen that the PIP can be visible in.
292 */
Winson Chung14fefc22016-11-02 10:02:29 -0700293 private void getInsetBounds(Rect outRect) {
294 mService.mPolicy.getStableInsetsLw(mDisplayInfo.rotation, mDisplayInfo.logicalWidth,
295 mDisplayInfo.logicalHeight, mTmpInsets);
296 outRect.set(mTmpInsets.left + mScreenEdgeInsets.x, mTmpInsets.top + mScreenEdgeInsets.y,
297 mDisplayInfo.logicalWidth - mTmpInsets.right - mScreenEdgeInsets.x,
298 mDisplayInfo.logicalHeight - mTmpInsets.bottom - mScreenEdgeInsets.y);
Winson Chung655332c2016-10-31 13:14:28 -0700299 }
300
301 /**
302 * @return the pixels for a given dp value.
303 */
304 private int dpToPx(float dpValue, DisplayMetrics dm) {
305 return (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP, dpValue, dm);
306 }
307
308 void dump(String prefix, PrintWriter pw) {
309 pw.println(prefix + "PinnedStackController");
Jorim Jaggiad5d2842016-11-01 18:22:53 -0700310 pw.print(prefix + " defaultBounds="); getDefaultBounds().printShortString(pw);
311 pw.println();
312 mService.getStackBounds(PINNED_STACK_ID, mTmpRect);
313 pw.print(prefix + " movementBounds="); getMovementBounds(mTmpRect).printShortString(pw);
314 pw.println();
Winson Chung655332c2016-10-31 13:14:28 -0700315 pw.println(prefix + " mIsImeShowing=" + mIsImeShowing);
316 pw.println(prefix + " mInInteractiveMode=" + mInInteractiveMode);
317 }
318}