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