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 final int size = mDockedStackListeners.beginBroadcast(); 573 for (int i = 0; i < size; ++i) { 574 final IDockedStackListener listener = mDockedStackListeners.getBroadcastItem(i); 575 try { 576 listener.onDockedStackMinimizedChanged(minimizedDock, animDuration, 577 isHomeStackResizable); 578 } catch (RemoteException e) { 579 Slog.e(TAG_WM, "Error delivering minimized dock changed event.", e); 580 } 581 } 582 mDockedStackListeners.finishBroadcast(); 583 // Only notify ATM after we update the remote listeners, otherwise it may trigger another 584 // minimize change, which would lead to an inversion of states send to the listeners 585 mService.mAtmInternal.notifyDockedStackMinimizedChanged(minimizedDock); 586 } 587 notifyDockSideChanged(int newDockSide)588 void notifyDockSideChanged(int newDockSide) { 589 final int size = mDockedStackListeners.beginBroadcast(); 590 for (int i = 0; i < size; ++i) { 591 final IDockedStackListener listener = mDockedStackListeners.getBroadcastItem(i); 592 try { 593 listener.onDockSideChanged(newDockSide); 594 } catch (RemoteException e) { 595 Slog.e(TAG_WM, "Error delivering dock side changed event.", e); 596 } 597 } 598 mDockedStackListeners.finishBroadcast(); 599 } 600 notifyAdjustedForImeChanged(boolean adjustedForIme, long animDuration)601 private void notifyAdjustedForImeChanged(boolean adjustedForIme, long animDuration) { 602 final int size = mDockedStackListeners.beginBroadcast(); 603 for (int i = 0; i < size; ++i) { 604 final IDockedStackListener listener = mDockedStackListeners.getBroadcastItem(i); 605 try { 606 listener.onAdjustedForImeChanged(adjustedForIme, animDuration); 607 } catch (RemoteException e) { 608 Slog.e(TAG_WM, "Error delivering adjusted for ime changed event.", e); 609 } 610 } 611 mDockedStackListeners.finishBroadcast(); 612 } 613 registerDockedStackListener(IDockedStackListener listener)614 void registerDockedStackListener(IDockedStackListener listener) { 615 mDockedStackListeners.register(listener); 616 notifyDockedDividerVisibilityChanged(wasVisible()); 617 notifyDockedStackExistsChanged( 618 mDisplayContent.getSplitScreenPrimaryStackIgnoringVisibility() != null); 619 notifyDockedStackMinimizedChanged(mMinimizedDock, false /* animate */, 620 isHomeStackResizable()); 621 notifyAdjustedForImeChanged(mAdjustedForIme, 0 /* animDuration */); 622 623 } 624 625 /** 626 * Shows a dim layer with {@param alpha} if {@param visible} is true and 627 * {@param targetWindowingMode} isn't 628 * {@link android.app.WindowConfiguration#WINDOWING_MODE_UNDEFINED} and there is a stack on the 629 * display in that windowing mode. 630 */ setResizeDimLayer(boolean visible, int targetWindowingMode, float alpha)631 void setResizeDimLayer(boolean visible, int targetWindowingMode, float alpha) { 632 // TODO: Maybe only allow split-screen windowing modes? 633 final TaskStack stack = targetWindowingMode != WINDOWING_MODE_UNDEFINED 634 ? mDisplayContent.getTopStackInWindowingMode(targetWindowingMode) 635 : null; 636 final TaskStack dockedStack = mDisplayContent.getSplitScreenPrimaryStack(); 637 boolean visibleAndValid = visible && stack != null && dockedStack != null; 638 639 // Ensure an old dim that was shown for the docked stack divider is removed so we don't end 640 // up with dim layers that can no longer be removed. 641 if (mDimmedStack != null && mDimmedStack != stack) { 642 mDimmedStack.stopDimming(); 643 mDimmedStack = null; 644 } 645 646 if (visibleAndValid) { 647 mDimmedStack = stack; 648 stack.dim(alpha); 649 } 650 if (!visibleAndValid && stack != null) { 651 mDimmedStack = null; 652 stack.stopDimming(); 653 } 654 } 655 656 /** 657 * @return The layer used for dimming the apps when dismissing docked/fullscreen stack. Just 658 * above all application surfaces. 659 */ getResizeDimLayer()660 private int getResizeDimLayer() { 661 return (mWindow != null) ? mWindow.mLayer - 1 : LAYER_OFFSET_DIM; 662 } 663 664 /** 665 * Notifies the docked stack divider controller of a visibility change that happens without 666 * an animation. 667 */ notifyAppVisibilityChanged()668 void notifyAppVisibilityChanged() { 669 checkMinimizeChanged(false /* animate */); 670 } 671 notifyAppTransitionStarting(ArraySet<AppWindowToken> openingApps, int appTransition)672 void notifyAppTransitionStarting(ArraySet<AppWindowToken> openingApps, int appTransition) { 673 final boolean wasMinimized = mMinimizedDock; 674 checkMinimizeChanged(true /* animate */); 675 676 // We were minimized, and now we are still minimized, but somebody is trying to launch an 677 // app in docked stack, better show recent apps so we actually get unminimized! However do 678 // not do this if keyguard is dismissed such as when the device is unlocking. This catches 679 // any case that was missed in ActivityStarter.postStartActivityUncheckedProcessing because 680 // we couldn't retrace the launch of the app in the docked stack to the launch from 681 // homescreen. 682 if (wasMinimized && mMinimizedDock && containsAppInDockedStack(openingApps) 683 && appTransition != TRANSIT_NONE && 684 !AppTransition.isKeyguardGoingAwayTransit(appTransition)) { 685 if (mService.mAtmInternal.isRecentsComponentHomeActivity(mService.mCurrentUserId)) { 686 // When the home activity is the recents component and we are already minimized, 687 // then there is nothing to do here since home is already visible 688 } else { 689 mService.showRecentApps(); 690 } 691 } 692 } 693 694 /** 695 * @return true if {@param apps} contains an activity in the docked stack, false otherwise. 696 */ containsAppInDockedStack(ArraySet<AppWindowToken> apps)697 private boolean containsAppInDockedStack(ArraySet<AppWindowToken> apps) { 698 for (int i = apps.size() - 1; i >= 0; i--) { 699 final AppWindowToken token = apps.valueAt(i); 700 if (token.getTask() != null && token.inSplitScreenPrimaryWindowingMode()) { 701 return true; 702 } 703 } 704 return false; 705 } 706 isMinimizedDock()707 boolean isMinimizedDock() { 708 return mMinimizedDock; 709 } 710 checkMinimizeChanged(boolean animate)711 void checkMinimizeChanged(boolean animate) { 712 if (mDisplayContent.getSplitScreenPrimaryStackIgnoringVisibility() == null) { 713 return; 714 } 715 final TaskStack homeStack = mDisplayContent.getHomeStack(); 716 if (homeStack == null) { 717 return; 718 } 719 final Task homeTask = homeStack.findHomeTask(); 720 if (homeTask == null || !isWithinDisplay(homeTask)) { 721 return; 722 } 723 724 // Do not minimize when dock is already minimized while keyguard is showing and not 725 // occluded such as unlocking the screen 726 if (mMinimizedDock && mService.mKeyguardOrAodShowingOnDefaultDisplay) { 727 return; 728 } 729 final TaskStack topSecondaryStack = mDisplayContent.getTopStackInWindowingMode( 730 WINDOWING_MODE_SPLIT_SCREEN_SECONDARY); 731 final RecentsAnimationController recentsAnim = mService.getRecentsAnimationController(); 732 final boolean minimizedForRecentsAnimation = recentsAnim != null && 733 recentsAnim.isSplitScreenMinimized(); 734 boolean homeVisible = homeTask.getTopVisibleAppToken() != null; 735 if (homeVisible && topSecondaryStack != null) { 736 // Home should only be considered visible if it is greater or equal to the top secondary 737 // stack in terms of z-order. 738 homeVisible = homeStack.compareTo(topSecondaryStack) >= 0; 739 } 740 setMinimizedDockedStack(homeVisible || minimizedForRecentsAnimation, animate); 741 } 742 isWithinDisplay(Task task)743 private boolean isWithinDisplay(Task task) { 744 task.getBounds(mTmpRect); 745 mDisplayContent.getBounds(mTmpRect2); 746 return mTmpRect.intersect(mTmpRect2); 747 } 748 749 /** 750 * Sets whether the docked stack is currently in a minimized state, i.e. all the tasks in the 751 * docked stack are heavily clipped so you can only see a minimal peek state. 752 * 753 * @param minimizedDock Whether the docked stack is currently minimized. 754 * @param animate Whether to animate the change. 755 */ setMinimizedDockedStack(boolean minimizedDock, boolean animate)756 private void setMinimizedDockedStack(boolean minimizedDock, boolean animate) { 757 final boolean wasMinimized = mMinimizedDock; 758 mMinimizedDock = minimizedDock; 759 if (minimizedDock == wasMinimized) { 760 return; 761 } 762 763 final boolean imeChanged = clearImeAdjustAnimation(); 764 boolean minimizedChange = false; 765 if (isHomeStackResizable()) { 766 notifyDockedStackMinimizedChanged(minimizedDock, animate, 767 true /* isHomeStackResizable */); 768 minimizedChange = true; 769 } else { 770 if (minimizedDock) { 771 if (animate) { 772 startAdjustAnimation(0f, 1f); 773 } else { 774 minimizedChange |= setMinimizedDockedStack(true); 775 } 776 } else { 777 if (animate) { 778 startAdjustAnimation(1f, 0f); 779 } else { 780 minimizedChange |= setMinimizedDockedStack(false); 781 } 782 } 783 } 784 if (imeChanged || minimizedChange) { 785 if (imeChanged && !minimizedChange) { 786 Slog.d(TAG, "setMinimizedDockedStack: IME adjust changed due to minimizing," 787 + " minimizedDock=" + minimizedDock 788 + " minimizedChange=" + minimizedChange); 789 } 790 mService.mWindowPlacerLocked.performSurfacePlacement(); 791 } 792 } 793 clearImeAdjustAnimation()794 private boolean clearImeAdjustAnimation() { 795 final boolean changed = mDisplayContent.clearImeAdjustAnimation(); 796 mAnimatingForIme = false; 797 return changed; 798 } 799 startAdjustAnimation(float from, float to)800 private void startAdjustAnimation(float from, float to) { 801 mAnimatingForMinimizedDockedStack = true; 802 mAnimationStarted = false; 803 mAnimationStart = from; 804 mAnimationTarget = to; 805 } 806 startImeAdjustAnimation( boolean adjustedForIme, boolean adjustedForDivider, WindowState imeWin)807 private void startImeAdjustAnimation( 808 boolean adjustedForIme, boolean adjustedForDivider, WindowState imeWin) { 809 810 // If we're not in an animation, the starting point depends on whether we're adjusted 811 // or not. If we're already in an animation, we start from where the current animation 812 // left off, so that the motion doesn't look discontinuous. 813 if (!mAnimatingForIme) { 814 mAnimationStart = mAdjustedForIme ? 1 : 0; 815 mDividerAnimationStart = mAdjustedForDivider ? 1 : 0; 816 mLastAnimationProgress = mAnimationStart; 817 mLastDividerProgress = mDividerAnimationStart; 818 } else { 819 mAnimationStart = mLastAnimationProgress; 820 mDividerAnimationStart = mLastDividerProgress; 821 } 822 mAnimatingForIme = true; 823 mAnimationStarted = false; 824 mAnimationTarget = adjustedForIme ? 1 : 0; 825 mDividerAnimationTarget = adjustedForDivider ? 1 : 0; 826 827 mDisplayContent.beginImeAdjustAnimation(); 828 829 // We put all tasks into drag resizing mode - wait until all of them have completed the 830 // drag resizing switch. 831 if (!mService.mWaitingForDrawn.isEmpty()) { 832 mService.mH.removeMessages(H.WAITING_FOR_DRAWN_TIMEOUT); 833 mService.mH.sendEmptyMessageDelayed(H.WAITING_FOR_DRAWN_TIMEOUT, 834 IME_ADJUST_DRAWN_TIMEOUT); 835 mAnimationStartDelayed = true; 836 if (imeWin != null) { 837 838 // There might be an old window delaying the animation start - clear it. 839 if (mDelayedImeWin != null) { 840 mDelayedImeWin.endDelayingAnimationStart(); 841 } 842 mDelayedImeWin = imeWin; 843 imeWin.startDelayingAnimationStart(); 844 } 845 846 // If we are already waiting for something to be drawn, clear out the old one so it 847 // still gets executed. 848 // TODO: Have a real system where we can wait on different windows to be drawn with 849 // different callbacks. 850 if (mService.mWaitingForDrawnCallback != null) { 851 mService.mWaitingForDrawnCallback.run(); 852 } 853 mService.mWaitingForDrawnCallback = () -> { 854 synchronized (mService.mGlobalLock) { 855 mAnimationStartDelayed = false; 856 if (mDelayedImeWin != null) { 857 mDelayedImeWin.endDelayingAnimationStart(); 858 } 859 // If the adjust status changed since this was posted, only notify 860 // the new states and don't animate. 861 long duration = 0; 862 if (mAdjustedForIme == adjustedForIme 863 && mAdjustedForDivider == adjustedForDivider) { 864 duration = IME_ADJUST_ANIM_DURATION; 865 } else { 866 Slog.w(TAG, "IME adjust changed while waiting for drawn:" 867 + " adjustedForIme=" + adjustedForIme 868 + " adjustedForDivider=" + adjustedForDivider 869 + " mAdjustedForIme=" + mAdjustedForIme 870 + " mAdjustedForDivider=" + mAdjustedForDivider); 871 } 872 notifyAdjustedForImeChanged( 873 mAdjustedForIme || mAdjustedForDivider, duration); 874 } 875 }; 876 } else { 877 notifyAdjustedForImeChanged( 878 adjustedForIme || adjustedForDivider, IME_ADJUST_ANIM_DURATION); 879 } 880 } 881 setMinimizedDockedStack(boolean minimized)882 private boolean setMinimizedDockedStack(boolean minimized) { 883 final TaskStack stack = mDisplayContent.getSplitScreenPrimaryStackIgnoringVisibility(); 884 notifyDockedStackMinimizedChanged(minimized, false /* animate */, isHomeStackResizable()); 885 return stack != null && stack.setAdjustedForMinimizedDock(minimized ? 1f : 0f); 886 } 887 isAnimationMaximizing()888 private boolean isAnimationMaximizing() { 889 return mAnimationTarget == 0f; 890 } 891 animate(long now)892 public boolean animate(long now) { 893 if (mWindow == null) { 894 return false; 895 } 896 if (mAnimatingForMinimizedDockedStack) { 897 return animateForMinimizedDockedStack(now); 898 } else if (mAnimatingForIme && !mDisplayContent.mAppTransition.isRunning()) { 899 // To prevent task stack resize animation may flicking when playing app transition 900 // animation & IME window enter animation in parallel, make sure app transition is done 901 // and then start to animate for IME. 902 return animateForIme(now); 903 } 904 return false; 905 } 906 animateForIme(long now)907 private boolean animateForIme(long now) { 908 if (!mAnimationStarted || mAnimationStartDelayed) { 909 mAnimationStarted = true; 910 mAnimationStartTime = now; 911 mAnimationDuration = (long) 912 (IME_ADJUST_ANIM_DURATION * mService.getWindowAnimationScaleLocked()); 913 } 914 float t = Math.min(1f, (float) (now - mAnimationStartTime) / mAnimationDuration); 915 t = (mAnimationTarget == 1f ? IME_ADJUST_ENTRY_INTERPOLATOR : TOUCH_RESPONSE_INTERPOLATOR) 916 .getInterpolation(t); 917 final boolean updated = 918 mDisplayContent.animateForIme(t, mAnimationTarget, mDividerAnimationTarget); 919 if (updated) { 920 mService.mWindowPlacerLocked.performSurfacePlacement(); 921 } 922 if (t >= 1.0f) { 923 mLastAnimationProgress = mAnimationTarget; 924 mLastDividerProgress = mDividerAnimationTarget; 925 mAnimatingForIme = false; 926 return false; 927 } else { 928 return true; 929 } 930 } 931 animateForMinimizedDockedStack(long now)932 private boolean animateForMinimizedDockedStack(long now) { 933 final TaskStack stack = mDisplayContent.getSplitScreenPrimaryStackIgnoringVisibility(); 934 if (!mAnimationStarted) { 935 mAnimationStarted = true; 936 mAnimationStartTime = now; 937 notifyDockedStackMinimizedChanged(mMinimizedDock, true /* animate */, 938 isHomeStackResizable() /* isHomeStackResizable */); 939 } 940 float t = Math.min(1f, (float) (now - mAnimationStartTime) / mAnimationDuration); 941 t = (isAnimationMaximizing() ? TOUCH_RESPONSE_INTERPOLATOR : mMinimizedDockInterpolator) 942 .getInterpolation(t); 943 if (stack != null) { 944 if (stack.setAdjustedForMinimizedDock(getMinimizeAmount(stack, t))) { 945 mService.mWindowPlacerLocked.performSurfacePlacement(); 946 } 947 } 948 if (t >= 1.0f) { 949 mAnimatingForMinimizedDockedStack = false; 950 return false; 951 } else { 952 return true; 953 } 954 } 955 getInterpolatedAnimationValue(float t)956 float getInterpolatedAnimationValue(float t) { 957 return t * mAnimationTarget + (1 - t) * mAnimationStart; 958 } 959 getInterpolatedDividerValue(float t)960 float getInterpolatedDividerValue(float t) { 961 return t * mDividerAnimationTarget + (1 - t) * mDividerAnimationStart; 962 } 963 964 /** 965 * Gets the amount how much to minimize a stack depending on the interpolated fraction t. 966 */ getMinimizeAmount(TaskStack stack, float t)967 private float getMinimizeAmount(TaskStack stack, float t) { 968 final float naturalAmount = getInterpolatedAnimationValue(t); 969 if (isAnimationMaximizing()) { 970 return adjustMaximizeAmount(stack, t, naturalAmount); 971 } else { 972 return naturalAmount; 973 } 974 } 975 976 /** 977 * When maximizing the stack during a clip reveal transition, this adjusts the minimize amount 978 * during the transition such that the edge of the clip reveal rect is met earlier in the 979 * transition so we don't create a visible "hole", but only if both the clip reveal and the 980 * docked stack divider start from about the same portion on the screen. 981 */ adjustMaximizeAmount(TaskStack stack, float t, float naturalAmount)982 private float adjustMaximizeAmount(TaskStack stack, float t, float naturalAmount) { 983 if (mMaximizeMeetFraction == 1f) { 984 return naturalAmount; 985 } 986 final int minimizeDistance = stack.getMinimizeDistance(); 987 final float startPrime = mDisplayContent.mAppTransition.getLastClipRevealMaxTranslation() 988 / (float) minimizeDistance; 989 final float amountPrime = t * mAnimationTarget + (1 - t) * startPrime; 990 final float t2 = Math.min(t / mMaximizeMeetFraction, 1); 991 return amountPrime * t2 + naturalAmount * (1 - t2); 992 } 993 994 /** 995 * Retrieves the animation fraction at which the docked stack has to meet the clip reveal 996 * edge. See {@link #adjustMaximizeAmount}. 997 */ getClipRevealMeetFraction(TaskStack stack)998 private float getClipRevealMeetFraction(TaskStack stack) { 999 if (!isAnimationMaximizing() || stack == null || 1000 !mDisplayContent.mAppTransition.hadClipRevealAnimation()) { 1001 return 1f; 1002 } 1003 final int minimizeDistance = stack.getMinimizeDistance(); 1004 final float fraction = Math.abs(mDisplayContent.mAppTransition 1005 .getLastClipRevealMaxTranslation()) / (float) minimizeDistance; 1006 final float t = Math.max(0, Math.min(1, (fraction - CLIP_REVEAL_MEET_FRACTION_MIN) 1007 / (CLIP_REVEAL_MEET_FRACTION_MAX - CLIP_REVEAL_MEET_FRACTION_MIN))); 1008 return CLIP_REVEAL_MEET_EARLIEST 1009 + (1 - t) * (CLIP_REVEAL_MEET_LAST - CLIP_REVEAL_MEET_EARLIEST); 1010 } 1011 toShortString()1012 public String toShortString() { 1013 return TAG; 1014 } 1015 getWindow()1016 WindowState getWindow() { 1017 return mWindow; 1018 } 1019 dump(String prefix, PrintWriter pw)1020 void dump(String prefix, PrintWriter pw) { 1021 pw.println(prefix + "DockedStackDividerController"); 1022 pw.println(prefix + " mLastVisibility=" + mLastVisibility); 1023 pw.println(prefix + " mMinimizedDock=" + mMinimizedDock); 1024 pw.println(prefix + " mAdjustedForIme=" + mAdjustedForIme); 1025 pw.println(prefix + " mAdjustedForDivider=" + mAdjustedForDivider); 1026 } 1027 writeToProto(ProtoOutputStream proto, long fieldId)1028 void writeToProto(ProtoOutputStream proto, long fieldId) { 1029 final long token = proto.start(fieldId); 1030 proto.write(MINIMIZED_DOCK, mMinimizedDock); 1031 proto.end(token); 1032 } 1033 } 1034