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