1 /* 2 * Copyright (C) 2012 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.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; 20 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; 21 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; 22 import static android.content.res.Configuration.ORIENTATION_PORTRAIT; 23 import static android.view.Surface.ROTATION_270; 24 import static android.view.Surface.ROTATION_90; 25 import static android.view.WindowManager.DOCKED_BOTTOM; 26 import static android.view.WindowManager.DOCKED_INVALID; 27 import static android.view.WindowManager.DOCKED_LEFT; 28 import static android.view.WindowManager.DOCKED_RIGHT; 29 import static android.view.WindowManager.DOCKED_TOP; 30 import static android.view.WindowManager.TRANSIT_NONE; 31 import static android.view.WindowManagerPolicyConstants.NAV_BAR_LEFT; 32 import static android.view.WindowManagerPolicyConstants.NAV_BAR_RIGHT; 33 34 import static com.android.server.wm.AppTransition.DEFAULT_APP_TRANSITION_DURATION; 35 import static com.android.server.wm.AppTransition.TOUCH_RESPONSE_INTERPOLATOR; 36 import static com.android.server.wm.DockedStackDividerControllerProto.MINIMIZED_DOCK; 37 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; 38 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; 39 import static com.android.server.wm.WindowManagerService.LAYER_OFFSET_DIM; 40 41 import android.content.Context; 42 import android.content.res.Configuration; 43 import android.graphics.Rect; 44 import android.os.RemoteCallbackList; 45 import android.os.RemoteException; 46 import android.util.ArraySet; 47 import android.util.Slog; 48 import android.util.proto.ProtoOutputStream; 49 import android.view.DisplayCutout; 50 import android.view.DisplayInfo; 51 import android.view.IDockedStackListener; 52 import android.view.animation.AnimationUtils; 53 import android.view.animation.Interpolator; 54 import android.view.animation.PathInterpolator; 55 56 import com.android.internal.annotations.VisibleForTesting; 57 import com.android.internal.policy.DividerSnapAlgorithm; 58 import com.android.internal.policy.DockedDividerUtils; 59 import com.android.server.LocalServices; 60 import com.android.server.inputmethod.InputMethodManagerInternal; 61 import com.android.server.wm.WindowManagerService.H; 62 63 import java.io.PrintWriter; 64 65 /** 66 * Keeps information about the docked stack divider. 67 */ 68 public class DockedStackDividerController { 69 70 private static final String TAG = TAG_WITH_CLASS_NAME ? "DockedStackDividerController" : TAG_WM; 71 72 /** 73 * The fraction during the maximize/clip reveal animation the divider meets the edge of the clip 74 * revealing surface at the earliest. 75 */ 76 private static final float CLIP_REVEAL_MEET_EARLIEST = 0.6f; 77 78 /** 79 * The fraction during the maximize/clip reveal animation the divider meets the edge of the clip 80 * revealing surface at the latest. 81 */ 82 private static final float CLIP_REVEAL_MEET_LAST = 1f; 83 84 /** 85 * If the app translates at least CLIP_REVEAL_MEET_FRACTION_MIN * minimize distance, we start 86 * meet somewhere between {@link #CLIP_REVEAL_MEET_LAST} and {@link #CLIP_REVEAL_MEET_EARLIEST}. 87 */ 88 private static final float CLIP_REVEAL_MEET_FRACTION_MIN = 0.4f; 89 90 /** 91 * If the app translates equals or more than CLIP_REVEAL_MEET_FRACTION_MIN * minimize distance, 92 * we meet at {@link #CLIP_REVEAL_MEET_EARLIEST}. 93 */ 94 private static final float CLIP_REVEAL_MEET_FRACTION_MAX = 0.8f; 95 96 private static final Interpolator IME_ADJUST_ENTRY_INTERPOLATOR = 97 new PathInterpolator(0.2f, 0f, 0.1f, 1f); 98 99 private static final long IME_ADJUST_ANIM_DURATION = 280; 100 101 private static final long IME_ADJUST_DRAWN_TIMEOUT = 200; 102 103 private static final int DIVIDER_WIDTH_INACTIVE_DP = 4; 104 105 private final WindowManagerService mService; 106 private final DisplayContent mDisplayContent; 107 private int mDividerWindowWidth; 108 private int mDividerWindowWidthInactive; 109 private int mDividerInsets; 110 private int mTaskHeightInMinimizedMode; 111 private boolean mResizing; 112 private WindowState mWindow; 113 private final Rect mTmpRect = new Rect(); 114 private final Rect mTmpRect2 = new Rect(); 115 private final Rect mTmpRect3 = new Rect(); 116 private final Rect mLastRect = new Rect(); 117 private boolean mLastVisibility = false; 118 private final RemoteCallbackList<IDockedStackListener> mDockedStackListeners 119 = new RemoteCallbackList<>(); 120 121 private boolean mMinimizedDock; 122 private int mOriginalDockedSide = DOCKED_INVALID; 123 private boolean mAnimatingForMinimizedDockedStack; 124 private boolean mAnimationStarted; 125 private long mAnimationStartTime; 126 private float mAnimationStart; 127 private float mAnimationTarget; 128 private long mAnimationDuration; 129 private boolean mAnimationStartDelayed; 130 private final Interpolator mMinimizedDockInterpolator; 131 private float mMaximizeMeetFraction; 132 private final Rect mTouchRegion = new Rect(); 133 private boolean mAnimatingForIme; 134 private boolean mAdjustedForIme; 135 private int mImeHeight; 136 private WindowState mDelayedImeWin; 137 private boolean mAdjustedForDivider; 138 private float mDividerAnimationStart; 139 private float mDividerAnimationTarget; 140 float mLastAnimationProgress; 141 float mLastDividerProgress; 142 private final DividerSnapAlgorithm[] mSnapAlgorithmForRotation = new DividerSnapAlgorithm[4]; 143 private boolean mImeHideRequested; 144 private final Rect mLastDimLayerRect = new Rect(); 145 private float mLastDimLayerAlpha; 146 private TaskStack mDimmedStack; 147 DockedStackDividerController(WindowManagerService service, DisplayContent displayContent)148 DockedStackDividerController(WindowManagerService service, DisplayContent displayContent) { 149 mService = service; 150 mDisplayContent = displayContent; 151 final Context context = service.mContext; 152 mMinimizedDockInterpolator = AnimationUtils.loadInterpolator( 153 context, android.R.interpolator.fast_out_slow_in); 154 loadDimens(); 155 } 156 getSmallestWidthDpForBounds(Rect bounds)157 int getSmallestWidthDpForBounds(Rect bounds) { 158 final DisplayInfo di = mDisplayContent.getDisplayInfo(); 159 160 final int baseDisplayWidth = mDisplayContent.mBaseDisplayWidth; 161 final int baseDisplayHeight = mDisplayContent.mBaseDisplayHeight; 162 int minWidth = Integer.MAX_VALUE; 163 164 // Go through all screen orientations and find the orientation in which the task has the 165 // smallest width. 166 for (int rotation = 0; rotation < 4; rotation++) { 167 mTmpRect.set(bounds); 168 mDisplayContent.rotateBounds(di.rotation, rotation, mTmpRect); 169 final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270); 170 mTmpRect2.set(0, 0, 171 rotated ? baseDisplayHeight : baseDisplayWidth, 172 rotated ? baseDisplayWidth : baseDisplayHeight); 173 final int orientation = mTmpRect2.width() <= mTmpRect2.height() 174 ? ORIENTATION_PORTRAIT 175 : ORIENTATION_LANDSCAPE; 176 final int dockSide = getDockSide(mTmpRect, mTmpRect2, orientation, rotation); 177 final int position = DockedDividerUtils.calculatePositionForBounds(mTmpRect, dockSide, 178 getContentWidth()); 179 180 final DisplayCutout displayCutout = mDisplayContent.calculateDisplayCutoutForRotation( 181 rotation).getDisplayCutout(); 182 183 // Since we only care about feasible states, snap to the closest snap target, like it 184 // would happen when actually rotating the screen. 185 final int snappedPosition = mSnapAlgorithmForRotation[rotation] 186 .calculateNonDismissingSnapTarget(position).position; 187 DockedDividerUtils.calculateBoundsForPosition(snappedPosition, dockSide, mTmpRect, 188 mTmpRect2.width(), mTmpRect2.height(), getContentWidth()); 189 mDisplayContent.getDisplayPolicy().getStableInsetsLw(rotation, mTmpRect2.width(), 190 mTmpRect2.height(), displayCutout, mTmpRect3); 191 mService.intersectDisplayInsetBounds(mTmpRect2, mTmpRect3, mTmpRect); 192 minWidth = Math.min(mTmpRect.width(), minWidth); 193 } 194 return (int) (minWidth / mDisplayContent.getDisplayMetrics().density); 195 } 196 197 /** 198 * Get the current docked side. Determined by its location of {@param bounds} within 199 * {@param displayRect} but if both are the same, it will try to dock to each side and determine 200 * if allowed in its respected {@param orientation}. 201 * 202 * @param bounds bounds of the docked task to get which side is docked 203 * @param displayRect bounds of the display that contains the docked task 204 * @param orientation the origination of device 205 * @return current docked side 206 */ getDockSide(Rect bounds, Rect displayRect, int orientation, int rotation)207 int getDockSide(Rect bounds, Rect displayRect, int orientation, int rotation) { 208 if (orientation == Configuration.ORIENTATION_PORTRAIT) { 209 // Portrait mode, docked either at the top or the bottom. 210 final int diff = (displayRect.bottom - bounds.bottom) - (bounds.top - displayRect.top); 211 if (diff > 0) { 212 return DOCKED_TOP; 213 } else if (diff < 0) { 214 return DOCKED_BOTTOM; 215 } 216 return canPrimaryStackDockTo(DOCKED_TOP, displayRect, rotation) 217 ? DOCKED_TOP : DOCKED_BOTTOM; 218 } else if (orientation == Configuration.ORIENTATION_LANDSCAPE) { 219 // Landscape mode, docked either on the left or on the right. 220 final int diff = (displayRect.right - bounds.right) - (bounds.left - displayRect.left); 221 if (diff > 0) { 222 return DOCKED_LEFT; 223 } else if (diff < 0) { 224 return DOCKED_RIGHT; 225 } 226 return canPrimaryStackDockTo(DOCKED_LEFT, displayRect, rotation) 227 ? DOCKED_LEFT : DOCKED_RIGHT; 228 } 229 return DOCKED_INVALID; 230 } 231 getHomeStackBoundsInDockedMode(Configuration parentConfig, int dockSide, Rect outBounds)232 void getHomeStackBoundsInDockedMode(Configuration parentConfig, int dockSide, Rect outBounds) { 233 final DisplayCutout displayCutout = mDisplayContent.getDisplayInfo().displayCutout; 234 final int displayWidth = parentConfig.windowConfiguration.getBounds().width(); 235 final int displayHeight = parentConfig.windowConfiguration.getBounds().height(); 236 mDisplayContent.getDisplayPolicy().getStableInsetsLw( 237 parentConfig.windowConfiguration.getRotation(), displayWidth, displayHeight, 238 displayCutout, mTmpRect); 239 int dividerSize = mDividerWindowWidth - 2 * mDividerInsets; 240 // The offset in the left (landscape)/top (portrait) is calculated with the minimized 241 // offset value with the divider size and any system insets in that direction. 242 if (parentConfig.orientation == Configuration.ORIENTATION_PORTRAIT) { 243 outBounds.set(0, mTaskHeightInMinimizedMode + dividerSize + mTmpRect.top, 244 displayWidth, displayHeight); 245 } else { 246 // In landscape also inset the left/right side with the status bar height to match the 247 // minimized size height in portrait mode. 248 final int primaryTaskWidth = mTaskHeightInMinimizedMode + dividerSize + mTmpRect.top; 249 int left = mTmpRect.left; 250 int right = displayWidth - mTmpRect.right; 251 if (dockSide == DOCKED_LEFT) { 252 left += primaryTaskWidth; 253 } else if (dockSide == DOCKED_RIGHT) { 254 right -= primaryTaskWidth; 255 } 256 outBounds.set(left, 0, right, displayHeight); 257 } 258 } 259 isHomeStackResizable()260 boolean isHomeStackResizable() { 261 final TaskStack homeStack = mDisplayContent.getHomeStack(); 262 if (homeStack == null) { 263 return false; 264 } 265 final Task homeTask = homeStack.findHomeTask(); 266 return homeTask != null && homeTask.isResizeable(); 267 } 268 initSnapAlgorithmForRotations()269 private void initSnapAlgorithmForRotations() { 270 final Configuration baseConfig = mDisplayContent.getConfiguration(); 271 272 // Initialize the snap algorithms for all 4 screen orientations. 273 final Configuration config = new Configuration(); 274 for (int rotation = 0; rotation < 4; rotation++) { 275 final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270); 276 final int dw = rotated 277 ? mDisplayContent.mBaseDisplayHeight 278 : mDisplayContent.mBaseDisplayWidth; 279 final int dh = rotated 280 ? mDisplayContent.mBaseDisplayWidth 281 : mDisplayContent.mBaseDisplayHeight; 282 final DisplayCutout displayCutout = 283 mDisplayContent.calculateDisplayCutoutForRotation(rotation).getDisplayCutout(); 284 final DisplayPolicy displayPolicy = mDisplayContent.getDisplayPolicy(); 285 displayPolicy.getStableInsetsLw(rotation, dw, dh, displayCutout, mTmpRect); 286 config.unset(); 287 config.orientation = (dw <= dh) ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE; 288 289 final int appWidth = displayPolicy.getNonDecorDisplayWidth(dw, dh, rotation, 290 baseConfig.uiMode, displayCutout); 291 final int appHeight = displayPolicy.getNonDecorDisplayHeight(dw, dh, rotation, 292 baseConfig.uiMode, displayCutout); 293 displayPolicy.getNonDecorInsetsLw(rotation, dw, dh, displayCutout, mTmpRect); 294 final int leftInset = mTmpRect.left; 295 final int topInset = mTmpRect.top; 296 297 config.windowConfiguration.setAppBounds(leftInset /*left*/, topInset /*top*/, 298 leftInset + appWidth /*right*/, topInset + appHeight /*bottom*/); 299 300 final float density = mDisplayContent.getDisplayMetrics().density; 301 config.screenWidthDp = (int) (displayPolicy.getConfigDisplayWidth(dw, dh, rotation, 302 baseConfig.uiMode, displayCutout) / density); 303 config.screenHeightDp = (int) (displayPolicy.getConfigDisplayHeight(dw, dh, rotation, 304 baseConfig.uiMode, displayCutout) / density); 305 final Context rotationContext = mService.mContext.createConfigurationContext(config); 306 mSnapAlgorithmForRotation[rotation] = new DividerSnapAlgorithm( 307 rotationContext.getResources(), dw, dh, getContentWidth(), 308 config.orientation == ORIENTATION_PORTRAIT, mTmpRect); 309 } 310 } 311 loadDimens()312 private void loadDimens() { 313 final Context context = mService.mContext; 314 mDividerWindowWidth = context.getResources().getDimensionPixelSize( 315 com.android.internal.R.dimen.docked_stack_divider_thickness); 316 mDividerInsets = context.getResources().getDimensionPixelSize( 317 com.android.internal.R.dimen.docked_stack_divider_insets); 318 mDividerWindowWidthInactive = WindowManagerService.dipToPixel( 319 DIVIDER_WIDTH_INACTIVE_DP, mDisplayContent.getDisplayMetrics()); 320 mTaskHeightInMinimizedMode = context.getResources().getDimensionPixelSize( 321 com.android.internal.R.dimen.task_height_of_minimized_mode); 322 initSnapAlgorithmForRotations(); 323 } 324 onConfigurationChanged()325 void onConfigurationChanged() { 326 loadDimens(); 327 } 328 isResizing()329 boolean isResizing() { 330 return mResizing; 331 } 332 getContentWidth()333 int getContentWidth() { 334 return mDividerWindowWidth - 2 * mDividerInsets; 335 } 336 getContentInsets()337 int getContentInsets() { 338 return mDividerInsets; 339 } 340 getContentWidthInactive()341 int getContentWidthInactive() { 342 return mDividerWindowWidthInactive; 343 } 344 setResizing(boolean resizing)345 void setResizing(boolean resizing) { 346 if (mResizing != resizing) { 347 mResizing = resizing; 348 resetDragResizingChangeReported(); 349 } 350 } 351 setTouchRegion(Rect touchRegion)352 void setTouchRegion(Rect touchRegion) { 353 mTouchRegion.set(touchRegion); 354 } 355 getTouchRegion(Rect outRegion)356 void getTouchRegion(Rect outRegion) { 357 outRegion.set(mTouchRegion); 358 outRegion.offset(mWindow.getFrameLw().left, mWindow.getFrameLw().top); 359 } 360 resetDragResizingChangeReported()361 private void resetDragResizingChangeReported() { 362 mDisplayContent.forAllWindows(WindowState::resetDragResizingChangeReported, 363 true /* traverseTopToBottom */ ); 364 } 365 setWindow(WindowState window)366 void setWindow(WindowState window) { 367 mWindow = window; 368 reevaluateVisibility(false); 369 } 370 reevaluateVisibility(boolean force)371 void reevaluateVisibility(boolean force) { 372 if (mWindow == null) { 373 return; 374 } 375 TaskStack stack = mDisplayContent.getSplitScreenPrimaryStackIgnoringVisibility(); 376 377 // If the stack is invisible, we policy force hide it in WindowAnimator.shouldForceHide 378 final boolean visible = stack != null; 379 if (mLastVisibility == visible && !force) { 380 return; 381 } 382 mLastVisibility = visible; 383 notifyDockedDividerVisibilityChanged(visible); 384 if (!visible) { 385 setResizeDimLayer(false, WINDOWING_MODE_UNDEFINED, 0f); 386 } 387 } 388 wasVisible()389 private boolean wasVisible() { 390 return mLastVisibility; 391 } 392 setAdjustedForIme( boolean adjustedForIme, boolean adjustedForDivider, boolean animate, WindowState imeWin, int imeHeight)393 void setAdjustedForIme( 394 boolean adjustedForIme, boolean adjustedForDivider, 395 boolean animate, WindowState imeWin, int imeHeight) { 396 if (mAdjustedForIme != adjustedForIme || (adjustedForIme && mImeHeight != imeHeight) 397 || mAdjustedForDivider != adjustedForDivider) { 398 if (animate && !mAnimatingForMinimizedDockedStack) { 399 // Notify SystemUI to set the target docked stack size according current docked 400 // state without animation when calling startImeAdjustAnimation. 401 notifyDockedStackMinimizedChanged(mMinimizedDock, false /* animate */, 402 isHomeStackResizable()); 403 startImeAdjustAnimation(adjustedForIme, adjustedForDivider, imeWin); 404 } else { 405 // Animation might be delayed, so only notify if we don't run an animation. 406 notifyAdjustedForImeChanged(adjustedForIme || adjustedForDivider, 0 /* duration */); 407 } 408 mAdjustedForIme = adjustedForIme; 409 mImeHeight = imeHeight; 410 mAdjustedForDivider = adjustedForDivider; 411 } 412 } 413 getImeHeightAdjustedFor()414 int getImeHeightAdjustedFor() { 415 return mImeHeight; 416 } 417 positionDockedStackedDivider(Rect frame)418 void positionDockedStackedDivider(Rect frame) { 419 TaskStack stack = mDisplayContent.getSplitScreenPrimaryStackIgnoringVisibility(); 420 if (stack == null) { 421 // Unfortunately we might end up with still having a divider, even though the underlying 422 // stack was already removed. This is because we are on AM thread and the removal of the 423 // divider was deferred to WM thread and hasn't happened yet. In that case let's just 424 // keep putting it in the same place it was before the stack was removed to have 425 // continuity and prevent it from jumping to the center. It will get hidden soon. 426 frame.set(mLastRect); 427 return; 428 } else { 429 stack.getDimBounds(mTmpRect); 430 } 431 int side = stack.getDockSide(); 432 switch (side) { 433 case DOCKED_LEFT: 434 frame.set(mTmpRect.right - mDividerInsets, frame.top, 435 mTmpRect.right + frame.width() - mDividerInsets, frame.bottom); 436 break; 437 case DOCKED_TOP: 438 frame.set(frame.left, mTmpRect.bottom - mDividerInsets, 439 mTmpRect.right, mTmpRect.bottom + frame.height() - mDividerInsets); 440 break; 441 case DOCKED_RIGHT: 442 frame.set(mTmpRect.left - frame.width() + mDividerInsets, frame.top, 443 mTmpRect.left + mDividerInsets, frame.bottom); 444 break; 445 case DOCKED_BOTTOM: 446 frame.set(frame.left, mTmpRect.top - frame.height() + mDividerInsets, 447 frame.right, mTmpRect.top + mDividerInsets); 448 break; 449 } 450 mLastRect.set(frame); 451 } 452 notifyDockedDividerVisibilityChanged(boolean visible)453 private void notifyDockedDividerVisibilityChanged(boolean visible) { 454 final int size = mDockedStackListeners.beginBroadcast(); 455 for (int i = 0; i < size; ++i) { 456 final IDockedStackListener listener = mDockedStackListeners.getBroadcastItem(i); 457 try { 458 listener.onDividerVisibilityChanged(visible); 459 } catch (RemoteException e) { 460 Slog.e(TAG_WM, "Error delivering divider visibility changed event.", e); 461 } 462 } 463 mDockedStackListeners.finishBroadcast(); 464 } 465 466 /** 467 * Checks if the primary stack is allowed to dock to a specific side based on its original dock 468 * side. 469 * 470 * @param dockSide the side to see if it is valid 471 * @return true if the side provided is valid 472 */ canPrimaryStackDockTo(int dockSide, Rect parentRect, int rotation)473 boolean canPrimaryStackDockTo(int dockSide, Rect parentRect, int rotation) { 474 final DisplayPolicy policy = mDisplayContent.getDisplayPolicy(); 475 return isDockSideAllowed(dockSide, mOriginalDockedSide, 476 policy.navigationBarPosition(parentRect.width(), parentRect.height(), rotation), 477 policy.navigationBarCanMove()); 478 } 479 480 @VisibleForTesting isDockSideAllowed(int dockSide, int originalDockSide, int navBarPosition, boolean navigationBarCanMove)481 static boolean isDockSideAllowed(int dockSide, int originalDockSide, int navBarPosition, 482 boolean navigationBarCanMove) { 483 if (dockSide == DOCKED_TOP) { 484 return true; 485 } 486 487 if (navigationBarCanMove) { 488 // Only allow the dockside opposite to the nav bar position in landscape 489 return dockSide == DOCKED_LEFT && navBarPosition == NAV_BAR_RIGHT 490 || dockSide == DOCKED_RIGHT && navBarPosition == NAV_BAR_LEFT; 491 } 492 493 // Side is the same as original side 494 if (dockSide == originalDockSide) { 495 return true; 496 } 497 498 // Only if original docked side was top in portrait will allow left for landscape 499 return dockSide == DOCKED_LEFT && originalDockSide == DOCKED_TOP; 500 } 501 notifyDockedStackExistsChanged(boolean exists)502 void notifyDockedStackExistsChanged(boolean exists) { 503 // TODO(multi-display): Perform all actions only for current display. 504 final int size = mDockedStackListeners.beginBroadcast(); 505 for (int i = 0; i < size; ++i) { 506 final IDockedStackListener listener = mDockedStackListeners.getBroadcastItem(i); 507 try { 508 listener.onDockedStackExistsChanged(exists); 509 } catch (RemoteException e) { 510 Slog.e(TAG_WM, "Error delivering docked stack exists changed event.", e); 511 } 512 } 513 mDockedStackListeners.finishBroadcast(); 514 if (exists) { 515 InputMethodManagerInternal inputMethodManagerInternal = 516 LocalServices.getService(InputMethodManagerInternal.class); 517 if (inputMethodManagerInternal != null) { 518 519 // Hide the current IME to avoid problems with animations from IME adjustment when 520 // attaching the docked stack. 521 inputMethodManagerInternal.hideCurrentInputMethod(); 522 mImeHideRequested = true; 523 } 524 525 // If a primary stack was just created, it will not have access to display content at 526 // this point so pass it from here to get a valid dock side. 527 final TaskStack stack = mDisplayContent.getSplitScreenPrimaryStackIgnoringVisibility(); 528 mOriginalDockedSide = stack.getDockSideForDisplay(mDisplayContent); 529 return; 530 } 531 mOriginalDockedSide = DOCKED_INVALID; 532 setMinimizedDockedStack(false /* minimizedDock */, false /* animate */); 533 534 if (mDimmedStack != null) { 535 mDimmedStack.stopDimming(); 536 mDimmedStack = null; 537 } 538 } 539 540 /** 541 * Resets the state that IME hide has been requested. See {@link #isImeHideRequested}. 542 */ resetImeHideRequested()543 void resetImeHideRequested() { 544 mImeHideRequested = false; 545 } 546 547 /** 548 * The docked stack divider controller makes sure the IME gets hidden when attaching the docked 549 * stack, to avoid animation problems. This flag indicates whether the request to hide the IME 550 * has been sent in an asynchronous manner, and the IME should be treated as hidden already. 551 * 552 * @return whether IME hide request has been sent 553 */ isImeHideRequested()554 boolean isImeHideRequested() { 555 return mImeHideRequested; 556 } 557 notifyDockedStackMinimizedChanged(boolean minimizedDock, boolean animate, boolean isHomeStackResizable)558 private void notifyDockedStackMinimizedChanged(boolean minimizedDock, boolean animate, 559 boolean isHomeStackResizable) { 560 long animDuration = 0; 561 if (animate) { 562 final TaskStack stack = 563 mDisplayContent.getSplitScreenPrimaryStackIgnoringVisibility(); 564 final long transitionDuration = isAnimationMaximizing() 565 ? mDisplayContent.mAppTransition.getLastClipRevealTransitionDuration() 566 : DEFAULT_APP_TRANSITION_DURATION; 567 mAnimationDuration = (long) 568 (transitionDuration * mService.getTransitionAnimationScaleLocked()); 569 mMaximizeMeetFraction = getClipRevealMeetFraction(stack); 570 animDuration = (long) (mAnimationDuration * mMaximizeMeetFraction); 571 } 572 mService.mAtmInternal.notifyDockedStackMinimizedChanged(minimizedDock); 573 final int size = mDockedStackListeners.beginBroadcast(); 574 for (int i = 0; i < size; ++i) { 575 final IDockedStackListener listener = mDockedStackListeners.getBroadcastItem(i); 576 try { 577 listener.onDockedStackMinimizedChanged(minimizedDock, animDuration, 578 isHomeStackResizable); 579 } catch (RemoteException e) { 580 Slog.e(TAG_WM, "Error delivering minimized dock changed event.", e); 581 } 582 } 583 mDockedStackListeners.finishBroadcast(); 584 } 585 notifyDockSideChanged(int newDockSide)586 void notifyDockSideChanged(int newDockSide) { 587 final int size = mDockedStackListeners.beginBroadcast(); 588 for (int i = 0; i < size; ++i) { 589 final IDockedStackListener listener = mDockedStackListeners.getBroadcastItem(i); 590 try { 591 listener.onDockSideChanged(newDockSide); 592 } catch (RemoteException e) { 593 Slog.e(TAG_WM, "Error delivering dock side changed event.", e); 594 } 595 } 596 mDockedStackListeners.finishBroadcast(); 597 } 598 notifyAdjustedForImeChanged(boolean adjustedForIme, long animDuration)599 private void notifyAdjustedForImeChanged(boolean adjustedForIme, long animDuration) { 600 final int size = mDockedStackListeners.beginBroadcast(); 601 for (int i = 0; i < size; ++i) { 602 final IDockedStackListener listener = mDockedStackListeners.getBroadcastItem(i); 603 try { 604 listener.onAdjustedForImeChanged(adjustedForIme, animDuration); 605 } catch (RemoteException e) { 606 Slog.e(TAG_WM, "Error delivering adjusted for ime changed event.", e); 607 } 608 } 609 mDockedStackListeners.finishBroadcast(); 610 } 611 registerDockedStackListener(IDockedStackListener listener)612 void registerDockedStackListener(IDockedStackListener listener) { 613 mDockedStackListeners.register(listener); 614 notifyDockedDividerVisibilityChanged(wasVisible()); 615 notifyDockedStackExistsChanged( 616 mDisplayContent.getSplitScreenPrimaryStackIgnoringVisibility() != null); 617 notifyDockedStackMinimizedChanged(mMinimizedDock, false /* animate */, 618 isHomeStackResizable()); 619 notifyAdjustedForImeChanged(mAdjustedForIme, 0 /* animDuration */); 620 621 } 622 623 /** 624 * Shows a dim layer with {@param alpha} if {@param visible} is true and 625 * {@param targetWindowingMode} isn't 626 * {@link android.app.WindowConfiguration#WINDOWING_MODE_UNDEFINED} and there is a stack on the 627 * display in that windowing mode. 628 */ setResizeDimLayer(boolean visible, int targetWindowingMode, float alpha)629 void setResizeDimLayer(boolean visible, int targetWindowingMode, float alpha) { 630 // TODO: Maybe only allow split-screen windowing modes? 631 final TaskStack stack = targetWindowingMode != WINDOWING_MODE_UNDEFINED 632 ? mDisplayContent.getTopStackInWindowingMode(targetWindowingMode) 633 : null; 634 final TaskStack dockedStack = mDisplayContent.getSplitScreenPrimaryStack(); 635 boolean visibleAndValid = visible && stack != null && dockedStack != null; 636 637 // Ensure an old dim that was shown for the docked stack divider is removed so we don't end 638 // up with dim layers that can no longer be removed. 639 if (mDimmedStack != null && mDimmedStack != stack) { 640 mDimmedStack.stopDimming(); 641 mDimmedStack = null; 642 } 643 644 if (visibleAndValid) { 645 mDimmedStack = stack; 646 stack.dim(alpha); 647 } 648 if (!visibleAndValid && stack != null) { 649 mDimmedStack = null; 650 stack.stopDimming(); 651 } 652 } 653 654 /** 655 * @return The layer used for dimming the apps when dismissing docked/fullscreen stack. Just 656 * above all application surfaces. 657 */ getResizeDimLayer()658 private int getResizeDimLayer() { 659 return (mWindow != null) ? mWindow.mLayer - 1 : LAYER_OFFSET_DIM; 660 } 661 662 /** 663 * Notifies the docked stack divider controller of a visibility change that happens without 664 * an animation. 665 */ notifyAppVisibilityChanged()666 void notifyAppVisibilityChanged() { 667 checkMinimizeChanged(false /* animate */); 668 } 669 notifyAppTransitionStarting(ArraySet<AppWindowToken> openingApps, int appTransition)670 void notifyAppTransitionStarting(ArraySet<AppWindowToken> openingApps, int appTransition) { 671 final boolean wasMinimized = mMinimizedDock; 672 checkMinimizeChanged(true /* animate */); 673 674 // We were minimized, and now we are still minimized, but somebody is trying to launch an 675 // app in docked stack, better show recent apps so we actually get unminimized! However do 676 // not do this if keyguard is dismissed such as when the device is unlocking. This catches 677 // any case that was missed in ActivityStarter.postStartActivityUncheckedProcessing because 678 // we couldn't retrace the launch of the app in the docked stack to the launch from 679 // homescreen. 680 if (wasMinimized && mMinimizedDock && containsAppInDockedStack(openingApps) 681 && appTransition != TRANSIT_NONE && 682 !AppTransition.isKeyguardGoingAwayTransit(appTransition)) { 683 if (mService.mAtmInternal.isRecentsComponentHomeActivity(mService.mCurrentUserId)) { 684 // When the home activity is the recents component and we are already minimized, 685 // then there is nothing to do here since home is already visible 686 } else { 687 mService.showRecentApps(); 688 } 689 } 690 } 691 692 /** 693 * @return true if {@param apps} contains an activity in the docked stack, false otherwise. 694 */ containsAppInDockedStack(ArraySet<AppWindowToken> apps)695 private boolean containsAppInDockedStack(ArraySet<AppWindowToken> apps) { 696 for (int i = apps.size() - 1; i >= 0; i--) { 697 final AppWindowToken token = apps.valueAt(i); 698 if (token.getTask() != null && token.inSplitScreenPrimaryWindowingMode()) { 699 return true; 700 } 701 } 702 return false; 703 } 704 isMinimizedDock()705 boolean isMinimizedDock() { 706 return mMinimizedDock; 707 } 708 checkMinimizeChanged(boolean animate)709 void checkMinimizeChanged(boolean animate) { 710 if (mDisplayContent.getSplitScreenPrimaryStackIgnoringVisibility() == null) { 711 return; 712 } 713 final TaskStack homeStack = mDisplayContent.getHomeStack(); 714 if (homeStack == null) { 715 return; 716 } 717 final Task homeTask = homeStack.findHomeTask(); 718 if (homeTask == null || !isWithinDisplay(homeTask)) { 719 return; 720 } 721 722 // Do not minimize when dock is already minimized while keyguard is showing and not 723 // occluded such as unlocking the screen 724 if (mMinimizedDock && mService.mKeyguardOrAodShowingOnDefaultDisplay) { 725 return; 726 } 727 final TaskStack topSecondaryStack = mDisplayContent.getTopStackInWindowingMode( 728 WINDOWING_MODE_SPLIT_SCREEN_SECONDARY); 729 final RecentsAnimationController recentsAnim = mService.getRecentsAnimationController(); 730 final boolean minimizedForRecentsAnimation = recentsAnim != null && 731 recentsAnim.isSplitScreenMinimized(); 732 boolean homeVisible = homeTask.getTopVisibleAppToken() != null; 733 if (homeVisible && topSecondaryStack != null) { 734 // Home should only be considered visible if it is greater or equal to the top secondary 735 // stack in terms of z-order. 736 homeVisible = homeStack.compareTo(topSecondaryStack) >= 0; 737 } 738 setMinimizedDockedStack(homeVisible || minimizedForRecentsAnimation, animate); 739 } 740 isWithinDisplay(Task task)741 private boolean isWithinDisplay(Task task) { 742 task.getBounds(mTmpRect); 743 mDisplayContent.getBounds(mTmpRect2); 744 return mTmpRect.intersect(mTmpRect2); 745 } 746 747 /** 748 * Sets whether the docked stack is currently in a minimized state, i.e. all the tasks in the 749 * docked stack are heavily clipped so you can only see a minimal peek state. 750 * 751 * @param minimizedDock Whether the docked stack is currently minimized. 752 * @param animate Whether to animate the change. 753 */ setMinimizedDockedStack(boolean minimizedDock, boolean animate)754 private void setMinimizedDockedStack(boolean minimizedDock, boolean animate) { 755 final boolean wasMinimized = mMinimizedDock; 756 mMinimizedDock = minimizedDock; 757 if (minimizedDock == wasMinimized) { 758 return; 759 } 760 761 final boolean imeChanged = clearImeAdjustAnimation(); 762 boolean minimizedChange = false; 763 if (isHomeStackResizable()) { 764 notifyDockedStackMinimizedChanged(minimizedDock, animate, 765 true /* isHomeStackResizable */); 766 minimizedChange = true; 767 } else { 768 if (minimizedDock) { 769 if (animate) { 770 startAdjustAnimation(0f, 1f); 771 } else { 772 minimizedChange |= setMinimizedDockedStack(true); 773 } 774 } else { 775 if (animate) { 776 startAdjustAnimation(1f, 0f); 777 } else { 778 minimizedChange |= setMinimizedDockedStack(false); 779 } 780 } 781 } 782 if (imeChanged || minimizedChange) { 783 if (imeChanged && !minimizedChange) { 784 Slog.d(TAG, "setMinimizedDockedStack: IME adjust changed due to minimizing," 785 + " minimizedDock=" + minimizedDock 786 + " minimizedChange=" + minimizedChange); 787 } 788 mService.mWindowPlacerLocked.performSurfacePlacement(); 789 } 790 } 791 clearImeAdjustAnimation()792 private boolean clearImeAdjustAnimation() { 793 final boolean changed = mDisplayContent.clearImeAdjustAnimation(); 794 mAnimatingForIme = false; 795 return changed; 796 } 797 startAdjustAnimation(float from, float to)798 private void startAdjustAnimation(float from, float to) { 799 mAnimatingForMinimizedDockedStack = true; 800 mAnimationStarted = false; 801 mAnimationStart = from; 802 mAnimationTarget = to; 803 } 804 startImeAdjustAnimation( boolean adjustedForIme, boolean adjustedForDivider, WindowState imeWin)805 private void startImeAdjustAnimation( 806 boolean adjustedForIme, boolean adjustedForDivider, WindowState imeWin) { 807 808 // If we're not in an animation, the starting point depends on whether we're adjusted 809 // or not. If we're already in an animation, we start from where the current animation 810 // left off, so that the motion doesn't look discontinuous. 811 if (!mAnimatingForIme) { 812 mAnimationStart = mAdjustedForIme ? 1 : 0; 813 mDividerAnimationStart = mAdjustedForDivider ? 1 : 0; 814 mLastAnimationProgress = mAnimationStart; 815 mLastDividerProgress = mDividerAnimationStart; 816 } else { 817 mAnimationStart = mLastAnimationProgress; 818 mDividerAnimationStart = mLastDividerProgress; 819 } 820 mAnimatingForIme = true; 821 mAnimationStarted = false; 822 mAnimationTarget = adjustedForIme ? 1 : 0; 823 mDividerAnimationTarget = adjustedForDivider ? 1 : 0; 824 825 mDisplayContent.beginImeAdjustAnimation(); 826 827 // We put all tasks into drag resizing mode - wait until all of them have completed the 828 // drag resizing switch. 829 if (!mService.mWaitingForDrawn.isEmpty()) { 830 mService.mH.removeMessages(H.WAITING_FOR_DRAWN_TIMEOUT); 831 mService.mH.sendEmptyMessageDelayed(H.WAITING_FOR_DRAWN_TIMEOUT, 832 IME_ADJUST_DRAWN_TIMEOUT); 833 mAnimationStartDelayed = true; 834 if (imeWin != null) { 835 836 // There might be an old window delaying the animation start - clear it. 837 if (mDelayedImeWin != null) { 838 mDelayedImeWin.endDelayingAnimationStart(); 839 } 840 mDelayedImeWin = imeWin; 841 imeWin.startDelayingAnimationStart(); 842 } 843 844 // If we are already waiting for something to be drawn, clear out the old one so it 845 // still gets executed. 846 // TODO: Have a real system where we can wait on different windows to be drawn with 847 // different callbacks. 848 if (mService.mWaitingForDrawnCallback != null) { 849 mService.mWaitingForDrawnCallback.run(); 850 } 851 mService.mWaitingForDrawnCallback = () -> { 852 synchronized (mService.mGlobalLock) { 853 mAnimationStartDelayed = false; 854 if (mDelayedImeWin != null) { 855 mDelayedImeWin.endDelayingAnimationStart(); 856 } 857 // If the adjust status changed since this was posted, only notify 858 // the new states and don't animate. 859 long duration = 0; 860 if (mAdjustedForIme == adjustedForIme 861 && mAdjustedForDivider == adjustedForDivider) { 862 duration = IME_ADJUST_ANIM_DURATION; 863 } else { 864 Slog.w(TAG, "IME adjust changed while waiting for drawn:" 865 + " adjustedForIme=" + adjustedForIme 866 + " adjustedForDivider=" + adjustedForDivider 867 + " mAdjustedForIme=" + mAdjustedForIme 868 + " mAdjustedForDivider=" + mAdjustedForDivider); 869 } 870 notifyAdjustedForImeChanged( 871 mAdjustedForIme || mAdjustedForDivider, duration); 872 } 873 }; 874 } else { 875 notifyAdjustedForImeChanged( 876 adjustedForIme || adjustedForDivider, IME_ADJUST_ANIM_DURATION); 877 } 878 } 879 setMinimizedDockedStack(boolean minimized)880 private boolean setMinimizedDockedStack(boolean minimized) { 881 final TaskStack stack = mDisplayContent.getSplitScreenPrimaryStackIgnoringVisibility(); 882 notifyDockedStackMinimizedChanged(minimized, false /* animate */, isHomeStackResizable()); 883 return stack != null && stack.setAdjustedForMinimizedDock(minimized ? 1f : 0f); 884 } 885 isAnimationMaximizing()886 private boolean isAnimationMaximizing() { 887 return mAnimationTarget == 0f; 888 } 889 animate(long now)890 public boolean animate(long now) { 891 if (mWindow == null) { 892 return false; 893 } 894 if (mAnimatingForMinimizedDockedStack) { 895 return animateForMinimizedDockedStack(now); 896 } else if (mAnimatingForIme && !mDisplayContent.mAppTransition.isRunning()) { 897 // To prevent task stack resize animation may flicking when playing app transition 898 // animation & IME window enter animation in parallel, make sure app transition is done 899 // and then start to animate for IME. 900 return animateForIme(now); 901 } 902 return false; 903 } 904 animateForIme(long now)905 private boolean animateForIme(long now) { 906 if (!mAnimationStarted || mAnimationStartDelayed) { 907 mAnimationStarted = true; 908 mAnimationStartTime = now; 909 mAnimationDuration = (long) 910 (IME_ADJUST_ANIM_DURATION * mService.getWindowAnimationScaleLocked()); 911 } 912 float t = Math.min(1f, (float) (now - mAnimationStartTime) / mAnimationDuration); 913 t = (mAnimationTarget == 1f ? IME_ADJUST_ENTRY_INTERPOLATOR : TOUCH_RESPONSE_INTERPOLATOR) 914 .getInterpolation(t); 915 final boolean updated = 916 mDisplayContent.animateForIme(t, mAnimationTarget, mDividerAnimationTarget); 917 if (updated) { 918 mService.mWindowPlacerLocked.performSurfacePlacement(); 919 } 920 if (t >= 1.0f) { 921 mLastAnimationProgress = mAnimationTarget; 922 mLastDividerProgress = mDividerAnimationTarget; 923 mAnimatingForIme = false; 924 return false; 925 } else { 926 return true; 927 } 928 } 929 animateForMinimizedDockedStack(long now)930 private boolean animateForMinimizedDockedStack(long now) { 931 final TaskStack stack = mDisplayContent.getSplitScreenPrimaryStackIgnoringVisibility(); 932 if (!mAnimationStarted) { 933 mAnimationStarted = true; 934 mAnimationStartTime = now; 935 notifyDockedStackMinimizedChanged(mMinimizedDock, true /* animate */, 936 isHomeStackResizable() /* isHomeStackResizable */); 937 } 938 float t = Math.min(1f, (float) (now - mAnimationStartTime) / mAnimationDuration); 939 t = (isAnimationMaximizing() ? TOUCH_RESPONSE_INTERPOLATOR : mMinimizedDockInterpolator) 940 .getInterpolation(t); 941 if (stack != null) { 942 if (stack.setAdjustedForMinimizedDock(getMinimizeAmount(stack, t))) { 943 mService.mWindowPlacerLocked.performSurfacePlacement(); 944 } 945 } 946 if (t >= 1.0f) { 947 mAnimatingForMinimizedDockedStack = false; 948 return false; 949 } else { 950 return true; 951 } 952 } 953 getInterpolatedAnimationValue(float t)954 float getInterpolatedAnimationValue(float t) { 955 return t * mAnimationTarget + (1 - t) * mAnimationStart; 956 } 957 getInterpolatedDividerValue(float t)958 float getInterpolatedDividerValue(float t) { 959 return t * mDividerAnimationTarget + (1 - t) * mDividerAnimationStart; 960 } 961 962 /** 963 * Gets the amount how much to minimize a stack depending on the interpolated fraction t. 964 */ getMinimizeAmount(TaskStack stack, float t)965 private float getMinimizeAmount(TaskStack stack, float t) { 966 final float naturalAmount = getInterpolatedAnimationValue(t); 967 if (isAnimationMaximizing()) { 968 return adjustMaximizeAmount(stack, t, naturalAmount); 969 } else { 970 return naturalAmount; 971 } 972 } 973 974 /** 975 * When maximizing the stack during a clip reveal transition, this adjusts the minimize amount 976 * during the transition such that the edge of the clip reveal rect is met earlier in the 977 * transition so we don't create a visible "hole", but only if both the clip reveal and the 978 * docked stack divider start from about the same portion on the screen. 979 */ adjustMaximizeAmount(TaskStack stack, float t, float naturalAmount)980 private float adjustMaximizeAmount(TaskStack stack, float t, float naturalAmount) { 981 if (mMaximizeMeetFraction == 1f) { 982 return naturalAmount; 983 } 984 final int minimizeDistance = stack.getMinimizeDistance(); 985 final float startPrime = mDisplayContent.mAppTransition.getLastClipRevealMaxTranslation() 986 / (float) minimizeDistance; 987 final float amountPrime = t * mAnimationTarget + (1 - t) * startPrime; 988 final float t2 = Math.min(t / mMaximizeMeetFraction, 1); 989 return amountPrime * t2 + naturalAmount * (1 - t2); 990 } 991 992 /** 993 * Retrieves the animation fraction at which the docked stack has to meet the clip reveal 994 * edge. See {@link #adjustMaximizeAmount}. 995 */ getClipRevealMeetFraction(TaskStack stack)996 private float getClipRevealMeetFraction(TaskStack stack) { 997 if (!isAnimationMaximizing() || stack == null || 998 !mDisplayContent.mAppTransition.hadClipRevealAnimation()) { 999 return 1f; 1000 } 1001 final int minimizeDistance = stack.getMinimizeDistance(); 1002 final float fraction = Math.abs(mDisplayContent.mAppTransition 1003 .getLastClipRevealMaxTranslation()) / (float) minimizeDistance; 1004 final float t = Math.max(0, Math.min(1, (fraction - CLIP_REVEAL_MEET_FRACTION_MIN) 1005 / (CLIP_REVEAL_MEET_FRACTION_MAX - CLIP_REVEAL_MEET_FRACTION_MIN))); 1006 return CLIP_REVEAL_MEET_EARLIEST 1007 + (1 - t) * (CLIP_REVEAL_MEET_LAST - CLIP_REVEAL_MEET_EARLIEST); 1008 } 1009 toShortString()1010 public String toShortString() { 1011 return TAG; 1012 } 1013 getWindow()1014 WindowState getWindow() { 1015 return mWindow; 1016 } 1017 dump(String prefix, PrintWriter pw)1018 void dump(String prefix, PrintWriter pw) { 1019 pw.println(prefix + "DockedStackDividerController"); 1020 pw.println(prefix + " mLastVisibility=" + mLastVisibility); 1021 pw.println(prefix + " mMinimizedDock=" + mMinimizedDock); 1022 pw.println(prefix + " mAdjustedForIme=" + mAdjustedForIme); 1023 pw.println(prefix + " mAdjustedForDivider=" + mAdjustedForDivider); 1024 } 1025 writeToProto(ProtoOutputStream proto, long fieldId)1026 void writeToProto(ProtoOutputStream proto, long fieldId) { 1027 final long token = proto.start(fieldId); 1028 proto.write(MINIMIZED_DOCK, mMinimizedDock); 1029 proto.end(token); 1030 } 1031 } 1032