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