• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 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.wm.shell.common.pip;
18 
19 import android.annotation.IntDef;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.app.ActivityTaskManager;
23 import android.app.PictureInPictureParams;
24 import android.app.PictureInPictureUiState;
25 import android.content.ComponentName;
26 import android.content.Context;
27 import android.content.pm.ActivityInfo;
28 import android.graphics.Point;
29 import android.graphics.Rect;
30 import android.os.RemoteException;
31 import android.util.ArraySet;
32 import android.util.Size;
33 import android.util.SparseArray;
34 
35 import com.android.internal.annotations.VisibleForTesting;
36 import com.android.internal.protolog.ProtoLog;
37 import com.android.internal.util.function.TriConsumer;
38 import com.android.wm.shell.R;
39 import com.android.wm.shell.common.DisplayLayout;
40 import com.android.wm.shell.protolog.ShellProtoLogGroup;
41 
42 import java.io.PrintWriter;
43 import java.lang.annotation.Retention;
44 import java.lang.annotation.RetentionPolicy;
45 import java.util.ArrayList;
46 import java.util.List;
47 import java.util.Objects;
48 import java.util.Set;
49 import java.util.function.Consumer;
50 
51 /**
52  * Singleton source of truth for the current state of PIP bounds.
53  */
54 public class PipBoundsState {
55     public static final int STASH_TYPE_NONE = 0;
56     public static final int STASH_TYPE_LEFT = 1;
57     public static final int STASH_TYPE_RIGHT = 2;
58     public static final int STASH_TYPE_BOTTOM = 3;
59     public static final int STASH_TYPE_TOP = 4;
60 
61     @IntDef(prefix = { "STASH_TYPE_" }, value =  {
62             STASH_TYPE_NONE,
63             STASH_TYPE_LEFT,
64             STASH_TYPE_RIGHT,
65             STASH_TYPE_BOTTOM,
66             STASH_TYPE_TOP
67     })
68     @Retention(RetentionPolicy.SOURCE)
69     public @interface StashType {}
70 
71     public static final int NAMED_KCA_LAUNCHER_SHELF = 0;
72     public static final int NAMED_KCA_TABLETOP_MODE = 1;
73 
74     @IntDef(prefix = { "NAMED_KCA_" }, value = {
75             NAMED_KCA_LAUNCHER_SHELF,
76             NAMED_KCA_TABLETOP_MODE
77     })
78     @Retention(RetentionPolicy.SOURCE)
79     public @interface NamedKca {}
80 
81     private static final String TAG = PipBoundsState.class.getSimpleName();
82 
83     @NonNull private final Rect mBounds = new Rect();
84     @NonNull private final Rect mMovementBounds = new Rect();
85     @NonNull private final Rect mNormalBounds = new Rect();
86     @NonNull private final Rect mExpandedBounds = new Rect();
87     @NonNull private final Rect mNormalMovementBounds = new Rect();
88     @NonNull private final Rect mExpandedMovementBounds = new Rect();
89     @NonNull private final Rect mRestoreBounds = new Rect();
90     @NonNull private final PipDisplayLayoutState mPipDisplayLayoutState;
91     private final Point mMaxSize = new Point();
92     private final Point mMinSize = new Point();
93     @NonNull private final Context mContext;
94     private float mAspectRatio;
95     private int mStashedState = STASH_TYPE_NONE;
96     private int mStashOffset;
97     @Nullable private PipReentryState mPipReentryState;
98     private final LauncherState mLauncherState = new LauncherState();
99     @NonNull private final SizeSpecSource mSizeSpecSource;
100     @Nullable private ComponentName mLastPipComponentName;
101     @NonNull private final MotionBoundsState mMotionBoundsState = new MotionBoundsState();
102     private boolean mIsImeShowing;
103     private int mImeHeight;
104     private boolean mIsShelfShowing;
105     private int mShelfHeight;
106     /** Whether the user has resized the PIP manually. */
107     private boolean mHasUserResizedPip;
108     /** Whether the user has moved the PIP manually. */
109     private boolean mHasUserMovedPip;
110     /**
111      * Areas defined by currently visible apps that they prefer to keep clear from overlays such as
112      * the PiP. Restricted areas may only move the PiP a limited amount from its anchor position.
113      * The system will try to respect these areas, but when not possible will ignore them.
114      *
115      * @see android.view.View#setPreferKeepClearRects
116      */
117     private final Set<Rect> mRestrictedKeepClearAreas = new ArraySet<>();
118     /**
119      * Areas defined by currently visible apps holding
120      * {@link android.Manifest.permission#SET_UNRESTRICTED_KEEP_CLEAR_AREAS} that they prefer to
121      * keep clear from overlays such as the PiP.
122      * Unrestricted areas can move the PiP farther than restricted areas, and the system will try
123      * harder to respect these areas.
124      *
125      * @see android.view.View#setPreferKeepClearRects
126      */
127     private final Set<Rect> mUnrestrictedKeepClearAreas = new ArraySet<>();
128     /**
129      * Additional to {@link #mUnrestrictedKeepClearAreas}, allow the caller to append named bounds
130      * as unrestricted keep clear area. Values in this map would be appended to
131      * {@link #getUnrestrictedKeepClearAreas()} and this is meant for internal usage only.
132      */
133     private final SparseArray<Rect> mNamedUnrestrictedKeepClearAreas = new SparseArray<>();
134 
135     @Nullable private Runnable mOnMinimalSizeChangeCallback;
136     @Nullable private TriConsumer<Boolean, Integer, Boolean> mOnShelfVisibilityChangeCallback;
137     private final List<Consumer<Rect>> mOnPipExclusionBoundsChangeCallbacks = new ArrayList<>();
138     private final List<Consumer<Float>> mOnAspectRatioChangedCallbacks = new ArrayList<>();
139 
140     /**
141      * This is used to set the launcher shelf height ahead of non-auto-enter-pip animation,
142      * to avoid the race condition. See also {@link #NAMED_KCA_LAUNCHER_SHELF}.
143      */
144     public final Rect mCachedLauncherShelfHeightKeepClearArea = new Rect();
145 
146     private final List<OnPipComponentChangedListener> mOnPipComponentChangedListeners =
147             new ArrayList<>();
148 
149     // the size of the current bounds relative to the max size spec
150     private float mBoundsScale;
151 
PipBoundsState(@onNull Context context, @NonNull SizeSpecSource sizeSpecSource, @NonNull PipDisplayLayoutState pipDisplayLayoutState)152     public PipBoundsState(@NonNull Context context, @NonNull SizeSpecSource sizeSpecSource,
153             @NonNull PipDisplayLayoutState pipDisplayLayoutState) {
154         mContext = context;
155         reloadResources();
156         mSizeSpecSource = sizeSpecSource;
157         mPipDisplayLayoutState = pipDisplayLayoutState;
158 
159         // Update the relative proportion of the bounds compared to max possible size. Max size
160         // spec takes the aspect ratio of the bounds into account, so both width and height
161         // scale by the same factor.
162         addPipExclusionBoundsChangeCallback((bounds) -> updateBoundsScale());
163     }
164 
165     /** Reloads the resources. */
onConfigurationChanged()166     public void onConfigurationChanged() {
167         reloadResources();
168 
169         // update the size spec resources upon config change too
170         mSizeSpecSource.onConfigurationChanged();
171     }
172 
173     /** Update the bounds scale percentage value. */
updateBoundsScale()174     public void updateBoundsScale() {
175         mBoundsScale = Math.min((float) mBounds.width() / mMaxSize.x, 1.0f);
176     }
177 
reloadResources()178     private void reloadResources() {
179         mStashOffset = mContext.getResources().getDimensionPixelSize(R.dimen.pip_stash_offset);
180     }
181 
182     /** Set the current PIP bounds. */
setBounds(@onNull Rect bounds)183     public void setBounds(@NonNull Rect bounds) {
184         mBounds.set(bounds);
185         for (Consumer<Rect> callback : mOnPipExclusionBoundsChangeCallbacks) {
186             callback.accept(bounds);
187         }
188     }
189 
190     /** Get the current PIP bounds. */
191     @NonNull
getBounds()192     public Rect getBounds() {
193         return new Rect(mBounds);
194     }
195 
196     /**
197      * Get the scale of the current bounds relative to the maximum size possible.
198      *
199      * @return 1.0 if {@link PipBoundsState#getBounds()} equals {@link PipBoundsState#getMaxSize()}.
200      */
getBoundsScale()201     public float getBoundsScale() {
202         return mBoundsScale;
203     }
204 
205     /** Returns the current movement bounds. */
206     @NonNull
getMovementBounds()207     public Rect getMovementBounds() {
208         return mMovementBounds;
209     }
210 
211     /** Set the current normal PIP bounds. */
setNormalBounds(@onNull Rect bounds)212     public void setNormalBounds(@NonNull Rect bounds) {
213         mNormalBounds.set(bounds);
214     }
215 
216     /** Get the current normal PIP bounds. */
217     @NonNull
getNormalBounds()218     public Rect getNormalBounds() {
219         return mNormalBounds;
220     }
221 
222     /** Set the expanded bounds of PIP. */
setExpandedBounds(@onNull Rect bounds)223     public void setExpandedBounds(@NonNull Rect bounds) {
224         mExpandedBounds.set(bounds);
225     }
226 
227     /** Get the PIP expanded bounds. */
228     @NonNull
getExpandedBounds()229     public Rect getExpandedBounds() {
230         return mExpandedBounds;
231     }
232 
233     /** Set the normal movement bounds. */
setNormalMovementBounds(@onNull Rect bounds)234     public void setNormalMovementBounds(@NonNull Rect bounds) {
235         mNormalMovementBounds.set(bounds);
236     }
237 
238     /** Returns the normal movement bounds. */
239     @NonNull
getNormalMovementBounds()240     public Rect getNormalMovementBounds() {
241         return mNormalMovementBounds;
242     }
243 
244     /** Set the expanded movement bounds. */
setExpandedMovementBounds(@onNull Rect bounds)245     public void setExpandedMovementBounds(@NonNull Rect bounds) {
246         mExpandedMovementBounds.set(bounds);
247     }
248 
249     /** Updates the min and max sizes based on the size spec and aspect ratio. */
updateMinMaxSize(float aspectRatio)250     public void updateMinMaxSize(float aspectRatio) {
251         final Size minSize = mSizeSpecSource.getMinSize(aspectRatio);
252         mMinSize.set(minSize.getWidth(), minSize.getHeight());
253         final Size maxSize = mSizeSpecSource.getMaxSize(aspectRatio);
254         mMaxSize.set(maxSize.getWidth(), maxSize.getHeight());
255     }
256 
257     /** Sets the max possible size for resize. */
setMaxSize(int width, int height)258     public void setMaxSize(int width, int height) {
259         mMaxSize.set(width, height);
260     }
261 
262     /** Sets the min possible size for resize. */
setMinSize(int width, int height)263     public void setMinSize(int width, int height) {
264         mMinSize.set(width, height);
265     }
266 
getMaxSize()267     public Point getMaxSize() {
268         return mMaxSize;
269     }
270 
getMinSize()271     public Point getMinSize() {
272         return mMinSize;
273     }
274 
275     /** Returns the expanded movement bounds. */
276     @NonNull
getExpandedMovementBounds()277     public Rect getExpandedMovementBounds() {
278         return mExpandedMovementBounds;
279     }
280 
281     /** Dictate where PiP currently should be stashed, if at all. */
setStashed(@tashType int stashedState)282     public void setStashed(@StashType int stashedState) {
283         if (mStashedState == stashedState) {
284             return;
285         }
286 
287         mStashedState = stashedState;
288         try {
289             ActivityTaskManager.getService().onPictureInPictureUiStateChanged(
290                     new PictureInPictureUiState(stashedState != STASH_TYPE_NONE /* isStashed */)
291             );
292         } catch (RemoteException | IllegalStateException e) {
293             ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
294                     "%s: Unable to set alert PiP state change.", TAG);
295         }
296     }
297 
298     /**
299      * Return where the PiP is stashed, if at all.
300      * @return {@code STASH_NONE}, {@code STASH_LEFT} or {@code STASH_RIGHT}.
301      */
getStashedState()302     public @StashType int getStashedState() {
303         return mStashedState;
304     }
305 
306     /** Whether PiP is stashed or not. */
isStashed()307     public boolean isStashed() {
308         return mStashedState != STASH_TYPE_NONE;
309     }
310 
311     /** Returns the offset from the edge of the screen for PiP stash. */
getStashOffset()312     public int getStashOffset() {
313         return mStashOffset;
314     }
315 
316     /** Set the PIP aspect ratio. */
setAspectRatio(float aspectRatio)317     public void setAspectRatio(float aspectRatio) {
318         if (Float.compare(mAspectRatio, aspectRatio) != 0) {
319             mAspectRatio = aspectRatio;
320             for (Consumer<Float> callback : mOnAspectRatioChangedCallbacks) {
321                 callback.accept(mAspectRatio);
322             }
323         }
324     }
325 
326     /** Get the PIP aspect ratio. */
getAspectRatio()327     public float getAspectRatio() {
328         return mAspectRatio;
329     }
330 
331     /** Save the reentry state to restore to when re-entering PIP mode. */
saveReentryState(float fraction)332     public void saveReentryState(float fraction) {
333         mPipReentryState = new PipReentryState(mBoundsScale, fraction);
334     }
335 
336     /** Returns the saved reentry state. */
337     @Nullable
getReentryState()338     public PipReentryState getReentryState() {
339         return mPipReentryState;
340     }
341 
342     /** Set the last {@link ComponentName} to enter PIP mode. */
setLastPipComponentName(@ullable ComponentName lastPipComponentName)343     public void setLastPipComponentName(@Nullable ComponentName lastPipComponentName) {
344         final boolean changed = !Objects.equals(mLastPipComponentName, lastPipComponentName);
345         if (!changed) return;
346         clearReentryState();
347         setHasUserResizedPip(false);
348         setHasUserMovedPip(false);
349         final ComponentName oldComponentName = mLastPipComponentName;
350         mLastPipComponentName = lastPipComponentName;
351         for (OnPipComponentChangedListener listener : mOnPipComponentChangedListeners) {
352             listener.onPipComponentChanged(oldComponentName, mLastPipComponentName);
353         }
354     }
355 
356     /** Get the last PIP component name, if any. */
357     @Nullable
getLastPipComponentName()358     public ComponentName getLastPipComponentName() {
359         return mLastPipComponentName;
360     }
361 
362     /** Returns the display's bounds. */
363     @NonNull
getDisplayBounds()364     public Rect getDisplayBounds() {
365         return mPipDisplayLayoutState.getDisplayBounds();
366     }
367 
368     /** Get a copy of the display layout. */
369     @NonNull
getDisplayLayout()370     public DisplayLayout getDisplayLayout() {
371         return mPipDisplayLayoutState.getDisplayLayout();
372     }
373 
374     /**
375      * Clears the PiP re-entry state.
376      */
377     @VisibleForTesting
clearReentryState()378     public void clearReentryState() {
379         mPipReentryState = null;
380     }
381 
382     /** Sets the preferred size of PIP as specified by the activity in PIP mode. */
setOverrideMinSize(@ullable Size overrideMinSize)383     public void setOverrideMinSize(@Nullable Size overrideMinSize) {
384         if (overrideMinSize != null) {
385             final Size defaultSize = mSizeSpecSource.getDefaultSize(getAspectRatio());
386             if (overrideMinSize.getWidth() > defaultSize.getWidth()
387                     || overrideMinSize.getHeight() > defaultSize.getHeight()) {
388                 ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
389                         "Ignore override min size(%s): larger than default size (%s)",
390                         overrideMinSize, defaultSize);
391                 return;
392             }
393         }
394         final boolean changed = !Objects.equals(overrideMinSize, getOverrideMinSize());
395         mSizeSpecSource.setOverrideMinSize(overrideMinSize);
396         if (changed && mOnMinimalSizeChangeCallback != null) {
397             mOnMinimalSizeChangeCallback.run();
398         }
399     }
400 
401     /** Returns the preferred minimal size specified by the activity in PIP. */
402     @Nullable
getOverrideMinSize()403     public Size getOverrideMinSize() {
404         return mSizeSpecSource.getOverrideMinSize();
405     }
406 
407     /** Returns the minimum edge size of the override minimum size, or 0 if not set. */
getOverrideMinEdgeSize()408     public int getOverrideMinEdgeSize() {
409         return mSizeSpecSource.getOverrideMinEdgeSize();
410     }
411 
412     /** Get the state of the bounds in motion. */
413     @NonNull
getMotionBoundsState()414     public MotionBoundsState getMotionBoundsState() {
415         return mMotionBoundsState;
416     }
417 
418     /** Set whether the IME is currently showing and its height. */
setImeVisibility(boolean imeShowing, int imeHeight)419     public void setImeVisibility(boolean imeShowing, int imeHeight) {
420         mIsImeShowing = imeShowing;
421         mImeHeight = imeHeight;
422         // If IME is showing, save the current PiP bounds in case we need to restore it later.
423         if (mIsImeShowing) {
424             mRestoreBounds.set(getBounds());
425         }
426     }
427 
428     /** Returns whether the IME is currently showing. */
isImeShowing()429     public boolean isImeShowing() {
430         return mIsImeShowing;
431     }
432 
433     /** Returns the bounds to restore PiP to (bounds before IME was expanded). */
getRestoreBounds()434     public Rect getRestoreBounds() {
435         return mRestoreBounds;
436     }
437 
438     /** Sets mRestoreBounds to (0,0,0,0). */
clearRestoreBounds()439     public void clearRestoreBounds() {
440         mRestoreBounds.setEmpty();
441     }
442 
443     /** Returns the IME height. */
getImeHeight()444     public int getImeHeight() {
445         return mImeHeight;
446     }
447 
448     /** Set whether the shelf is showing and its height. */
setShelfVisibility(boolean showing, int height)449     public void setShelfVisibility(boolean showing, int height) {
450         setShelfVisibility(showing, height, true);
451     }
452 
453     /** Set whether the shelf is showing and its height. */
setShelfVisibility(boolean showing, int height, boolean updateMovementBounds)454     public void setShelfVisibility(boolean showing, int height, boolean updateMovementBounds) {
455         final boolean shelfShowing = showing && height > 0;
456         if (shelfShowing == mIsShelfShowing && height == mShelfHeight) {
457             return;
458         }
459 
460         mIsShelfShowing = showing;
461         mShelfHeight = height;
462         if (mOnShelfVisibilityChangeCallback != null) {
463             mOnShelfVisibilityChangeCallback.accept(mIsShelfShowing, mShelfHeight,
464                     updateMovementBounds);
465         }
466     }
467 
468     /** Set the keep clear areas onscreen. The PiP should ideally not cover them. */
setKeepClearAreas(@onNull Set<Rect> restrictedAreas, @NonNull Set<Rect> unrestrictedAreas)469     public void setKeepClearAreas(@NonNull Set<Rect> restrictedAreas,
470             @NonNull Set<Rect> unrestrictedAreas) {
471         mRestrictedKeepClearAreas.clear();
472         mRestrictedKeepClearAreas.addAll(restrictedAreas);
473         mUnrestrictedKeepClearAreas.clear();
474         mUnrestrictedKeepClearAreas.addAll(unrestrictedAreas);
475     }
476 
477     /** Set a named unrestricted keep clear area. */
setNamedUnrestrictedKeepClearArea( @amedKca int tag, @Nullable Rect unrestrictedArea)478     public void setNamedUnrestrictedKeepClearArea(
479             @NamedKca int tag, @Nullable Rect unrestrictedArea) {
480         if (unrestrictedArea == null) {
481             mNamedUnrestrictedKeepClearAreas.remove(tag);
482         } else {
483             mNamedUnrestrictedKeepClearAreas.put(tag, unrestrictedArea);
484             if (tag == NAMED_KCA_LAUNCHER_SHELF) {
485                 mCachedLauncherShelfHeightKeepClearArea.set(unrestrictedArea);
486             }
487         }
488     }
489 
490     /**
491      * Forcefully set the keep-clear-area for launcher shelf height if applicable.
492      * This is used for entering PiP in button navigation mode to make sure the destination bounds
493      * calculation includes the shelf height, to avoid race conditions that such callback is sent
494      * from Launcher after the entering animation is started.
495      */
mayUseCachedLauncherShelfHeight()496     public void mayUseCachedLauncherShelfHeight() {
497         if (!mCachedLauncherShelfHeightKeepClearArea.isEmpty()) {
498             setNamedUnrestrictedKeepClearArea(
499                     NAMED_KCA_LAUNCHER_SHELF, mCachedLauncherShelfHeightKeepClearArea);
500         }
501     }
502 
503     /**
504      * @return restricted keep clear areas.
505      */
506     @NonNull
getRestrictedKeepClearAreas()507     public Set<Rect> getRestrictedKeepClearAreas() {
508         return mRestrictedKeepClearAreas;
509     }
510 
511     /**
512      * @return unrestricted keep clear areas.
513      */
514     @NonNull
getUnrestrictedKeepClearAreas()515     public Set<Rect> getUnrestrictedKeepClearAreas() {
516         if (mNamedUnrestrictedKeepClearAreas.size() == 0) return mUnrestrictedKeepClearAreas;
517         final Set<Rect> unrestrictedAreas = new ArraySet<>(mUnrestrictedKeepClearAreas);
518         for (int i = 0; i < mNamedUnrestrictedKeepClearAreas.size(); i++) {
519             final int key = mNamedUnrestrictedKeepClearAreas.keyAt(i);
520             unrestrictedAreas.add(mNamedUnrestrictedKeepClearAreas.get(key));
521         }
522         return unrestrictedAreas;
523     }
524 
525     /**
526      * Initialize states when first entering PiP.
527      */
setBoundsStateForEntry(ComponentName componentName, ActivityInfo activityInfo, PictureInPictureParams params, PipBoundsAlgorithm pipBoundsAlgorithm)528     public void setBoundsStateForEntry(ComponentName componentName, ActivityInfo activityInfo,
529             PictureInPictureParams params, PipBoundsAlgorithm pipBoundsAlgorithm) {
530         setLastPipComponentName(componentName);
531         setAspectRatio(pipBoundsAlgorithm.getAspectRatioOrDefault(params));
532         setOverrideMinSize(pipBoundsAlgorithm.getMinimalSize(activityInfo));
533     }
534 
535     /** Returns whether the shelf is currently showing. */
isShelfShowing()536     public boolean isShelfShowing() {
537         return mIsShelfShowing;
538     }
539 
540     /** Returns the shelf height. */
getShelfHeight()541     public int getShelfHeight() {
542         return mShelfHeight;
543     }
544 
545     /** Returns whether the user has resized the PIP. */
hasUserResizedPip()546     public boolean hasUserResizedPip() {
547         return mHasUserResizedPip;
548     }
549 
550     /** Set whether the user has resized the PIP. */
setHasUserResizedPip(boolean hasUserResizedPip)551     public void setHasUserResizedPip(boolean hasUserResizedPip) {
552         mHasUserResizedPip = hasUserResizedPip;
553         // If user resized PiP while IME is showing, clear the pre-IME restore bounds.
554         if (hasUserResizedPip && isImeShowing()) {
555             clearRestoreBounds();
556         }
557     }
558 
559     /** Returns whether the user has moved the PIP. */
hasUserMovedPip()560     public boolean hasUserMovedPip() {
561         return mHasUserMovedPip;
562     }
563 
564     /** Set whether the user has moved the PIP. */
setHasUserMovedPip(boolean hasUserMovedPip)565     public void setHasUserMovedPip(boolean hasUserMovedPip) {
566         mHasUserMovedPip = hasUserMovedPip;
567         // If user moved PiP while IME is showing, clear the pre-IME restore bounds.
568         if (hasUserMovedPip && isImeShowing()) {
569             clearRestoreBounds();
570         }
571     }
572 
573     /**
574      * Registers a callback when the minimal size of PIP that is set by the app changes.
575      */
setOnMinimalSizeChangeCallback(@ullable Runnable onMinimalSizeChangeCallback)576     public void setOnMinimalSizeChangeCallback(@Nullable Runnable onMinimalSizeChangeCallback) {
577         mOnMinimalSizeChangeCallback = onMinimalSizeChangeCallback;
578     }
579 
580     /** Set a callback to be notified when the shelf visibility changes. */
setOnShelfVisibilityChangeCallback( @ullable TriConsumer<Boolean, Integer, Boolean> onShelfVisibilityChangeCallback)581     public void setOnShelfVisibilityChangeCallback(
582             @Nullable TriConsumer<Boolean, Integer, Boolean> onShelfVisibilityChangeCallback) {
583         mOnShelfVisibilityChangeCallback = onShelfVisibilityChangeCallback;
584     }
585 
586     /**
587      * Add a callback to watch out for PiP bounds. This is mostly used by SystemUI's
588      * Back-gesture handler, to avoid conflicting with PiP when it's stashed.
589      */
addPipExclusionBoundsChangeCallback( @ullable Consumer<Rect> onPipExclusionBoundsChangeCallback)590     public void addPipExclusionBoundsChangeCallback(
591             @Nullable Consumer<Rect> onPipExclusionBoundsChangeCallback) {
592         mOnPipExclusionBoundsChangeCallbacks.add(onPipExclusionBoundsChangeCallback);
593         for (Consumer<Rect> callback : mOnPipExclusionBoundsChangeCallbacks) {
594             callback.accept(getBounds());
595         }
596     }
597 
598     /**
599      * Remove a callback that was previously added.
600      */
removePipExclusionBoundsChangeCallback( @ullable Consumer<Rect> onPipExclusionBoundsChangeCallback)601     public void removePipExclusionBoundsChangeCallback(
602             @Nullable Consumer<Rect> onPipExclusionBoundsChangeCallback) {
603         mOnPipExclusionBoundsChangeCallbacks.remove(onPipExclusionBoundsChangeCallback);
604     }
605 
606     /** Adds callback to listen on aspect ratio change. */
addOnAspectRatioChangedCallback( @onNull Consumer<Float> onAspectRatioChangedCallback)607     public void addOnAspectRatioChangedCallback(
608             @NonNull Consumer<Float> onAspectRatioChangedCallback) {
609         if (!mOnAspectRatioChangedCallbacks.contains(onAspectRatioChangedCallback)) {
610             mOnAspectRatioChangedCallbacks.add(onAspectRatioChangedCallback);
611             onAspectRatioChangedCallback.accept(mAspectRatio);
612         }
613     }
614 
615     /** Removes callback to listen on aspect ratio change. */
removeOnAspectRatioChangedCallback( @onNull Consumer<Float> onAspectRatioChangedCallback)616     public void removeOnAspectRatioChangedCallback(
617             @NonNull Consumer<Float> onAspectRatioChangedCallback) {
618         if (mOnAspectRatioChangedCallbacks.contains(onAspectRatioChangedCallback)) {
619             mOnAspectRatioChangedCallbacks.remove(onAspectRatioChangedCallback);
620         }
621     }
622 
623     /** Adds callback to listen on component change. */
addOnPipComponentChangedListener(@onNull OnPipComponentChangedListener listener)624     public void addOnPipComponentChangedListener(@NonNull OnPipComponentChangedListener listener) {
625         if (!mOnPipComponentChangedListeners.contains(listener)) {
626             mOnPipComponentChangedListeners.add(listener);
627         }
628     }
629 
630     /** Removes callback to listen on component change. */
removeOnPipComponentChangedListener( @onNull OnPipComponentChangedListener listener)631     public void removeOnPipComponentChangedListener(
632             @NonNull OnPipComponentChangedListener listener) {
633         if (mOnPipComponentChangedListeners.contains(listener)) {
634             mOnPipComponentChangedListeners.remove(listener);
635         }
636     }
637 
getLauncherState()638     public LauncherState getLauncherState() {
639         return mLauncherState;
640     }
641 
642     /** Source of truth for the current bounds of PIP that may be in motion. */
643     public static class MotionBoundsState {
644         /** The bounds used when PIP is in motion (e.g. during a drag or animation) */
645         private final @NonNull Rect mBoundsInMotion = new Rect();
646         /** The destination bounds to which PIP is animating. */
647         private final @NonNull Rect mAnimatingToBounds = new Rect();
648 
649         /** Whether PIP is being dragged or animated (e.g. resizing, in fling, etc). */
isInMotion()650         public boolean isInMotion() {
651             return !mBoundsInMotion.isEmpty();
652         }
653 
654         /** Set the temporary bounds used to represent the drag or animation bounds of PIP. */
setBoundsInMotion(@onNull Rect bounds)655         public void setBoundsInMotion(@NonNull Rect bounds) {
656             mBoundsInMotion.set(bounds);
657         }
658 
659         /** Set the bounds to which PIP is animating. */
setAnimatingToBounds(@onNull Rect bounds)660         public void setAnimatingToBounds(@NonNull Rect bounds) {
661             mAnimatingToBounds.set(bounds);
662         }
663 
664         /** Called when all ongoing motion operations have ended. */
onAllAnimationsEnded()665         public void onAllAnimationsEnded() {
666             mBoundsInMotion.setEmpty();
667         }
668 
669         /** Called when an ongoing physics animation has ended. */
onPhysicsAnimationEnded()670         public void onPhysicsAnimationEnded() {
671             mAnimatingToBounds.setEmpty();
672         }
673 
674         /** Returns the motion bounds. */
675         @NonNull
getBoundsInMotion()676         public Rect getBoundsInMotion() {
677             return mBoundsInMotion;
678         }
679 
680         /** Returns the destination bounds to which PIP is currently animating. */
681         @NonNull
getAnimatingToBounds()682         public Rect getAnimatingToBounds() {
683             return mAnimatingToBounds;
684         }
685 
dump(PrintWriter pw, String prefix)686         void dump(PrintWriter pw, String prefix) {
687             final String innerPrefix = prefix + "  ";
688             pw.println(prefix + MotionBoundsState.class.getSimpleName());
689             pw.println(innerPrefix + "mBoundsInMotion=" + mBoundsInMotion);
690             pw.println(innerPrefix + "mAnimatingToBounds=" + mAnimatingToBounds);
691         }
692     }
693 
694     /** Data class for Launcher state. */
695     public static final class LauncherState {
696         private int mAppIconSizePx;
697 
setAppIconSizePx(int appIconSizePx)698         public void setAppIconSizePx(int appIconSizePx) {
699             mAppIconSizePx = appIconSizePx;
700         }
701 
getAppIconSizePx()702         public int getAppIconSizePx() {
703             return mAppIconSizePx;
704         }
705 
dump(PrintWriter pw, String prefix)706         void dump(PrintWriter pw, String prefix) {
707             final String innerPrefix = prefix + "    ";
708             pw.println(prefix + LauncherState.class.getSimpleName());
709             pw.println(innerPrefix + "getAppIconSizePx=" + getAppIconSizePx());
710         }
711     }
712 
713     /**
714      * Represents the state of pip to potentially restore upon reentry.
715      */
716     @VisibleForTesting
717     static final class PipReentryState {
718         private static final String TAG = PipReentryState.class.getSimpleName();
719 
720         private final float mSnapFraction;
721         private final float mBoundsScale;
722 
PipReentryState(float boundsScale, float snapFraction)723         PipReentryState(float boundsScale, float snapFraction) {
724             mBoundsScale = boundsScale;
725             mSnapFraction = snapFraction;
726         }
727 
getBoundsScale()728         public float getBoundsScale() {
729             return mBoundsScale;
730         }
731 
getSnapFraction()732         public float getSnapFraction() {
733             return mSnapFraction;
734         }
735 
dump(PrintWriter pw, String prefix)736         void dump(PrintWriter pw, String prefix) {
737             final String innerPrefix = prefix + "  ";
738             pw.println(prefix + TAG);
739             pw.println(innerPrefix + "mBoundsScale=" + mBoundsScale);
740             pw.println(innerPrefix + "mSnapFraction=" + mSnapFraction);
741         }
742     }
743 
744     /**
745      * Listener interface for PiP component change, i.e. the app in pip mode changes
746      * TODO: Move this out of PipBoundsState once pip1 is deprecated.
747      */
748     public interface OnPipComponentChangedListener {
749         /**
750          * Callback when the component in pip mode changes.
751          * @param oldPipComponent previous component in pip mode,
752          *                        {@code null} if this is the very first time PiP appears.
753          * @param newPipComponent new component that enters pip mode.
754          */
onPipComponentChanged( @ullable ComponentName oldPipComponent, @NonNull ComponentName newPipComponent)755         void onPipComponentChanged(
756                 @Nullable ComponentName oldPipComponent,
757                 @NonNull ComponentName newPipComponent);
758     }
759 
760     /** Dumps internal state. */
dump(PrintWriter pw, String prefix)761     public void dump(PrintWriter pw, String prefix) {
762         final String innerPrefix = prefix + "  ";
763         pw.println(prefix + TAG);
764         pw.println(innerPrefix + "mBounds=" + mBounds);
765         pw.println(innerPrefix + "mNormalBounds=" + mNormalBounds);
766         pw.println(innerPrefix + "mExpandedBounds=" + mExpandedBounds);
767         pw.println(innerPrefix + "mMovementBounds=" + mMovementBounds);
768         pw.println(innerPrefix + "mNormalMovementBounds=" + mNormalMovementBounds);
769         pw.println(innerPrefix + "mExpandedMovementBounds=" + mExpandedMovementBounds);
770         pw.println(innerPrefix + "mLastPipComponentName=" + mLastPipComponentName);
771         pw.println(innerPrefix + "mAspectRatio=" + mAspectRatio);
772         pw.println(innerPrefix + "mStashedState=" + mStashedState);
773         pw.println(innerPrefix + "mStashOffset=" + mStashOffset);
774         pw.println(innerPrefix + "mIsImeShowing=" + mIsImeShowing);
775         pw.println(innerPrefix + "mImeHeight=" + mImeHeight);
776         pw.println(innerPrefix + "mIsShelfShowing=" + mIsShelfShowing);
777         pw.println(innerPrefix + "mShelfHeight=" + mShelfHeight);
778         pw.println(innerPrefix + "mHasUserMovedPip=" + mHasUserMovedPip);
779         pw.println(innerPrefix + "mHasUserResizedPip=" + mHasUserResizedPip);
780         pw.println(innerPrefix + "mMinSize=" + mMinSize);
781         pw.println(innerPrefix + "mMaxSize=" + mMaxSize);
782         pw.println(innerPrefix + "mBoundsScale" + mBoundsScale);
783         if (mPipReentryState == null) {
784             pw.println(innerPrefix + "mPipReentryState=null");
785         } else {
786             mPipReentryState.dump(pw, innerPrefix);
787         }
788         mLauncherState.dump(pw, innerPrefix);
789         mMotionBoundsState.dump(pw, innerPrefix);
790         mSizeSpecSource.dump(pw, innerPrefix);
791     }
792 }
793