blob: 023a6991e76c55f52c8c5f954379c7eb3ae56304 [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;
Wale Ogunwaleb783fd82016-11-04 09:51:54 -070046import com.android.server.UiThread;
Winson Chung655332c2016-10-31 13:14:28 -070047
48import java.io.PrintWriter;
49
50/**
51 * Holds the common state of the pinned stack between the system and SystemUI.
52 */
53class PinnedStackController {
54
55 private static final String TAG = TAG_WITH_CLASS_NAME ? "PinnedStackController" : TAG_WM;
56
57 private final WindowManagerService mService;
58 private final DisplayContent mDisplayContent;
Wale Ogunwaleb783fd82016-11-04 09:51:54 -070059 private final Handler mHandler = UiThread.getHandler();
Winson Chung655332c2016-10-31 13:14:28 -070060
61 private IPinnedStackListener mPinnedStackListener;
62 private final PinnedStackListenerDeathHandler mPinnedStackListenerDeathHandler =
63 new PinnedStackListenerDeathHandler();
64
65 private final PinnedStackControllerCallback mCallbacks = new PinnedStackControllerCallback();
66 private final PipSnapAlgorithm mSnapAlgorithm;
67 private final PipMotionHelper mMotionHelper;
68
69 // States that affect how the PIP can be manipulated
70 private boolean mInInteractiveMode;
71 private boolean mIsImeShowing;
72 private int mImeHeight;
Winson Chung655332c2016-10-31 13:14:28 -070073 private ValueAnimator mBoundsAnimator = null;
74
Winson Chung14fefc22016-11-02 10:02:29 -070075 // Used to calculate stack bounds across rotations
76 private final DisplayInfo mDisplayInfo = new DisplayInfo();
77
Winson Chung655332c2016-10-31 13:14:28 -070078 // The size and position information that describes where the pinned stack will go by default.
79 private int mDefaultStackGravity;
80 private Size mDefaultStackSize;
81 private Point mScreenEdgeInsets;
82
83 // Temp vars for calculation
84 private final DisplayMetrics mTmpMetrics = new DisplayMetrics();
85 private final Rect mTmpInsets = new Rect();
Jorim Jaggiad5d2842016-11-01 18:22:53 -070086 private final Rect mTmpRect = new Rect();
Winson Chung655332c2016-10-31 13:14:28 -070087
88 /**
89 * The callback object passed to listeners for them to notify the controller of state changes.
90 */
91 private class PinnedStackControllerCallback extends IPinnedStackController.Stub {
92
93 @Override
94 public void setInInteractiveMode(final boolean inInteractiveMode) {
95 mHandler.post(() -> {
96 // Cancel any existing animations on the PIP once the user starts dragging it
97 if (mBoundsAnimator != null && inInteractiveMode) {
98 mBoundsAnimator.cancel();
99 }
100 mInInteractiveMode = inInteractiveMode;
Winson Chung655332c2016-10-31 13:14:28 -0700101 });
102 }
Winson Chungdff5c082016-11-02 17:28:03 -0700103
104 @Override
105 public void setSnapToEdge(final boolean snapToEdge) {
106 mHandler.post(() -> {
107 mSnapAlgorithm.setSnapToEdge(snapToEdge);
108 });
109 }
Winson Chung655332c2016-10-31 13:14:28 -0700110 }
111
112 /**
113 * Handler for the case where the listener dies.
114 */
115 private class PinnedStackListenerDeathHandler implements IBinder.DeathRecipient {
116
117 @Override
118 public void binderDied() {
119 // Clean up the state if the listener dies
120 mInInteractiveMode = false;
121 mPinnedStackListener = null;
122 }
123 }
124
125 PinnedStackController(WindowManagerService service, DisplayContent displayContent) {
126 mService = service;
127 mDisplayContent = displayContent;
128 mSnapAlgorithm = new PipSnapAlgorithm(service.mContext);
129 mMotionHelper = new PipMotionHelper(BackgroundThread.getHandler());
Winson Chung14fefc22016-11-02 10:02:29 -0700130 mDisplayInfo.copyFrom(mDisplayContent.getDisplayInfo());
Winson Chung655332c2016-10-31 13:14:28 -0700131 reloadResources();
132 }
133
134 void onConfigurationChanged() {
135 reloadResources();
136 }
137
138 /**
139 * Reloads all the resources for the current configuration.
140 */
141 void reloadResources() {
142 final Resources res = mService.mContext.getResources();
143 final Size defaultSizeDp = Size.parseSize(res.getString(
144 com.android.internal.R.string.config_defaultPictureInPictureSize));
145 final Size screenEdgeInsetsDp = Size.parseSize(res.getString(
146 com.android.internal.R.string.config_defaultPictureInPictureScreenEdgeInsets));
147 mDefaultStackGravity = res.getInteger(
148 com.android.internal.R.integer.config_defaultPictureInPictureGravity);
149 mDisplayContent.getDisplay().getRealMetrics(mTmpMetrics);
150 mDefaultStackSize = new Size(dpToPx(defaultSizeDp.getWidth(), mTmpMetrics),
151 dpToPx(defaultSizeDp.getHeight(), mTmpMetrics));
152 mScreenEdgeInsets = new Point(dpToPx(screenEdgeInsetsDp.getWidth(), mTmpMetrics),
153 dpToPx(screenEdgeInsetsDp.getHeight(), mTmpMetrics));
154 }
155
156 /**
157 * Registers a pinned stack listener.
158 */
159 void registerPinnedStackListener(IPinnedStackListener listener) {
160 try {
161 listener.asBinder().linkToDeath(mPinnedStackListenerDeathHandler, 0);
162 listener.onListenerRegistered(mCallbacks);
163 mPinnedStackListener = listener;
164 notifyBoundsChanged(mIsImeShowing);
165 } catch (RemoteException e) {
166 Log.e(TAG, "Failed to register pinned stack listener", e);
167 }
168 }
169
170 /**
171 * @return the default bounds to show the PIP when there is no active PIP.
172 */
173 Rect getDefaultBounds() {
Winson Chung655332c2016-10-31 13:14:28 -0700174 final Rect insetBounds = new Rect();
Winson Chung14fefc22016-11-02 10:02:29 -0700175 getInsetBounds(insetBounds);
Winson Chung655332c2016-10-31 13:14:28 -0700176
177 final Rect defaultBounds = new Rect();
178 Gravity.apply(mDefaultStackGravity, mDefaultStackSize.getWidth(),
179 mDefaultStackSize.getHeight(), insetBounds, 0, 0, defaultBounds);
180 return defaultBounds;
181 }
182
183 /**
184 * @return the movement bounds for the given {@param stackBounds} and the current state of the
185 * controller.
186 */
187 Rect getMovementBounds(Rect stackBounds) {
Winson Chung14fefc22016-11-02 10:02:29 -0700188 return getMovementBounds(stackBounds, true /* adjustForIme */);
189 }
190
191 /**
192 * @return the movement bounds for the given {@param stackBounds} and the current state of the
193 * controller.
194 */
195 Rect getMovementBounds(Rect stackBounds, boolean adjustForIme) {
Winson Chung655332c2016-10-31 13:14:28 -0700196 final Rect movementBounds = new Rect();
Winson Chung14fefc22016-11-02 10:02:29 -0700197 getInsetBounds(movementBounds);
Winson Chung655332c2016-10-31 13:14:28 -0700198
199 // Adjust the right/bottom to ensure the stack bounds never goes offscreen
200 movementBounds.right = Math.max(movementBounds.left, movementBounds.right -
201 stackBounds.width());
202 movementBounds.bottom = Math.max(movementBounds.top, movementBounds.bottom -
203 stackBounds.height());
204
Winson Chung14fefc22016-11-02 10:02:29 -0700205 // Apply the movement bounds adjustments based on the current state
206 if (adjustForIme) {
207 if (mIsImeShowing) {
208 movementBounds.bottom -= mImeHeight;
209 }
Winson Chung655332c2016-10-31 13:14:28 -0700210 }
Winson Chung655332c2016-10-31 13:14:28 -0700211 return movementBounds;
212 }
213
214 /**
Winson Chung14fefc22016-11-02 10:02:29 -0700215 * @return the repositioned PIP bounds given it's pre-change bounds, and the new display info.
Winson Chung655332c2016-10-31 13:14:28 -0700216 */
Winson Chung14fefc22016-11-02 10:02:29 -0700217 Rect onDisplayChanged(Rect preChangeStackBounds, DisplayInfo displayInfo) {
218 final Rect postChangeStackBounds = new Rect(preChangeStackBounds);
219 if (!mDisplayInfo.equals(displayInfo)) {
220 // Calculate the snap fraction of the current stack along the old movement bounds, and
221 // then update the stack bounds to the same fraction along the rotated movement bounds.
222 final Rect preChangeMovementBounds = getMovementBounds(preChangeStackBounds);
223 final float snapFraction = mSnapAlgorithm.getSnapFraction(preChangeStackBounds,
224 preChangeMovementBounds);
225 mDisplayInfo.copyFrom(displayInfo);
226
227 final Rect postChangeMovementBounds = getMovementBounds(preChangeStackBounds,
228 false /* adjustForIme */);
229 mSnapAlgorithm.applySnapFraction(postChangeStackBounds, postChangeMovementBounds,
230 snapFraction);
231 }
232 return postChangeStackBounds;
Winson Chung655332c2016-10-31 13:14:28 -0700233 }
234
235 /**
236 * Sets the Ime state and height.
237 */
238 void setAdjustedForIme(boolean adjustedForIme, int imeHeight) {
239 // Return early if there is no state change
240 if (mIsImeShowing == adjustedForIme && mImeHeight == imeHeight) {
241 return;
242 }
243
244 final Rect stackBounds = new Rect();
245 mService.getStackBounds(PINNED_STACK_ID, stackBounds);
246 final Rect prevMovementBounds = getMovementBounds(stackBounds);
Winson Chung655332c2016-10-31 13:14:28 -0700247 mIsImeShowing = adjustedForIme;
248 mImeHeight = imeHeight;
249 if (mInInteractiveMode) {
250 // If the user is currently interacting with the PIP and the ime state changes, then
251 // don't adjust the bounds and defer that to after the interaction
252 notifyBoundsChanged(adjustedForIme /* adjustedForIme */);
253 } else {
254 // Otherwise, we can move the PIP to a sane location to ensure that it does not block
255 // the user from interacting with the IME
Winson Chung14fefc22016-11-02 10:02:29 -0700256 final Rect movementBounds = getMovementBounds(stackBounds);
257 final Rect toBounds = new Rect(stackBounds);
258 if (adjustedForIme) {
259 // IME visible
260 toBounds.offset(0, Math.min(0, movementBounds.bottom - stackBounds.top));
Winson Chung655332c2016-10-31 13:14:28 -0700261 } else {
Winson Chung14fefc22016-11-02 10:02:29 -0700262 // IME hidden
263 if (stackBounds.top == prevMovementBounds.bottom) {
264 // If the PIP is resting on top of the IME, then adjust it with the hiding IME
265 toBounds.offsetTo(toBounds.left, movementBounds.bottom);
266 }
Winson Chung655332c2016-10-31 13:14:28 -0700267 }
268 if (!toBounds.equals(stackBounds)) {
269 if (mBoundsAnimator != null) {
270 mBoundsAnimator.cancel();
271 }
272 mBoundsAnimator = mMotionHelper.createAnimationToBounds(stackBounds, toBounds);
273 mBoundsAnimator.start();
274 }
275 }
276 }
277
278 /**
Winson Chung655332c2016-10-31 13:14:28 -0700279 * Sends a broadcast that the PIP movement bounds have changed.
280 */
281 private void notifyBoundsChanged(boolean adjustedForIme) {
282 if (mPinnedStackListener != null) {
283 try {
284 mPinnedStackListener.onBoundsChanged(adjustedForIme);
285 } catch (RemoteException e) {
286 Slog.e(TAG_WM, "Error delivering bounds changed event.", e);
287 }
288 }
289 }
290
291 /**
292 * @return the bounds on the screen that the PIP can be visible in.
293 */
Winson Chung14fefc22016-11-02 10:02:29 -0700294 private void getInsetBounds(Rect outRect) {
295 mService.mPolicy.getStableInsetsLw(mDisplayInfo.rotation, mDisplayInfo.logicalWidth,
296 mDisplayInfo.logicalHeight, mTmpInsets);
297 outRect.set(mTmpInsets.left + mScreenEdgeInsets.x, mTmpInsets.top + mScreenEdgeInsets.y,
298 mDisplayInfo.logicalWidth - mTmpInsets.right - mScreenEdgeInsets.x,
299 mDisplayInfo.logicalHeight - mTmpInsets.bottom - mScreenEdgeInsets.y);
Winson Chung655332c2016-10-31 13:14:28 -0700300 }
301
302 /**
303 * @return the pixels for a given dp value.
304 */
305 private int dpToPx(float dpValue, DisplayMetrics dm) {
306 return (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP, dpValue, dm);
307 }
308
309 void dump(String prefix, PrintWriter pw) {
310 pw.println(prefix + "PinnedStackController");
Jorim Jaggiad5d2842016-11-01 18:22:53 -0700311 pw.print(prefix + " defaultBounds="); getDefaultBounds().printShortString(pw);
312 pw.println();
313 mService.getStackBounds(PINNED_STACK_ID, mTmpRect);
314 pw.print(prefix + " movementBounds="); getMovementBounds(mTmpRect).printShortString(pw);
315 pw.println();
Winson Chung655332c2016-10-31 13:14:28 -0700316 pw.println(prefix + " mIsImeShowing=" + mIsImeShowing);
317 pw.println(prefix + " mInInteractiveMode=" + mInInteractiveMode);
318 }
319}