1 /*
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 
17 package com.android.server.wm;
18 
19 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
20 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
21 import static android.util.TypedValue.COMPLEX_UNIT_DIP;
22 
23 import static com.android.server.wm.PinnedStackControllerProto.DEFAULT_BOUNDS;
24 import static com.android.server.wm.PinnedStackControllerProto.MOVEMENT_BOUNDS;
25 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
26 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
27 
28 import android.annotation.NonNull;
29 import android.app.RemoteAction;
30 import android.content.pm.ParceledListSlice;
31 import android.content.res.Resources;
32 import android.graphics.Point;
33 import android.graphics.Rect;
34 import android.os.Handler;
35 import android.os.IBinder;
36 import android.os.RemoteException;
37 import android.util.DisplayMetrics;
38 import android.util.Log;
39 import android.util.Size;
40 import android.util.Slog;
41 import android.util.TypedValue;
42 import android.util.proto.ProtoOutputStream;
43 import android.view.DisplayInfo;
44 import android.view.Gravity;
45 import android.view.IPinnedStackController;
46 import android.view.IPinnedStackListener;
47 
48 import com.android.internal.policy.PipSnapAlgorithm;
49 import com.android.internal.util.Preconditions;
50 import com.android.server.UiThread;
51 
52 import java.io.PrintWriter;
53 import java.lang.ref.WeakReference;
54 import java.util.ArrayList;
55 import java.util.List;
56 
57 /**
58  * Holds the common state of the pinned stack between the system and SystemUI. If SystemUI ever
59  * needs to be restarted, it will be notified with the last known state.
60  *
61  * Changes to the pinned stack also flow through this controller, and generally, the system only
62  * changes the pinned stack bounds through this controller in two ways:
63  *
64  * 1) When first entering PiP: the controller returns the valid bounds given, taking aspect ratio
65  *    and IME state into account.
66  * 2) When rotating the device: the controller calculates the new bounds in the new orientation,
67  *    taking the minimized and IME state into account. In this case, we currently ignore the
68  *    SystemUI adjustments (ie. expanded for menu, interaction, etc).
69  *
70  * Other changes in the system, including adjustment of IME, configuration change, and more are
71  * handled by SystemUI (similar to the docked stack divider).
72  */
73 class PinnedStackController {
74 
75     private static final String TAG = TAG_WITH_CLASS_NAME ? "PinnedStackController" : TAG_WM;
76 
77     public static final float INVALID_SNAP_FRACTION = -1f;
78     private final WindowManagerService mService;
79     private final DisplayContent mDisplayContent;
80     private final Handler mHandler = UiThread.getHandler();
81 
82     private IPinnedStackListener mPinnedStackListener;
83     private final PinnedStackListenerDeathHandler mPinnedStackListenerDeathHandler =
84             new PinnedStackListenerDeathHandler();
85 
86     private final PinnedStackControllerCallback mCallbacks = new PinnedStackControllerCallback();
87     private final PipSnapAlgorithm mSnapAlgorithm;
88 
89     // States that affect how the PIP can be manipulated
90     private boolean mIsMinimized;
91     private boolean mIsImeShowing;
92     private int mImeHeight;
93     private boolean mIsShelfShowing;
94     private int mShelfHeight;
95 
96     // The set of actions and aspect-ratio for the that are currently allowed on the PiP activity
97     private ArrayList<RemoteAction> mActions = new ArrayList<>();
98     private float mAspectRatio = -1f;
99 
100     // Used to calculate stack bounds across rotations
101     private final DisplayInfo mDisplayInfo = new DisplayInfo();
102     private final Rect mStableInsets = new Rect();
103 
104     // The size and position information that describes where the pinned stack will go by default.
105     private int mDefaultMinSize;
106     private int mDefaultStackGravity;
107     private float mDefaultAspectRatio;
108     private Point mScreenEdgeInsets;
109     private int mCurrentMinSize;
110     private float mReentrySnapFraction = INVALID_SNAP_FRACTION;
111     private WeakReference<AppWindowToken> mLastPipActivity = null;
112 
113     // The aspect ratio bounds of the PIP.
114     private float mMinAspectRatio;
115     private float mMaxAspectRatio;
116 
117     // Temp vars for calculation
118     private final DisplayMetrics mTmpMetrics = new DisplayMetrics();
119     private final Rect mTmpInsets = new Rect();
120     private final Rect mTmpRect = new Rect();
121     private final Rect mTmpAnimatingBoundsRect = new Rect();
122     private final Point mTmpDisplaySize = new Point();
123 
124 
125     /**
126      * The callback object passed to listeners for them to notify the controller of state changes.
127      */
128     private class PinnedStackControllerCallback extends IPinnedStackController.Stub {
129 
130         @Override
setIsMinimized(final boolean isMinimized)131         public void setIsMinimized(final boolean isMinimized) {
132             mHandler.post(() -> {
133                 mIsMinimized = isMinimized;
134                 mSnapAlgorithm.setMinimized(isMinimized);
135             });
136         }
137 
138         @Override
setMinEdgeSize(int minEdgeSize)139         public void setMinEdgeSize(int minEdgeSize) {
140             mHandler.post(() -> {
141                 mCurrentMinSize = Math.max(mDefaultMinSize, minEdgeSize);
142             });
143         }
144 
145         @Override
getDisplayRotation()146         public int getDisplayRotation() {
147             synchronized (mService.mGlobalLock) {
148                 return mDisplayInfo.rotation;
149             }
150         }
151     }
152 
153     /**
154      * Handler for the case where the listener dies.
155      */
156     private class PinnedStackListenerDeathHandler implements IBinder.DeathRecipient {
157 
158         @Override
binderDied()159         public void binderDied() {
160             // Clean up the state if the listener dies
161             if (mPinnedStackListener != null) {
162                 mPinnedStackListener.asBinder().unlinkToDeath(mPinnedStackListenerDeathHandler, 0);
163             }
164             mPinnedStackListener = null;
165         }
166     }
167 
PinnedStackController(WindowManagerService service, DisplayContent displayContent)168     PinnedStackController(WindowManagerService service, DisplayContent displayContent) {
169         mService = service;
170         mDisplayContent = displayContent;
171         mSnapAlgorithm = new PipSnapAlgorithm(service.mContext);
172         mDisplayInfo.copyFrom(mDisplayContent.getDisplayInfo());
173         reloadResources();
174         // Initialize the aspect ratio to the default aspect ratio.  Don't do this in reload
175         // resources as it would clobber mAspectRatio when entering PiP from fullscreen which
176         // triggers a configuration change and the resources to be reloaded.
177         mAspectRatio = mDefaultAspectRatio;
178     }
179 
onConfigurationChanged()180     void onConfigurationChanged() {
181         reloadResources();
182     }
183 
184     /**
185      * Reloads all the resources for the current configuration.
186      */
reloadResources()187     private void reloadResources() {
188         final Resources res = mService.mContext.getResources();
189         mDefaultMinSize = res.getDimensionPixelSize(
190                 com.android.internal.R.dimen.default_minimal_size_pip_resizable_task);
191         mCurrentMinSize = mDefaultMinSize;
192         mDefaultAspectRatio = res.getFloat(
193                 com.android.internal.R.dimen.config_pictureInPictureDefaultAspectRatio);
194         final String screenEdgeInsetsDpString = res.getString(
195                 com.android.internal.R.string.config_defaultPictureInPictureScreenEdgeInsets);
196         final Size screenEdgeInsetsDp = !screenEdgeInsetsDpString.isEmpty()
197                 ? Size.parseSize(screenEdgeInsetsDpString)
198                 : null;
199         mDefaultStackGravity = res.getInteger(
200                 com.android.internal.R.integer.config_defaultPictureInPictureGravity);
201         mDisplayContent.getDisplay().getRealMetrics(mTmpMetrics);
202         mScreenEdgeInsets = screenEdgeInsetsDp == null ? new Point()
203                 : new Point(dpToPx(screenEdgeInsetsDp.getWidth(), mTmpMetrics),
204                         dpToPx(screenEdgeInsetsDp.getHeight(), mTmpMetrics));
205         mMinAspectRatio = res.getFloat(
206                 com.android.internal.R.dimen.config_pictureInPictureMinAspectRatio);
207         mMaxAspectRatio = res.getFloat(
208                 com.android.internal.R.dimen.config_pictureInPictureMaxAspectRatio);
209     }
210 
211     /**
212      * Registers a pinned stack listener.
213      */
registerPinnedStackListener(IPinnedStackListener listener)214     void registerPinnedStackListener(IPinnedStackListener listener) {
215         try {
216             listener.asBinder().linkToDeath(mPinnedStackListenerDeathHandler, 0);
217             listener.onListenerRegistered(mCallbacks);
218             mPinnedStackListener = listener;
219             notifyImeVisibilityChanged(mIsImeShowing, mImeHeight);
220             notifyShelfVisibilityChanged(mIsShelfShowing, mShelfHeight);
221             // The movement bounds notification needs to be sent before the minimized state, since
222             // SystemUI may use the bounds to retore the minimized position
223             notifyMovementBoundsChanged(false /* fromImeAdjustment */,
224                     false /* fromShelfAdjustment */);
225             notifyActionsChanged(mActions);
226             notifyMinimizeChanged(mIsMinimized);
227         } catch (RemoteException e) {
228             Log.e(TAG, "Failed to register pinned stack listener", e);
229         }
230     }
231 
232     /**
233      * @return whether the given {@param aspectRatio} is valid.
234      */
isValidPictureInPictureAspectRatio(float aspectRatio)235     public boolean isValidPictureInPictureAspectRatio(float aspectRatio) {
236         return Float.compare(mMinAspectRatio, aspectRatio) <= 0 &&
237                 Float.compare(aspectRatio, mMaxAspectRatio) <= 0;
238     }
239 
240     /**
241      * Returns the current bounds (or the default bounds if there are no current bounds) with the
242      * specified aspect ratio.
243      */
transformBoundsToAspectRatio(Rect stackBounds, float aspectRatio, boolean useCurrentMinEdgeSize)244     Rect transformBoundsToAspectRatio(Rect stackBounds, float aspectRatio,
245             boolean useCurrentMinEdgeSize) {
246         // Save the snap fraction, calculate the aspect ratio based on screen size
247         final float snapFraction = mSnapAlgorithm.getSnapFraction(stackBounds,
248                 getMovementBounds(stackBounds));
249 
250         final int minEdgeSize = useCurrentMinEdgeSize ? mCurrentMinSize : mDefaultMinSize;
251         final Size size = mSnapAlgorithm.getSizeForAspectRatio(aspectRatio, minEdgeSize,
252                 mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight);
253         final int left = (int) (stackBounds.centerX() - size.getWidth() / 2f);
254         final int top = (int) (stackBounds.centerY() - size.getHeight() / 2f);
255         stackBounds.set(left, top, left + size.getWidth(), top + size.getHeight());
256         mSnapAlgorithm.applySnapFraction(stackBounds, getMovementBounds(stackBounds), snapFraction);
257         if (mIsMinimized) {
258             applyMinimizedOffset(stackBounds, getMovementBounds(stackBounds));
259         }
260         return stackBounds;
261     }
262 
263     /**
264      * Saves the current snap fraction for re-entry of the current activity into PiP.
265      */
saveReentrySnapFraction(final AppWindowToken token, final Rect stackBounds)266     void saveReentrySnapFraction(final AppWindowToken token, final Rect stackBounds) {
267         mReentrySnapFraction = getSnapFraction(stackBounds);
268         mLastPipActivity = new WeakReference<>(token);
269     }
270 
271     /**
272      * Resets the last saved snap fraction so that the default bounds will be returned.
273      */
resetReentrySnapFraction(AppWindowToken token)274     void resetReentrySnapFraction(AppWindowToken token) {
275         if (mLastPipActivity != null && mLastPipActivity.get() == token) {
276             mReentrySnapFraction = INVALID_SNAP_FRACTION;
277             mLastPipActivity = null;
278         }
279     }
280 
281     /**
282      * @return the default bounds to show the PIP when there is no active PIP.
283      */
getDefaultOrLastSavedBounds()284     Rect getDefaultOrLastSavedBounds() {
285         return getDefaultBounds(mReentrySnapFraction);
286     }
287 
288     /**
289      * @return the default bounds to show the PIP, if a {@param snapFraction} is provided, then it
290      * will apply the default bounds to the provided snap fraction.
291      */
getDefaultBounds(float snapFraction)292     Rect getDefaultBounds(float snapFraction) {
293         synchronized (mService.mGlobalLock) {
294             final Rect insetBounds = new Rect();
295             getInsetBounds(insetBounds);
296 
297             final Rect defaultBounds = new Rect();
298             final Size size = mSnapAlgorithm.getSizeForAspectRatio(mDefaultAspectRatio,
299                     mDefaultMinSize, mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight);
300             if (snapFraction != INVALID_SNAP_FRACTION) {
301                 defaultBounds.set(0, 0, size.getWidth(), size.getHeight());
302                 final Rect movementBounds = getMovementBounds(defaultBounds);
303                 mSnapAlgorithm.applySnapFraction(defaultBounds, movementBounds, snapFraction);
304             } else {
305                 Gravity.apply(mDefaultStackGravity, size.getWidth(), size.getHeight(), insetBounds,
306                         0, Math.max(mIsImeShowing ? mImeHeight : 0,
307                                 mIsShelfShowing ? mShelfHeight : 0),
308                         defaultBounds);
309             }
310             return defaultBounds;
311         }
312     }
313 
314     /**
315      * In the case where the display rotation is changed but there is no stack, we can't depend on
316      * onTaskStackBoundsChanged() to be called.  But we still should update our known display info
317      * with the new state so that we can update SystemUI.
318      */
onDisplayInfoChanged(DisplayInfo displayInfo)319     synchronized void onDisplayInfoChanged(DisplayInfo displayInfo) {
320         mDisplayInfo.copyFrom(displayInfo);
321         notifyMovementBoundsChanged(false /* fromImeAdjustment */, false /* fromShelfAdjustment */);
322     }
323 
324     /**
325      * Updates the display info, calculating and returning the new stack and movement bounds in the
326      * new orientation of the device if necessary.
327      */
onTaskStackBoundsChanged(Rect targetBounds, Rect outBounds)328     boolean onTaskStackBoundsChanged(Rect targetBounds, Rect outBounds) {
329         synchronized (mService.mGlobalLock) {
330             final DisplayInfo displayInfo = mDisplayContent.getDisplayInfo();
331             if (isSameDimensionAndRotation(mDisplayInfo, displayInfo)) {
332                 // No dimension/rotation change, ignore
333                 outBounds.setEmpty();
334                 return false;
335             } else if (targetBounds.isEmpty()) {
336                 // The stack is null, we are just initializing the stack, so just store the display
337                 // info and ignore
338                 mDisplayInfo.copyFrom(displayInfo);
339                 outBounds.setEmpty();
340                 return false;
341             }
342 
343             mTmpRect.set(targetBounds);
344             final Rect postChangeStackBounds = mTmpRect;
345 
346             // Calculate the snap fraction of the current stack along the old movement bounds
347             final float snapFraction = getSnapFraction(postChangeStackBounds);
348             mDisplayInfo.copyFrom(displayInfo);
349 
350             // Calculate the stack bounds in the new orientation to the same same fraction along the
351             // rotated movement bounds.
352             final Rect postChangeMovementBounds = getMovementBounds(postChangeStackBounds,
353                     false /* adjustForIme */, false /* adjustForShelf */);
354             mSnapAlgorithm.applySnapFraction(postChangeStackBounds, postChangeMovementBounds,
355                     snapFraction);
356             if (mIsMinimized) {
357                 applyMinimizedOffset(postChangeStackBounds, postChangeMovementBounds);
358             }
359 
360             notifyMovementBoundsChanged(false /* fromImeAdjustment */,
361                     false /* fromShelfAdjustment */);
362 
363             outBounds.set(postChangeStackBounds);
364             return true;
365         }
366     }
367 
368     /**
369      * Sets the Ime state and height.
370      */
setAdjustedForIme(boolean adjustedForIme, int imeHeight)371     void setAdjustedForIme(boolean adjustedForIme, int imeHeight) {
372         // Due to the order of callbacks from the system, we may receive an ime height even when
373         // {@param adjustedForIme} is false, and also a zero height when {@param adjustedForIme}
374         // is true.  Instead, ensure that the ime state changes with the height and if the ime is
375         // showing, then the height is non-zero.
376         final boolean imeShowing = adjustedForIme && imeHeight > 0;
377         imeHeight = imeShowing ? imeHeight : 0;
378         if (imeShowing == mIsImeShowing && imeHeight == mImeHeight) {
379             return;
380         }
381 
382         mIsImeShowing = imeShowing;
383         mImeHeight = imeHeight;
384         notifyImeVisibilityChanged(imeShowing, imeHeight);
385         notifyMovementBoundsChanged(true /* fromImeAdjustment */, false /* fromShelfAdjustment */);
386     }
387 
388     /**
389      * Sets the shelf state and height.
390      */
setAdjustedForShelf(boolean adjustedForShelf, int shelfHeight)391     void setAdjustedForShelf(boolean adjustedForShelf, int shelfHeight) {
392         final boolean shelfShowing = adjustedForShelf && shelfHeight > 0;
393         if (shelfShowing == mIsShelfShowing && shelfHeight == mShelfHeight) {
394             return;
395         }
396 
397         mIsShelfShowing = shelfShowing;
398         mShelfHeight = shelfHeight;
399         notifyShelfVisibilityChanged(shelfShowing, shelfHeight);
400         notifyMovementBoundsChanged(false /* fromImeAdjustment */, true /* fromShelfAdjustment */);
401     }
402 
403     /**
404      * Sets the current aspect ratio.
405      */
setAspectRatio(float aspectRatio)406     void setAspectRatio(float aspectRatio) {
407         if (Float.compare(mAspectRatio, aspectRatio) != 0) {
408             mAspectRatio = aspectRatio;
409             notifyMovementBoundsChanged(false /* fromImeAdjustment */,
410                     false /* fromShelfAdjustment */);
411         }
412     }
413 
414     /**
415      * @return the current aspect ratio.
416      */
getAspectRatio()417     float getAspectRatio() {
418         return mAspectRatio;
419     }
420 
421     /**
422      * Sets the current set of actions.
423      */
setActions(List<RemoteAction> actions)424     void setActions(List<RemoteAction> actions) {
425         mActions.clear();
426         if (actions != null) {
427             mActions.addAll(actions);
428         }
429         notifyActionsChanged(mActions);
430     }
431 
isSameDimensionAndRotation(@onNull DisplayInfo display1, @NonNull DisplayInfo display2)432     private boolean isSameDimensionAndRotation(@NonNull DisplayInfo display1,
433             @NonNull DisplayInfo display2) {
434         Preconditions.checkNotNull(display1);
435         Preconditions.checkNotNull(display2);
436         return ((display1.rotation == display2.rotation)
437                 && (display1.logicalWidth == display2.logicalWidth)
438                 && (display1.logicalHeight == display2.logicalHeight));
439     }
440 
441     /**
442      * Notifies listeners that the PIP needs to be adjusted for the IME.
443      */
notifyImeVisibilityChanged(boolean imeVisible, int imeHeight)444     private void notifyImeVisibilityChanged(boolean imeVisible, int imeHeight) {
445         if (mPinnedStackListener != null) {
446             try {
447                 mPinnedStackListener.onImeVisibilityChanged(imeVisible, imeHeight);
448             } catch (RemoteException e) {
449                 Slog.e(TAG_WM, "Error delivering bounds changed event.", e);
450             }
451         }
452     }
453 
notifyShelfVisibilityChanged(boolean shelfVisible, int shelfHeight)454     private void notifyShelfVisibilityChanged(boolean shelfVisible, int shelfHeight) {
455         if (mPinnedStackListener != null) {
456             try {
457                 mPinnedStackListener.onShelfVisibilityChanged(shelfVisible, shelfHeight);
458             } catch (RemoteException e) {
459                 Slog.e(TAG_WM, "Error delivering bounds changed event.", e);
460             }
461         }
462     }
463 
464     /**
465      * Notifies listeners that the PIP minimized state has changed.
466      */
notifyMinimizeChanged(boolean isMinimized)467     private void notifyMinimizeChanged(boolean isMinimized) {
468         if (mPinnedStackListener != null) {
469             try {
470                 mPinnedStackListener.onMinimizedStateChanged(isMinimized);
471             } catch (RemoteException e) {
472                 Slog.e(TAG_WM, "Error delivering minimize changed event.", e);
473             }
474         }
475     }
476 
477     /**
478      * Notifies listeners that the PIP actions have changed.
479      */
notifyActionsChanged(List<RemoteAction> actions)480     private void notifyActionsChanged(List<RemoteAction> actions) {
481         if (mPinnedStackListener != null) {
482             try {
483                 mPinnedStackListener.onActionsChanged(new ParceledListSlice(actions));
484             } catch (RemoteException e) {
485                 Slog.e(TAG_WM, "Error delivering actions changed event.", e);
486             }
487         }
488     }
489 
490     /**
491      * Notifies listeners that the PIP movement bounds have changed.
492      */
notifyMovementBoundsChanged(boolean fromImeAdjustment, boolean fromShelfAdjustment)493     private void notifyMovementBoundsChanged(boolean fromImeAdjustment,
494             boolean fromShelfAdjustment) {
495         synchronized (mService.mGlobalLock) {
496             if (mPinnedStackListener == null) {
497                 return;
498             }
499             try {
500                 final Rect insetBounds = new Rect();
501                 getInsetBounds(insetBounds);
502                 final Rect normalBounds = getDefaultBounds(INVALID_SNAP_FRACTION);
503                 if (isValidPictureInPictureAspectRatio(mAspectRatio)) {
504                     transformBoundsToAspectRatio(normalBounds, mAspectRatio,
505                             false /* useCurrentMinEdgeSize */);
506                 }
507                 final Rect animatingBounds = mTmpAnimatingBoundsRect;
508                 final TaskStack pinnedStack = mDisplayContent.getPinnedStack();
509                 if (pinnedStack != null) {
510                     pinnedStack.getAnimationOrCurrentBounds(animatingBounds);
511                 } else {
512                     animatingBounds.set(normalBounds);
513                 }
514                 mPinnedStackListener.onMovementBoundsChanged(insetBounds, normalBounds,
515                         animatingBounds, fromImeAdjustment, fromShelfAdjustment,
516                         mDisplayInfo.rotation);
517             } catch (RemoteException e) {
518                 Slog.e(TAG_WM, "Error delivering actions changed event.", e);
519             }
520         }
521     }
522 
523     /**
524      * @return the bounds on the screen that the PIP can be visible in.
525      */
getInsetBounds(Rect outRect)526     private void getInsetBounds(Rect outRect) {
527         synchronized (mService.mGlobalLock) {
528             mDisplayContent.getDisplayPolicy().getStableInsetsLw(mDisplayInfo.rotation,
529                     mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight,
530                     mDisplayInfo.displayCutout, mTmpInsets);
531             outRect.set(mTmpInsets.left + mScreenEdgeInsets.x, mTmpInsets.top + mScreenEdgeInsets.y,
532                     mDisplayInfo.logicalWidth - mTmpInsets.right - mScreenEdgeInsets.x,
533                     mDisplayInfo.logicalHeight - mTmpInsets.bottom - mScreenEdgeInsets.y);
534         }
535     }
536 
537     /**
538      * @return the movement bounds for the given {@param stackBounds} and the current state of the
539      *         controller.
540      */
getMovementBounds(Rect stackBounds)541     private Rect getMovementBounds(Rect stackBounds) {
542         synchronized (mService.mGlobalLock) {
543             return getMovementBounds(stackBounds, true /* adjustForIme */,
544                     true /* adjustForShelf */);
545         }
546     }
547 
548     /**
549      * @return the movement bounds for the given {@param stackBounds} and the current state of the
550      *         controller.
551      */
getMovementBounds(Rect stackBounds, boolean adjustForIme, boolean adjustForShelf)552     private Rect getMovementBounds(Rect stackBounds, boolean adjustForIme, boolean adjustForShelf) {
553         synchronized (mService.mGlobalLock) {
554             final Rect movementBounds = new Rect();
555             getInsetBounds(movementBounds);
556 
557             // Apply the movement bounds adjustments based on the current state
558             mSnapAlgorithm.getMovementBounds(stackBounds, movementBounds, movementBounds,
559                     Math.max((adjustForIme && mIsImeShowing) ? mImeHeight : 0,
560                             (adjustForShelf && mIsShelfShowing) ? mShelfHeight : 0));
561             return movementBounds;
562         }
563     }
564 
565     /**
566      * Applies the minimized offsets to the given stack bounds.
567      */
applyMinimizedOffset(Rect stackBounds, Rect movementBounds)568     private void applyMinimizedOffset(Rect stackBounds, Rect movementBounds) {
569         synchronized (mService.mGlobalLock) {
570             mTmpDisplaySize.set(mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight);
571             mService.getStableInsetsLocked(mDisplayContent.getDisplayId(), mStableInsets);
572             mSnapAlgorithm.applyMinimizedOffset(stackBounds, movementBounds, mTmpDisplaySize,
573                     mStableInsets);
574         }
575     }
576 
577     /**
578      * @return the default snap fraction to apply instead of the default gravity when calculating
579      *         the default stack bounds when first entering PiP.
580      */
getSnapFraction(Rect stackBounds)581     private float getSnapFraction(Rect stackBounds) {
582         return mSnapAlgorithm.getSnapFraction(stackBounds, getMovementBounds(stackBounds));
583     }
584 
585     /**
586      * @return the pixels for a given dp value.
587      */
dpToPx(float dpValue, DisplayMetrics dm)588     private int dpToPx(float dpValue, DisplayMetrics dm) {
589         return (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP, dpValue, dm);
590     }
591 
dump(String prefix, PrintWriter pw)592     void dump(String prefix, PrintWriter pw) {
593         pw.println(prefix + "PinnedStackController");
594         pw.print(prefix + "  defaultBounds=");
595         getDefaultBounds(INVALID_SNAP_FRACTION).printShortString(pw);
596         pw.println();
597         pw.println(prefix + "  mDefaultMinSize=" + mDefaultMinSize);
598         pw.println(prefix + "  mDefaultStackGravity=" + mDefaultStackGravity);
599         pw.println(prefix + "  mDefaultAspectRatio=" + mDefaultAspectRatio);
600         mService.getStackBounds(WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, mTmpRect);
601         pw.print(prefix + "  movementBounds="); getMovementBounds(mTmpRect).printShortString(pw);
602         pw.println();
603         pw.println(prefix + "  mIsImeShowing=" + mIsImeShowing);
604         pw.println(prefix + "  mImeHeight=" + mImeHeight);
605         pw.println(prefix + "  mIsShelfShowing=" + mIsShelfShowing);
606         pw.println(prefix + "  mShelfHeight=" + mShelfHeight);
607         pw.println(prefix + "  mReentrySnapFraction=" + mReentrySnapFraction);
608         pw.println(prefix + "  mIsMinimized=" + mIsMinimized);
609         pw.println(prefix + "  mAspectRatio=" + mAspectRatio);
610         pw.println(prefix + "  mMinAspectRatio=" + mMinAspectRatio);
611         pw.println(prefix + "  mMaxAspectRatio=" + mMaxAspectRatio);
612         if (mActions.isEmpty()) {
613             pw.println(prefix + "  mActions=[]");
614         } else {
615             pw.println(prefix + "  mActions=[");
616             for (int i = 0; i < mActions.size(); i++) {
617                 RemoteAction action = mActions.get(i);
618                 pw.print(prefix + "    Action[" + i + "]: ");
619                 action.dump("", pw);
620             }
621             pw.println(prefix + "  ]");
622         }
623         pw.println(prefix + "  mDisplayInfo=" + mDisplayInfo);
624     }
625 
writeToProto(ProtoOutputStream proto, long fieldId)626     void writeToProto(ProtoOutputStream proto, long fieldId) {
627         final long token = proto.start(fieldId);
628         getDefaultBounds(INVALID_SNAP_FRACTION).writeToProto(proto, DEFAULT_BOUNDS);
629         mService.getStackBounds(WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, mTmpRect);
630         getMovementBounds(mTmpRect).writeToProto(proto, MOVEMENT_BOUNDS);
631         proto.end(token);
632     }
633 }
634