blob: effb1b284cc28760e99df125d061b27345ddf7e5 [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;
Winson Chung84a38342016-11-08 16:15:10 -080029import android.graphics.PointF;
Winson Chung655332c2016-10-31 13:14:28 -070030import android.graphics.Rect;
31import android.os.Handler;
32import android.os.IBinder;
33import android.os.RemoteException;
34import android.util.DisplayMetrics;
35import android.util.Log;
36import android.util.Size;
37import android.util.Slog;
38import android.util.TypedValue;
Winson Chung14fefc22016-11-02 10:02:29 -070039import android.view.DisplayInfo;
Winson Chung655332c2016-10-31 13:14:28 -070040import android.view.Gravity;
41import android.view.IPinnedStackController;
42import android.view.IPinnedStackListener;
43
44import com.android.internal.os.BackgroundThread;
45import com.android.internal.policy.PipMotionHelper;
46import com.android.internal.policy.PipSnapAlgorithm;
Wale Ogunwaleb783fd82016-11-04 09:51:54 -070047import com.android.server.UiThread;
Winson Chung655332c2016-10-31 13:14:28 -070048
49import java.io.PrintWriter;
50
51/**
52 * Holds the common state of the pinned stack between the system and SystemUI.
53 */
54class PinnedStackController {
55
56 private static final String TAG = TAG_WITH_CLASS_NAME ? "PinnedStackController" : TAG_WM;
57
58 private final WindowManagerService mService;
59 private final DisplayContent mDisplayContent;
Wale Ogunwaleb783fd82016-11-04 09:51:54 -070060 private final Handler mHandler = UiThread.getHandler();
Winson Chung655332c2016-10-31 13:14:28 -070061
62 private IPinnedStackListener mPinnedStackListener;
63 private final PinnedStackListenerDeathHandler mPinnedStackListenerDeathHandler =
64 new PinnedStackListenerDeathHandler();
65
66 private final PinnedStackControllerCallback mCallbacks = new PinnedStackControllerCallback();
67 private final PipSnapAlgorithm mSnapAlgorithm;
68 private final PipMotionHelper mMotionHelper;
69
70 // States that affect how the PIP can be manipulated
71 private boolean mInInteractiveMode;
72 private boolean mIsImeShowing;
73 private int mImeHeight;
Winson Chung655332c2016-10-31 13:14:28 -070074 private ValueAnimator mBoundsAnimator = null;
75
Winson Chung14fefc22016-11-02 10:02:29 -070076 // Used to calculate stack bounds across rotations
77 private final DisplayInfo mDisplayInfo = new DisplayInfo();
78
Winson Chung655332c2016-10-31 13:14:28 -070079 // The size and position information that describes where the pinned stack will go by default.
80 private int mDefaultStackGravity;
81 private Size mDefaultStackSize;
82 private Point mScreenEdgeInsets;
83
84 // Temp vars for calculation
85 private final DisplayMetrics mTmpMetrics = new DisplayMetrics();
86 private final Rect mTmpInsets = new Rect();
Jorim Jaggiad5d2842016-11-01 18:22:53 -070087 private final Rect mTmpRect = new Rect();
Winson Chung655332c2016-10-31 13:14:28 -070088
89 /**
90 * The callback object passed to listeners for them to notify the controller of state changes.
91 */
92 private class PinnedStackControllerCallback extends IPinnedStackController.Stub {
93
94 @Override
95 public void setInInteractiveMode(final boolean inInteractiveMode) {
96 mHandler.post(() -> {
97 // Cancel any existing animations on the PIP once the user starts dragging it
98 if (mBoundsAnimator != null && inInteractiveMode) {
99 mBoundsAnimator.cancel();
100 }
101 mInInteractiveMode = inInteractiveMode;
Winson Chung655332c2016-10-31 13:14:28 -0700102 });
103 }
Winson Chungdff5c082016-11-02 17:28:03 -0700104
105 @Override
106 public void setSnapToEdge(final boolean snapToEdge) {
107 mHandler.post(() -> {
108 mSnapAlgorithm.setSnapToEdge(snapToEdge);
109 });
110 }
Winson Chung655332c2016-10-31 13:14:28 -0700111 }
112
113 /**
114 * Handler for the case where the listener dies.
115 */
116 private class PinnedStackListenerDeathHandler implements IBinder.DeathRecipient {
117
118 @Override
119 public void binderDied() {
120 // Clean up the state if the listener dies
121 mInInteractiveMode = false;
122 mPinnedStackListener = null;
123 }
124 }
125
126 PinnedStackController(WindowManagerService service, DisplayContent displayContent) {
127 mService = service;
128 mDisplayContent = displayContent;
129 mSnapAlgorithm = new PipSnapAlgorithm(service.mContext);
130 mMotionHelper = new PipMotionHelper(BackgroundThread.getHandler());
Winson Chung14fefc22016-11-02 10:02:29 -0700131 mDisplayInfo.copyFrom(mDisplayContent.getDisplayInfo());
Winson Chung655332c2016-10-31 13:14:28 -0700132 reloadResources();
133 }
134
135 void onConfigurationChanged() {
136 reloadResources();
137 }
138
139 /**
140 * Reloads all the resources for the current configuration.
141 */
142 void reloadResources() {
143 final Resources res = mService.mContext.getResources();
144 final Size defaultSizeDp = Size.parseSize(res.getString(
145 com.android.internal.R.string.config_defaultPictureInPictureSize));
146 final Size screenEdgeInsetsDp = Size.parseSize(res.getString(
147 com.android.internal.R.string.config_defaultPictureInPictureScreenEdgeInsets));
148 mDefaultStackGravity = res.getInteger(
149 com.android.internal.R.integer.config_defaultPictureInPictureGravity);
150 mDisplayContent.getDisplay().getRealMetrics(mTmpMetrics);
151 mDefaultStackSize = new Size(dpToPx(defaultSizeDp.getWidth(), mTmpMetrics),
152 dpToPx(defaultSizeDp.getHeight(), mTmpMetrics));
153 mScreenEdgeInsets = new Point(dpToPx(screenEdgeInsetsDp.getWidth(), mTmpMetrics),
154 dpToPx(screenEdgeInsetsDp.getHeight(), mTmpMetrics));
155 }
156
157 /**
158 * Registers a pinned stack listener.
159 */
160 void registerPinnedStackListener(IPinnedStackListener listener) {
161 try {
162 listener.asBinder().linkToDeath(mPinnedStackListenerDeathHandler, 0);
163 listener.onListenerRegistered(mCallbacks);
164 mPinnedStackListener = listener;
165 notifyBoundsChanged(mIsImeShowing);
166 } catch (RemoteException e) {
167 Log.e(TAG, "Failed to register pinned stack listener", e);
168 }
169 }
170
171 /**
Winson Chung84a38342016-11-08 16:15:10 -0800172 * Returns the current bounds (or the default bounds if there are no current bounds) with the
173 * specified aspect ratio.
174 */
175 Rect getAspectRatioBounds(Rect stackBounds, float aspectRatio) {
176 // Save the snap fraction, calculate the aspect ratio based on the current bounds
177 final float snapFraction = mSnapAlgorithm.getSnapFraction(stackBounds,
178 getMovementBounds(stackBounds));
179 final float radius = PointF.length(stackBounds.width(), stackBounds.height());
180 final int height = (int) Math.round(Math.sqrt((radius * radius) /
181 (aspectRatio * aspectRatio + 1)));
182 final int width = Math.round(height * aspectRatio);
183 final int left = (int) (stackBounds.centerX() - width / 2f);
184 final int top = (int) (stackBounds.centerY() - height / 2f);
185 stackBounds.set(left, top, left + width, top + height);
186 mSnapAlgorithm.applySnapFraction(stackBounds, getMovementBounds(stackBounds), snapFraction);
187 return stackBounds;
188 }
189
190 /**
Winson Chung655332c2016-10-31 13:14:28 -0700191 * @return the default bounds to show the PIP when there is no active PIP.
192 */
193 Rect getDefaultBounds() {
Winson Chung655332c2016-10-31 13:14:28 -0700194 final Rect insetBounds = new Rect();
Winson Chung14fefc22016-11-02 10:02:29 -0700195 getInsetBounds(insetBounds);
Winson Chung655332c2016-10-31 13:14:28 -0700196
197 final Rect defaultBounds = new Rect();
198 Gravity.apply(mDefaultStackGravity, mDefaultStackSize.getWidth(),
199 mDefaultStackSize.getHeight(), insetBounds, 0, 0, defaultBounds);
200 return defaultBounds;
201 }
202
203 /**
204 * @return the movement bounds for the given {@param stackBounds} and the current state of the
205 * controller.
206 */
207 Rect getMovementBounds(Rect stackBounds) {
Winson Chung14fefc22016-11-02 10:02:29 -0700208 return getMovementBounds(stackBounds, true /* adjustForIme */);
209 }
210
211 /**
212 * @return the movement bounds for the given {@param stackBounds} and the current state of the
213 * controller.
214 */
215 Rect getMovementBounds(Rect stackBounds, boolean adjustForIme) {
Winson Chung655332c2016-10-31 13:14:28 -0700216 final Rect movementBounds = new Rect();
Winson Chung14fefc22016-11-02 10:02:29 -0700217 getInsetBounds(movementBounds);
Winson Chung655332c2016-10-31 13:14:28 -0700218
219 // Adjust the right/bottom to ensure the stack bounds never goes offscreen
220 movementBounds.right = Math.max(movementBounds.left, movementBounds.right -
221 stackBounds.width());
222 movementBounds.bottom = Math.max(movementBounds.top, movementBounds.bottom -
223 stackBounds.height());
224
Winson Chung14fefc22016-11-02 10:02:29 -0700225 // Apply the movement bounds adjustments based on the current state
226 if (adjustForIme) {
227 if (mIsImeShowing) {
228 movementBounds.bottom -= mImeHeight;
229 }
Winson Chung655332c2016-10-31 13:14:28 -0700230 }
Winson Chung655332c2016-10-31 13:14:28 -0700231 return movementBounds;
232 }
233
234 /**
Winson Chung14fefc22016-11-02 10:02:29 -0700235 * @return the repositioned PIP bounds given it's pre-change bounds, and the new display info.
Winson Chung655332c2016-10-31 13:14:28 -0700236 */
Winson Chung14fefc22016-11-02 10:02:29 -0700237 Rect onDisplayChanged(Rect preChangeStackBounds, DisplayInfo displayInfo) {
238 final Rect postChangeStackBounds = new Rect(preChangeStackBounds);
239 if (!mDisplayInfo.equals(displayInfo)) {
240 // Calculate the snap fraction of the current stack along the old movement bounds, and
241 // then update the stack bounds to the same fraction along the rotated movement bounds.
242 final Rect preChangeMovementBounds = getMovementBounds(preChangeStackBounds);
243 final float snapFraction = mSnapAlgorithm.getSnapFraction(preChangeStackBounds,
244 preChangeMovementBounds);
245 mDisplayInfo.copyFrom(displayInfo);
246
247 final Rect postChangeMovementBounds = getMovementBounds(preChangeStackBounds,
248 false /* adjustForIme */);
249 mSnapAlgorithm.applySnapFraction(postChangeStackBounds, postChangeMovementBounds,
250 snapFraction);
251 }
252 return postChangeStackBounds;
Winson Chung655332c2016-10-31 13:14:28 -0700253 }
254
255 /**
256 * Sets the Ime state and height.
257 */
258 void setAdjustedForIme(boolean adjustedForIme, int imeHeight) {
259 // Return early if there is no state change
260 if (mIsImeShowing == adjustedForIme && mImeHeight == imeHeight) {
261 return;
262 }
263
264 final Rect stackBounds = new Rect();
265 mService.getStackBounds(PINNED_STACK_ID, stackBounds);
266 final Rect prevMovementBounds = getMovementBounds(stackBounds);
Winson Chung655332c2016-10-31 13:14:28 -0700267 mIsImeShowing = adjustedForIme;
268 mImeHeight = imeHeight;
269 if (mInInteractiveMode) {
270 // If the user is currently interacting with the PIP and the ime state changes, then
271 // don't adjust the bounds and defer that to after the interaction
272 notifyBoundsChanged(adjustedForIme /* adjustedForIme */);
273 } else {
274 // Otherwise, we can move the PIP to a sane location to ensure that it does not block
275 // the user from interacting with the IME
Winson Chung14fefc22016-11-02 10:02:29 -0700276 final Rect movementBounds = getMovementBounds(stackBounds);
277 final Rect toBounds = new Rect(stackBounds);
278 if (adjustedForIme) {
279 // IME visible
280 toBounds.offset(0, Math.min(0, movementBounds.bottom - stackBounds.top));
Winson Chung655332c2016-10-31 13:14:28 -0700281 } else {
Winson Chung14fefc22016-11-02 10:02:29 -0700282 // IME hidden
283 if (stackBounds.top == prevMovementBounds.bottom) {
284 // If the PIP is resting on top of the IME, then adjust it with the hiding IME
285 toBounds.offsetTo(toBounds.left, movementBounds.bottom);
286 }
Winson Chung655332c2016-10-31 13:14:28 -0700287 }
288 if (!toBounds.equals(stackBounds)) {
289 if (mBoundsAnimator != null) {
290 mBoundsAnimator.cancel();
291 }
292 mBoundsAnimator = mMotionHelper.createAnimationToBounds(stackBounds, toBounds);
293 mBoundsAnimator.start();
294 }
295 }
296 }
297
298 /**
Winson Chung655332c2016-10-31 13:14:28 -0700299 * Sends a broadcast that the PIP movement bounds have changed.
300 */
301 private void notifyBoundsChanged(boolean adjustedForIme) {
302 if (mPinnedStackListener != null) {
303 try {
304 mPinnedStackListener.onBoundsChanged(adjustedForIme);
305 } catch (RemoteException e) {
306 Slog.e(TAG_WM, "Error delivering bounds changed event.", e);
307 }
308 }
309 }
310
311 /**
312 * @return the bounds on the screen that the PIP can be visible in.
313 */
Winson Chung14fefc22016-11-02 10:02:29 -0700314 private void getInsetBounds(Rect outRect) {
315 mService.mPolicy.getStableInsetsLw(mDisplayInfo.rotation, mDisplayInfo.logicalWidth,
316 mDisplayInfo.logicalHeight, mTmpInsets);
317 outRect.set(mTmpInsets.left + mScreenEdgeInsets.x, mTmpInsets.top + mScreenEdgeInsets.y,
318 mDisplayInfo.logicalWidth - mTmpInsets.right - mScreenEdgeInsets.x,
319 mDisplayInfo.logicalHeight - mTmpInsets.bottom - mScreenEdgeInsets.y);
Winson Chung655332c2016-10-31 13:14:28 -0700320 }
321
322 /**
323 * @return the pixels for a given dp value.
324 */
325 private int dpToPx(float dpValue, DisplayMetrics dm) {
326 return (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP, dpValue, dm);
327 }
328
329 void dump(String prefix, PrintWriter pw) {
330 pw.println(prefix + "PinnedStackController");
Jorim Jaggiad5d2842016-11-01 18:22:53 -0700331 pw.print(prefix + " defaultBounds="); getDefaultBounds().printShortString(pw);
332 pw.println();
333 mService.getStackBounds(PINNED_STACK_ID, mTmpRect);
334 pw.print(prefix + " movementBounds="); getMovementBounds(mTmpRect).printShortString(pw);
335 pw.println();
Winson Chung655332c2016-10-31 13:14:28 -0700336 pw.println(prefix + " mIsImeShowing=" + mIsImeShowing);
337 pw.println(prefix + " mInInteractiveMode=" + mInInteractiveMode);
338 }
339}