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