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