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