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