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