• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 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.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.PictureInPictureUiState;
24 import android.content.ComponentName;
25 import android.content.Context;
26 import android.graphics.Point;
27 import android.graphics.Rect;
28 import android.os.RemoteException;
29 import android.util.Log;
30 import android.util.Size;
31 import android.view.Display;
32 
33 import com.android.internal.annotations.VisibleForTesting;
34 import com.android.internal.util.function.TriConsumer;
35 import com.android.wm.shell.R;
36 import com.android.wm.shell.common.DisplayLayout;
37 
38 import java.io.PrintWriter;
39 import java.lang.annotation.Retention;
40 import java.lang.annotation.RetentionPolicy;
41 import java.util.Objects;
42 import java.util.function.Consumer;
43 
44 /**
45  * Singleton source of truth for the current state of PIP bounds.
46  */
47 public final class PipBoundsState {
48     public static final int STASH_TYPE_NONE = 0;
49     public static final int STASH_TYPE_LEFT = 1;
50     public static final int STASH_TYPE_RIGHT = 2;
51 
52     @IntDef(prefix = { "STASH_TYPE_" }, value =  {
53             STASH_TYPE_NONE,
54             STASH_TYPE_LEFT,
55             STASH_TYPE_RIGHT
56     })
57     @Retention(RetentionPolicy.SOURCE)
58     public @interface StashType {}
59 
60     private static final String TAG = PipBoundsState.class.getSimpleName();
61 
62     private final @NonNull Rect mBounds = new Rect();
63     private final @NonNull Rect mMovementBounds = new Rect();
64     private final @NonNull Rect mNormalBounds = new Rect();
65     private final @NonNull Rect mExpandedBounds = new Rect();
66     private final @NonNull Rect mNormalMovementBounds = new Rect();
67     private final @NonNull Rect mExpandedMovementBounds = new Rect();
68     private final Point mMaxSize = new Point();
69     private final Point mMinSize = new Point();
70     private final @NonNull Context mContext;
71     private float mAspectRatio;
72     private int mStashedState = STASH_TYPE_NONE;
73     private int mStashOffset;
74     private @Nullable PipReentryState mPipReentryState;
75     private @Nullable ComponentName mLastPipComponentName;
76     private int mDisplayId = Display.DEFAULT_DISPLAY;
77     private final @NonNull DisplayLayout mDisplayLayout = new DisplayLayout();
78     /** The current minimum edge size of PIP. */
79     private int mMinEdgeSize;
80     /** The preferred minimum (and default) size specified by apps. */
81     private @Nullable Size mOverrideMinSize;
82     private final @NonNull MotionBoundsState mMotionBoundsState = new MotionBoundsState();
83     private boolean mIsImeShowing;
84     private int mImeHeight;
85     private boolean mIsShelfShowing;
86     private int mShelfHeight;
87     /** Whether the user has resized the PIP manually. */
88     private boolean mHasUserResizedPip;
89 
90     private @Nullable Runnable mOnMinimalSizeChangeCallback;
91     private @Nullable TriConsumer<Boolean, Integer, Boolean> mOnShelfVisibilityChangeCallback;
92     private @Nullable Consumer<Rect> mOnPipExclusionBoundsChangeCallback;
93 
PipBoundsState(@onNull Context context)94     public PipBoundsState(@NonNull Context context) {
95         mContext = context;
96         reloadResources();
97     }
98 
99     /** Reloads the resources. */
onConfigurationChanged()100     public void onConfigurationChanged() {
101         reloadResources();
102     }
103 
reloadResources()104     private void reloadResources() {
105         mStashOffset = mContext.getResources().getDimensionPixelSize(R.dimen.pip_stash_offset);
106     }
107 
108     /** Set the current PIP bounds. */
setBounds(@onNull Rect bounds)109     public void setBounds(@NonNull Rect bounds) {
110         mBounds.set(bounds);
111         if (mOnPipExclusionBoundsChangeCallback != null) {
112             mOnPipExclusionBoundsChangeCallback.accept(bounds);
113         }
114     }
115 
116     /** Get the current PIP bounds. */
117     @NonNull
getBounds()118     public Rect getBounds() {
119         return new Rect(mBounds);
120     }
121 
122     /** Returns the current movement bounds. */
123     @NonNull
getMovementBounds()124     public Rect getMovementBounds() {
125         return mMovementBounds;
126     }
127 
128     /** Set the current normal PIP bounds. */
setNormalBounds(@onNull Rect bounds)129     public void setNormalBounds(@NonNull Rect bounds) {
130         mNormalBounds.set(bounds);
131     }
132 
133     /** Get the current normal PIP bounds. */
134     @NonNull
getNormalBounds()135     public Rect getNormalBounds() {
136         return mNormalBounds;
137     }
138 
139     /** Set the expanded bounds of PIP. */
setExpandedBounds(@onNull Rect bounds)140     public void setExpandedBounds(@NonNull Rect bounds) {
141         mExpandedBounds.set(bounds);
142     }
143 
144     /** Get the PIP expanded bounds. */
145     @NonNull
getExpandedBounds()146     public Rect getExpandedBounds() {
147         return mExpandedBounds;
148     }
149 
150     /** Set the normal movement bounds. */
setNormalMovementBounds(@onNull Rect bounds)151     public void setNormalMovementBounds(@NonNull Rect bounds) {
152         mNormalMovementBounds.set(bounds);
153     }
154 
155     /** Returns the normal movement bounds. */
156     @NonNull
getNormalMovementBounds()157     public Rect getNormalMovementBounds() {
158         return mNormalMovementBounds;
159     }
160 
161     /** Set the expanded movement bounds. */
setExpandedMovementBounds(@onNull Rect bounds)162     public void setExpandedMovementBounds(@NonNull Rect bounds) {
163         mExpandedMovementBounds.set(bounds);
164     }
165 
166     /** Sets the max possible size for resize. */
setMaxSize(int width, int height)167     public void setMaxSize(int width, int height) {
168         mMaxSize.set(width, height);
169     }
170 
171     /** Sets the min possible size for resize. */
setMinSize(int width, int height)172     public void setMinSize(int width, int height) {
173         mMinSize.set(width, height);
174     }
175 
getMaxSize()176     public Point getMaxSize() {
177         return mMaxSize;
178     }
179 
getMinSize()180     public Point getMinSize() {
181         return mMinSize;
182     }
183 
184     /** Returns the expanded movement bounds. */
185     @NonNull
getExpandedMovementBounds()186     public Rect getExpandedMovementBounds() {
187         return mExpandedMovementBounds;
188     }
189 
190     /** Dictate where PiP currently should be stashed, if at all. */
setStashed(@tashType int stashedState)191     public void setStashed(@StashType int stashedState) {
192         if (mStashedState == stashedState) {
193             return;
194         }
195 
196         mStashedState = stashedState;
197         try {
198             ActivityTaskManager.getService().onPictureInPictureStateChanged(
199                     new PictureInPictureUiState(stashedState != STASH_TYPE_NONE /* isStashed */)
200             );
201         } catch (RemoteException e) {
202             Log.e(TAG, "Unable to set alert PiP state change.");
203         }
204     }
205 
206     /**
207      * Return where the PiP is stashed, if at all.
208      * @return {@code STASH_NONE}, {@code STASH_LEFT} or {@code STASH_RIGHT}.
209      */
getStashedState()210     public @StashType int getStashedState() {
211         return mStashedState;
212     }
213 
214     /** Whether PiP is stashed or not. */
isStashed()215     public boolean isStashed() {
216         return mStashedState != STASH_TYPE_NONE;
217     }
218 
219     /** Returns the offset from the edge of the screen for PiP stash. */
getStashOffset()220     public int getStashOffset() {
221         return mStashOffset;
222     }
223 
224     /** Set the PIP aspect ratio. */
setAspectRatio(float aspectRatio)225     public void setAspectRatio(float aspectRatio) {
226         mAspectRatio = aspectRatio;
227     }
228 
229     /** Get the PIP aspect ratio. */
getAspectRatio()230     public float getAspectRatio() {
231         return mAspectRatio;
232     }
233 
234     /** Save the reentry state to restore to when re-entering PIP mode. */
saveReentryState(Size size, float fraction)235     public void saveReentryState(Size size, float fraction) {
236         mPipReentryState = new PipReentryState(size, fraction);
237     }
238 
239     /** Returns the saved reentry state. */
240     @Nullable
getReentryState()241     public PipReentryState getReentryState() {
242         return mPipReentryState;
243     }
244 
245     /** Set the last {@link ComponentName} to enter PIP mode. */
setLastPipComponentName(@ullable ComponentName lastPipComponentName)246     public void setLastPipComponentName(@Nullable ComponentName lastPipComponentName) {
247         final boolean changed = !Objects.equals(mLastPipComponentName, lastPipComponentName);
248         mLastPipComponentName = lastPipComponentName;
249         if (changed) {
250             clearReentryState();
251             setHasUserResizedPip(false);
252         }
253     }
254 
255     /** Get the last PIP component name, if any. */
256     @Nullable
getLastPipComponentName()257     public ComponentName getLastPipComponentName() {
258         return mLastPipComponentName;
259     }
260 
261     /** Get the current display id. */
getDisplayId()262     public int getDisplayId() {
263         return mDisplayId;
264     }
265 
266     /** Set the current display id for the associated display layout. */
setDisplayId(int displayId)267     public void setDisplayId(int displayId) {
268         mDisplayId = displayId;
269     }
270 
271     /** Returns the display's bounds. */
272     @NonNull
getDisplayBounds()273     public Rect getDisplayBounds() {
274         return new Rect(0, 0, mDisplayLayout.width(), mDisplayLayout.height());
275     }
276 
277     /** Update the display layout. */
setDisplayLayout(@onNull DisplayLayout displayLayout)278     public void setDisplayLayout(@NonNull DisplayLayout displayLayout) {
279         mDisplayLayout.set(displayLayout);
280     }
281 
282     /** Get the display layout. */
283     @NonNull
getDisplayLayout()284     public DisplayLayout getDisplayLayout() {
285         return mDisplayLayout;
286     }
287 
288     @VisibleForTesting
clearReentryState()289     void clearReentryState() {
290         mPipReentryState = null;
291     }
292 
293     /** Set the PIP minimum edge size. */
setMinEdgeSize(int minEdgeSize)294     public void setMinEdgeSize(int minEdgeSize) {
295         mMinEdgeSize = minEdgeSize;
296     }
297 
298     /** Returns the PIP's current minimum edge size. */
getMinEdgeSize()299     public int getMinEdgeSize() {
300         return mMinEdgeSize;
301     }
302 
303     /** Sets the preferred size of PIP as specified by the activity in PIP mode. */
setOverrideMinSize(@ullable Size overrideMinSize)304     public void setOverrideMinSize(@Nullable Size overrideMinSize) {
305         final boolean changed = !Objects.equals(overrideMinSize, mOverrideMinSize);
306         mOverrideMinSize = overrideMinSize;
307         if (changed && mOnMinimalSizeChangeCallback != null) {
308             mOnMinimalSizeChangeCallback.run();
309         }
310     }
311 
312     /** Returns the preferred minimal size specified by the activity in PIP. */
313     @Nullable
getOverrideMinSize()314     public Size getOverrideMinSize() {
315         return mOverrideMinSize;
316     }
317 
318     /** Returns the minimum edge size of the override minimum size, or 0 if not set. */
getOverrideMinEdgeSize()319     public int getOverrideMinEdgeSize() {
320         if (mOverrideMinSize == null) return 0;
321         return Math.min(mOverrideMinSize.getWidth(), mOverrideMinSize.getHeight());
322     }
323 
324     /** Get the state of the bounds in motion. */
325     @NonNull
getMotionBoundsState()326     public MotionBoundsState getMotionBoundsState() {
327         return mMotionBoundsState;
328     }
329 
330     /** Set whether the IME is currently showing and its height. */
setImeVisibility(boolean imeShowing, int imeHeight)331     public void setImeVisibility(boolean imeShowing, int imeHeight) {
332         mIsImeShowing = imeShowing;
333         mImeHeight = imeHeight;
334     }
335 
336     /** Returns whether the IME is currently showing. */
isImeShowing()337     public boolean isImeShowing() {
338         return mIsImeShowing;
339     }
340 
341     /** Returns the IME height. */
getImeHeight()342     public int getImeHeight() {
343         return mImeHeight;
344     }
345 
346     /** Set whether the shelf is showing and its height. */
setShelfVisibility(boolean showing, int height)347     public void setShelfVisibility(boolean showing, int height) {
348         setShelfVisibility(showing, height, true);
349     }
350 
351     /** Set whether the shelf is showing and its height. */
setShelfVisibility(boolean showing, int height, boolean updateMovementBounds)352     public void setShelfVisibility(boolean showing, int height, boolean updateMovementBounds) {
353         final boolean shelfShowing = showing && height > 0;
354         if (shelfShowing == mIsShelfShowing && height == mShelfHeight) {
355             return;
356         }
357 
358         mIsShelfShowing = showing;
359         mShelfHeight = height;
360         if (mOnShelfVisibilityChangeCallback != null) {
361             mOnShelfVisibilityChangeCallback.accept(mIsShelfShowing, mShelfHeight,
362                     updateMovementBounds);
363         }
364     }
365 
366     /**
367      * Initialize states when first entering PiP.
368      */
setBoundsStateForEntry(ComponentName componentName, float aspectRatio, Size overrideMinSize)369     public void setBoundsStateForEntry(ComponentName componentName, float aspectRatio,
370             Size overrideMinSize) {
371         setLastPipComponentName(componentName);
372         setAspectRatio(aspectRatio);
373         setOverrideMinSize(overrideMinSize);
374     }
375 
376     /** Returns whether the shelf is currently showing. */
isShelfShowing()377     public boolean isShelfShowing() {
378         return mIsShelfShowing;
379     }
380 
381     /** Returns the shelf height. */
getShelfHeight()382     public int getShelfHeight() {
383         return mShelfHeight;
384     }
385 
386     /** Returns whether the user has resized the PIP. */
hasUserResizedPip()387     public boolean hasUserResizedPip() {
388         return mHasUserResizedPip;
389     }
390 
391     /** Set whether the user has resized the PIP. */
setHasUserResizedPip(boolean hasUserResizedPip)392     public void setHasUserResizedPip(boolean hasUserResizedPip) {
393         mHasUserResizedPip = hasUserResizedPip;
394     }
395 
396     /**
397      * Registers a callback when the minimal size of PIP that is set by the app changes.
398      */
setOnMinimalSizeChangeCallback(@ullable Runnable onMinimalSizeChangeCallback)399     public void setOnMinimalSizeChangeCallback(@Nullable Runnable onMinimalSizeChangeCallback) {
400         mOnMinimalSizeChangeCallback = onMinimalSizeChangeCallback;
401     }
402 
403     /** Set a callback to be notified when the shelf visibility changes. */
setOnShelfVisibilityChangeCallback( @ullable TriConsumer<Boolean, Integer, Boolean> onShelfVisibilityChangeCallback)404     public void setOnShelfVisibilityChangeCallback(
405             @Nullable TriConsumer<Boolean, Integer, Boolean> onShelfVisibilityChangeCallback) {
406         mOnShelfVisibilityChangeCallback = onShelfVisibilityChangeCallback;
407     }
408 
409     /**
410      * Set a callback to watch out for PiP bounds. This is mostly used by SystemUI's
411      * Back-gesture handler, to avoid conflicting with PiP when it's stashed.
412      */
setPipExclusionBoundsChangeCallback( @ullable Consumer<Rect> onPipExclusionBoundsChangeCallback)413     public void setPipExclusionBoundsChangeCallback(
414             @Nullable Consumer<Rect> onPipExclusionBoundsChangeCallback) {
415         mOnPipExclusionBoundsChangeCallback = onPipExclusionBoundsChangeCallback;
416         if (mOnPipExclusionBoundsChangeCallback != null) {
417             mOnPipExclusionBoundsChangeCallback.accept(getBounds());
418         }
419     }
420 
421     /** Source of truth for the current bounds of PIP that may be in motion. */
422     public static class MotionBoundsState {
423         /** The bounds used when PIP is in motion (e.g. during a drag or animation) */
424         private final @NonNull Rect mBoundsInMotion = new Rect();
425         /** The destination bounds to which PIP is animating. */
426         private final @NonNull Rect mAnimatingToBounds = new Rect();
427 
428         /** Whether PIP is being dragged or animated (e.g. resizing, in fling, etc). */
isInMotion()429         public boolean isInMotion() {
430             return !mBoundsInMotion.isEmpty();
431         }
432 
433         /** Set the temporary bounds used to represent the drag or animation bounds of PIP. */
setBoundsInMotion(@onNull Rect bounds)434         public void setBoundsInMotion(@NonNull Rect bounds) {
435             mBoundsInMotion.set(bounds);
436         }
437 
438         /** Set the bounds to which PIP is animating. */
setAnimatingToBounds(@onNull Rect bounds)439         public void setAnimatingToBounds(@NonNull Rect bounds) {
440             mAnimatingToBounds.set(bounds);
441         }
442 
443         /** Called when all ongoing motion operations have ended. */
onAllAnimationsEnded()444         public void onAllAnimationsEnded() {
445             mBoundsInMotion.setEmpty();
446         }
447 
448         /** Called when an ongoing physics animation has ended. */
onPhysicsAnimationEnded()449         public void onPhysicsAnimationEnded() {
450             mAnimatingToBounds.setEmpty();
451         }
452 
453         /** Returns the motion bounds. */
454         @NonNull
getBoundsInMotion()455         public Rect getBoundsInMotion() {
456             return mBoundsInMotion;
457         }
458 
459         /** Returns the destination bounds to which PIP is currently animating. */
460         @NonNull
getAnimatingToBounds()461         public Rect getAnimatingToBounds() {
462             return mAnimatingToBounds;
463         }
464 
dump(PrintWriter pw, String prefix)465         void dump(PrintWriter pw, String prefix) {
466             final String innerPrefix = prefix + "  ";
467             pw.println(prefix + MotionBoundsState.class.getSimpleName());
468             pw.println(innerPrefix + "mBoundsInMotion=" + mBoundsInMotion);
469             pw.println(innerPrefix + "mAnimatingToBounds=" + mAnimatingToBounds);
470         }
471     }
472 
473     static final class PipReentryState {
474         private static final String TAG = PipReentryState.class.getSimpleName();
475 
476         private final @Nullable Size mSize;
477         private final float mSnapFraction;
478 
PipReentryState(@ullable Size size, float snapFraction)479         PipReentryState(@Nullable Size size, float snapFraction) {
480             mSize = size;
481             mSnapFraction = snapFraction;
482         }
483 
484         @Nullable
getSize()485         Size getSize() {
486             return mSize;
487         }
488 
getSnapFraction()489         float getSnapFraction() {
490             return mSnapFraction;
491         }
492 
dump(PrintWriter pw, String prefix)493         void dump(PrintWriter pw, String prefix) {
494             final String innerPrefix = prefix + "  ";
495             pw.println(prefix + TAG);
496             pw.println(innerPrefix + "mSize=" + mSize);
497             pw.println(innerPrefix + "mSnapFraction=" + mSnapFraction);
498         }
499     }
500 
501     /** Dumps internal state. */
dump(PrintWriter pw, String prefix)502     public void dump(PrintWriter pw, String prefix) {
503         final String innerPrefix = prefix + "  ";
504         pw.println(prefix + TAG);
505         pw.println(innerPrefix + "mBounds=" + mBounds);
506         pw.println(innerPrefix + "mNormalBounds=" + mNormalBounds);
507         pw.println(innerPrefix + "mExpandedBounds=" + mExpandedBounds);
508         pw.println(innerPrefix + "mMovementBounds=" + mMovementBounds);
509         pw.println(innerPrefix + "mNormalMovementBounds=" + mNormalMovementBounds);
510         pw.println(innerPrefix + "mExpandedMovementBounds=" + mExpandedMovementBounds);
511         pw.println(innerPrefix + "mLastPipComponentName=" + mLastPipComponentName);
512         pw.println(innerPrefix + "mAspectRatio=" + mAspectRatio);
513         pw.println(innerPrefix + "mDisplayId=" + mDisplayId);
514         pw.println(innerPrefix + "mDisplayLayout=" + mDisplayLayout);
515         pw.println(innerPrefix + "mStashedState=" + mStashedState);
516         pw.println(innerPrefix + "mStashOffset=" + mStashOffset);
517         pw.println(innerPrefix + "mMinEdgeSize=" + mMinEdgeSize);
518         pw.println(innerPrefix + "mOverrideMinSize=" + mOverrideMinSize);
519         pw.println(innerPrefix + "mIsImeShowing=" + mIsImeShowing);
520         pw.println(innerPrefix + "mImeHeight=" + mImeHeight);
521         pw.println(innerPrefix + "mIsShelfShowing=" + mIsShelfShowing);
522         pw.println(innerPrefix + "mShelfHeight=" + mShelfHeight);
523         if (mPipReentryState == null) {
524             pw.println(innerPrefix + "mPipReentryState=null");
525         } else {
526             mPipReentryState.dump(pw, innerPrefix);
527         }
528         mMotionBoundsState.dump(pw, innerPrefix);
529     }
530 }
531