blob: 0ad4e0a6479181a74b9fae1b3ca29a690fa6afd5 [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;
Winson Chungfa7053782016-11-08 15:45:10 -080072 private boolean mIsMinimized;
Winson Chung655332c2016-10-31 13:14:28 -070073 private boolean mIsImeShowing;
74 private int mImeHeight;
Winson Chung655332c2016-10-31 13:14:28 -070075 private ValueAnimator mBoundsAnimator = null;
76
Winson Chung14fefc22016-11-02 10:02:29 -070077 // Used to calculate stack bounds across rotations
78 private final DisplayInfo mDisplayInfo = new DisplayInfo();
79
Winson Chung655332c2016-10-31 13:14:28 -070080 // The size and position information that describes where the pinned stack will go by default.
81 private int mDefaultStackGravity;
82 private Size mDefaultStackSize;
83 private Point mScreenEdgeInsets;
84
85 // Temp vars for calculation
86 private final DisplayMetrics mTmpMetrics = new DisplayMetrics();
87 private final Rect mTmpInsets = new Rect();
Jorim Jaggiad5d2842016-11-01 18:22:53 -070088 private final Rect mTmpRect = new Rect();
Winson Chung655332c2016-10-31 13:14:28 -070089
90 /**
91 * The callback object passed to listeners for them to notify the controller of state changes.
92 */
93 private class PinnedStackControllerCallback extends IPinnedStackController.Stub {
94
95 @Override
96 public void setInInteractiveMode(final boolean inInteractiveMode) {
97 mHandler.post(() -> {
98 // Cancel any existing animations on the PIP once the user starts dragging it
99 if (mBoundsAnimator != null && inInteractiveMode) {
100 mBoundsAnimator.cancel();
101 }
102 mInInteractiveMode = inInteractiveMode;
Winson Chung655332c2016-10-31 13:14:28 -0700103 });
104 }
Winson Chungdff5c082016-11-02 17:28:03 -0700105
106 @Override
Winson Chungfa7053782016-11-08 15:45:10 -0800107 public void setIsMinimized(final boolean isMinimized) {
108 mHandler.post(() -> {
109 mIsMinimized = isMinimized;
110 });
111 }
112
113 @Override
Winson Chungdff5c082016-11-02 17:28:03 -0700114 public void setSnapToEdge(final boolean snapToEdge) {
115 mHandler.post(() -> {
116 mSnapAlgorithm.setSnapToEdge(snapToEdge);
117 });
118 }
Winson Chung655332c2016-10-31 13:14:28 -0700119 }
120
121 /**
122 * Handler for the case where the listener dies.
123 */
124 private class PinnedStackListenerDeathHandler implements IBinder.DeathRecipient {
125
126 @Override
127 public void binderDied() {
128 // Clean up the state if the listener dies
129 mInInteractiveMode = false;
130 mPinnedStackListener = null;
131 }
132 }
133
134 PinnedStackController(WindowManagerService service, DisplayContent displayContent) {
135 mService = service;
136 mDisplayContent = displayContent;
137 mSnapAlgorithm = new PipSnapAlgorithm(service.mContext);
Winson Chungb5c41b72016-12-07 15:00:47 -0800138 mMotionHelper = new PipMotionHelper(UiThread.getHandler());
Winson Chung14fefc22016-11-02 10:02:29 -0700139 mDisplayInfo.copyFrom(mDisplayContent.getDisplayInfo());
Winson Chung655332c2016-10-31 13:14:28 -0700140 reloadResources();
141 }
142
143 void onConfigurationChanged() {
144 reloadResources();
145 }
146
147 /**
148 * Reloads all the resources for the current configuration.
149 */
150 void reloadResources() {
151 final Resources res = mService.mContext.getResources();
152 final Size defaultSizeDp = Size.parseSize(res.getString(
153 com.android.internal.R.string.config_defaultPictureInPictureSize));
154 final Size screenEdgeInsetsDp = Size.parseSize(res.getString(
155 com.android.internal.R.string.config_defaultPictureInPictureScreenEdgeInsets));
156 mDefaultStackGravity = res.getInteger(
157 com.android.internal.R.integer.config_defaultPictureInPictureGravity);
158 mDisplayContent.getDisplay().getRealMetrics(mTmpMetrics);
159 mDefaultStackSize = new Size(dpToPx(defaultSizeDp.getWidth(), mTmpMetrics),
160 dpToPx(defaultSizeDp.getHeight(), mTmpMetrics));
161 mScreenEdgeInsets = new Point(dpToPx(screenEdgeInsetsDp.getWidth(), mTmpMetrics),
162 dpToPx(screenEdgeInsetsDp.getHeight(), mTmpMetrics));
163 }
164
165 /**
166 * Registers a pinned stack listener.
167 */
168 void registerPinnedStackListener(IPinnedStackListener listener) {
169 try {
170 listener.asBinder().linkToDeath(mPinnedStackListenerDeathHandler, 0);
171 listener.onListenerRegistered(mCallbacks);
172 mPinnedStackListener = listener;
173 notifyBoundsChanged(mIsImeShowing);
174 } catch (RemoteException e) {
175 Log.e(TAG, "Failed to register pinned stack listener", e);
176 }
177 }
178
179 /**
Winson Chung84a38342016-11-08 16:15:10 -0800180 * Returns the current bounds (or the default bounds if there are no current bounds) with the
181 * specified aspect ratio.
182 */
183 Rect getAspectRatioBounds(Rect stackBounds, float aspectRatio) {
184 // Save the snap fraction, calculate the aspect ratio based on the current bounds
185 final float snapFraction = mSnapAlgorithm.getSnapFraction(stackBounds,
186 getMovementBounds(stackBounds));
187 final float radius = PointF.length(stackBounds.width(), stackBounds.height());
188 final int height = (int) Math.round(Math.sqrt((radius * radius) /
189 (aspectRatio * aspectRatio + 1)));
190 final int width = Math.round(height * aspectRatio);
191 final int left = (int) (stackBounds.centerX() - width / 2f);
192 final int top = (int) (stackBounds.centerY() - height / 2f);
193 stackBounds.set(left, top, left + width, top + height);
194 mSnapAlgorithm.applySnapFraction(stackBounds, getMovementBounds(stackBounds), snapFraction);
195 return stackBounds;
196 }
197
198 /**
Winson Chung655332c2016-10-31 13:14:28 -0700199 * @return the default bounds to show the PIP when there is no active PIP.
200 */
201 Rect getDefaultBounds() {
Winson Chung655332c2016-10-31 13:14:28 -0700202 final Rect insetBounds = new Rect();
Winson Chung14fefc22016-11-02 10:02:29 -0700203 getInsetBounds(insetBounds);
Winson Chung655332c2016-10-31 13:14:28 -0700204
205 final Rect defaultBounds = new Rect();
206 Gravity.apply(mDefaultStackGravity, mDefaultStackSize.getWidth(),
207 mDefaultStackSize.getHeight(), insetBounds, 0, 0, defaultBounds);
208 return defaultBounds;
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) {
Winson Chung14fefc22016-11-02 10:02:29 -0700216 return getMovementBounds(stackBounds, true /* adjustForIme */);
217 }
218
219 /**
220 * @return the movement bounds for the given {@param stackBounds} and the current state of the
221 * controller.
222 */
223 Rect getMovementBounds(Rect stackBounds, boolean adjustForIme) {
Winson Chung655332c2016-10-31 13:14:28 -0700224 final Rect movementBounds = new Rect();
Winson Chung14fefc22016-11-02 10:02:29 -0700225 getInsetBounds(movementBounds);
Winson Chung655332c2016-10-31 13:14:28 -0700226
227 // Adjust the right/bottom to ensure the stack bounds never goes offscreen
228 movementBounds.right = Math.max(movementBounds.left, movementBounds.right -
229 stackBounds.width());
230 movementBounds.bottom = Math.max(movementBounds.top, movementBounds.bottom -
231 stackBounds.height());
232
Winson Chung14fefc22016-11-02 10:02:29 -0700233 // Apply the movement bounds adjustments based on the current state
234 if (adjustForIme) {
235 if (mIsImeShowing) {
236 movementBounds.bottom -= mImeHeight;
237 }
Winson Chung655332c2016-10-31 13:14:28 -0700238 }
Winson Chung655332c2016-10-31 13:14:28 -0700239 return movementBounds;
240 }
241
242 /**
Winson Chung14fefc22016-11-02 10:02:29 -0700243 * @return the repositioned PIP bounds given it's pre-change bounds, and the new display info.
Winson Chung655332c2016-10-31 13:14:28 -0700244 */
Winson Chung14fefc22016-11-02 10:02:29 -0700245 Rect onDisplayChanged(Rect preChangeStackBounds, DisplayInfo displayInfo) {
246 final Rect postChangeStackBounds = new Rect(preChangeStackBounds);
247 if (!mDisplayInfo.equals(displayInfo)) {
248 // Calculate the snap fraction of the current stack along the old movement bounds, and
249 // then update the stack bounds to the same fraction along the rotated movement bounds.
250 final Rect preChangeMovementBounds = getMovementBounds(preChangeStackBounds);
251 final float snapFraction = mSnapAlgorithm.getSnapFraction(preChangeStackBounds,
252 preChangeMovementBounds);
253 mDisplayInfo.copyFrom(displayInfo);
254
255 final Rect postChangeMovementBounds = getMovementBounds(preChangeStackBounds,
256 false /* adjustForIme */);
257 mSnapAlgorithm.applySnapFraction(postChangeStackBounds, postChangeMovementBounds,
258 snapFraction);
Winson Chungd5a01592016-11-11 16:25:04 -0800259 if (mIsMinimized) {
260 final Point displaySize = new Point(mDisplayInfo.logicalWidth,
261 mDisplayInfo.logicalHeight);
262 mSnapAlgorithm.applyMinimizedOffset(postChangeStackBounds, postChangeMovementBounds,
263 displaySize);
264 }
Winson Chung14fefc22016-11-02 10:02:29 -0700265 }
266 return postChangeStackBounds;
Winson Chung655332c2016-10-31 13:14:28 -0700267 }
268
269 /**
270 * Sets the Ime state and height.
271 */
272 void setAdjustedForIme(boolean adjustedForIme, int imeHeight) {
273 // Return early if there is no state change
274 if (mIsImeShowing == adjustedForIme && mImeHeight == imeHeight) {
275 return;
276 }
277
278 final Rect stackBounds = new Rect();
279 mService.getStackBounds(PINNED_STACK_ID, stackBounds);
280 final Rect prevMovementBounds = getMovementBounds(stackBounds);
Winson Chung655332c2016-10-31 13:14:28 -0700281 mIsImeShowing = adjustedForIme;
282 mImeHeight = imeHeight;
283 if (mInInteractiveMode) {
284 // If the user is currently interacting with the PIP and the ime state changes, then
285 // don't adjust the bounds and defer that to after the interaction
286 notifyBoundsChanged(adjustedForIme /* adjustedForIme */);
287 } else {
288 // Otherwise, we can move the PIP to a sane location to ensure that it does not block
289 // the user from interacting with the IME
Winson Chung14fefc22016-11-02 10:02:29 -0700290 final Rect movementBounds = getMovementBounds(stackBounds);
291 final Rect toBounds = new Rect(stackBounds);
292 if (adjustedForIme) {
293 // IME visible
Winson Chungd5a01592016-11-11 16:25:04 -0800294 if (stackBounds.top == prevMovementBounds.bottom) {
295 // If the PIP is resting on top of the IME, then adjust it with the hiding IME
296 toBounds.offsetTo(toBounds.left, movementBounds.bottom);
297 } else {
298 toBounds.offset(0, Math.min(0, movementBounds.bottom - stackBounds.top));
299 }
Winson Chung655332c2016-10-31 13:14:28 -0700300 } else {
Winson Chung14fefc22016-11-02 10:02:29 -0700301 // IME hidden
302 if (stackBounds.top == prevMovementBounds.bottom) {
303 // If the PIP is resting on top of the IME, then adjust it with the hiding IME
304 toBounds.offsetTo(toBounds.left, movementBounds.bottom);
305 }
Winson Chung655332c2016-10-31 13:14:28 -0700306 }
307 if (!toBounds.equals(stackBounds)) {
308 if (mBoundsAnimator != null) {
309 mBoundsAnimator.cancel();
310 }
311 mBoundsAnimator = mMotionHelper.createAnimationToBounds(stackBounds, toBounds);
312 mBoundsAnimator.start();
313 }
314 }
315 }
316
317 /**
Winson Chung655332c2016-10-31 13:14:28 -0700318 * Sends a broadcast that the PIP movement bounds have changed.
319 */
320 private void notifyBoundsChanged(boolean adjustedForIme) {
321 if (mPinnedStackListener != null) {
322 try {
323 mPinnedStackListener.onBoundsChanged(adjustedForIme);
324 } catch (RemoteException e) {
325 Slog.e(TAG_WM, "Error delivering bounds changed event.", e);
326 }
327 }
328 }
329
330 /**
331 * @return the bounds on the screen that the PIP can be visible in.
332 */
Winson Chung14fefc22016-11-02 10:02:29 -0700333 private void getInsetBounds(Rect outRect) {
334 mService.mPolicy.getStableInsetsLw(mDisplayInfo.rotation, mDisplayInfo.logicalWidth,
335 mDisplayInfo.logicalHeight, mTmpInsets);
336 outRect.set(mTmpInsets.left + mScreenEdgeInsets.x, mTmpInsets.top + mScreenEdgeInsets.y,
337 mDisplayInfo.logicalWidth - mTmpInsets.right - mScreenEdgeInsets.x,
338 mDisplayInfo.logicalHeight - mTmpInsets.bottom - mScreenEdgeInsets.y);
Winson Chung655332c2016-10-31 13:14:28 -0700339 }
340
341 /**
342 * @return the pixels for a given dp value.
343 */
344 private int dpToPx(float dpValue, DisplayMetrics dm) {
345 return (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP, dpValue, dm);
346 }
347
348 void dump(String prefix, PrintWriter pw) {
349 pw.println(prefix + "PinnedStackController");
Jorim Jaggiad5d2842016-11-01 18:22:53 -0700350 pw.print(prefix + " defaultBounds="); getDefaultBounds().printShortString(pw);
351 pw.println();
352 mService.getStackBounds(PINNED_STACK_ID, mTmpRect);
353 pw.print(prefix + " movementBounds="); getMovementBounds(mTmpRect).printShortString(pw);
354 pw.println();
Winson Chung655332c2016-10-31 13:14:28 -0700355 pw.println(prefix + " mIsImeShowing=" + mIsImeShowing);
356 pw.println(prefix + " mInInteractiveMode=" + mInInteractiveMode);
Winson Chungfa7053782016-11-08 15:45:10 -0800357 pw.println(prefix + " mIsMinimized=" + mIsMinimized);
Winson Chung655332c2016-10-31 13:14:28 -0700358 }
359}