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 17package com.android.server.wm; 18 19import static android.app.ActivityManager.StackId.DOCKED_STACK_ID; 20import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID; 21import static android.app.ActivityManager.StackId.INVALID_STACK_ID; 22import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; 23import static android.content.res.Configuration.ORIENTATION_PORTRAIT; 24import static android.view.Surface.ROTATION_270; 25import static android.view.Surface.ROTATION_90; 26import static android.view.WindowManager.DOCKED_BOTTOM; 27import static android.view.WindowManager.DOCKED_LEFT; 28import static android.view.WindowManager.DOCKED_RIGHT; 29import static android.view.WindowManager.DOCKED_TOP; 30import static com.android.server.wm.AppTransition.DEFAULT_APP_TRANSITION_DURATION; 31import static com.android.server.wm.AppTransition.TOUCH_RESPONSE_INTERPOLATOR; 32import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; 33import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; 34import static com.android.server.wm.WindowManagerService.H.NOTIFY_DOCKED_STACK_MINIMIZED_CHANGED; 35 36import android.content.Context; 37import android.content.res.Configuration; 38import android.graphics.Rect; 39import android.os.RemoteCallbackList; 40import android.os.RemoteException; 41import android.util.Slog; 42import android.view.DisplayInfo; 43import android.view.IDockedStackListener; 44import android.view.SurfaceControl; 45import android.view.animation.AnimationUtils; 46import android.view.animation.Interpolator; 47import android.view.animation.PathInterpolator; 48import android.view.inputmethod.InputMethodManagerInternal; 49 50import com.android.internal.policy.DividerSnapAlgorithm; 51import com.android.internal.policy.DockedDividerUtils; 52import com.android.server.LocalServices; 53import com.android.server.wm.DimLayer.DimLayerUser; 54import com.android.server.wm.WindowManagerService.H; 55 56import java.io.PrintWriter; 57import java.util.ArrayList; 58 59/** 60 * Keeps information about the docked stack divider. 61 */ 62public 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 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 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 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 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 231 void onConfigurationChanged() { 232 loadDimens(); 233 } 234 235 boolean isResizing() { 236 return mResizing; 237 } 238 239 int getContentWidth() { 240 return mDividerWindowWidth - 2 * mDividerInsets; 241 } 242 243 int getContentInsets() { 244 return mDividerInsets; 245 } 246 247 int getContentWidthInactive() { 248 return mDividerWindowWidthInactive; 249 } 250 251 void setResizing(boolean resizing) { 252 if (mResizing != resizing) { 253 mResizing = resizing; 254 resetDragResizingChangeReported(); 255 } 256 } 257 258 void setTouchRegion(Rect touchRegion) { 259 mTouchRegion.set(touchRegion); 260 } 261 262 void getTouchRegion(Rect outRegion) { 263 outRegion.set(mTouchRegion); 264 outRegion.offset(mWindow.getFrameLw().left, mWindow.getFrameLw().top); 265 } 266 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 274 void setWindow(WindowState window) { 275 mWindow = window; 276 reevaluateVisibility(false); 277 } 278 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 297 boolean wasVisible() { 298 return mLastVisibility; 299 } 300 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 318 int getImeHeightAdjustedFor() { 319 return mImeHeight; 320 } 321 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 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 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 { 392 setMinimizedDockedStack(false); 393 } 394 } 395 396 /** 397 * Resets the state that IME hide has been requested. See {@link #isImeHideRequested}. 398 */ 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 */ 410 boolean isImeHideRequested() { 411 return mImeHideRequested; 412 } 413 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 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 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 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 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 */ 491 void notifyAppVisibilityChanged() { 492 checkMinimizeChanged(false /* animate */); 493 } 494 495 void notifyAppTransitionStarting() { 496 checkMinimizeChanged(true /* animate */); 497 } 498 499 boolean isMinimizedDock() { 500 return mMinimizedDock; 501 } 502 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 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 */ 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 clearImeAdjustAnimation(); 546 if (minimizedDock) { 547 if (animate) { 548 startAdjustAnimation(0f, 1f); 549 } else { 550 setMinimizedDockedStack(true); 551 } 552 } else { 553 if (animate) { 554 startAdjustAnimation(1f, 0f); 555 } else { 556 setMinimizedDockedStack(false); 557 } 558 } 559 } 560 561 private void clearImeAdjustAnimation() { 562 final ArrayList<TaskStack> stacks = mDisplayContent.getStacks(); 563 for (int i = stacks.size() - 1; i >= 0; --i) { 564 final TaskStack stack = stacks.get(i); 565 if (stack != null && stack.isAdjustedForIme()) { 566 stack.resetAdjustedForIme(true /* adjustBoundsNow */); 567 } 568 } 569 mAnimatingForIme = false; 570 } 571 572 private void startAdjustAnimation(float from, float to) { 573 mAnimatingForMinimizedDockedStack = true; 574 mAnimationStarted = false; 575 mAnimationStart = from; 576 mAnimationTarget = to; 577 } 578 579 private void startImeAdjustAnimation( 580 boolean adjustedForIme, boolean adjustedForDivider, WindowState imeWin) { 581 582 // If we're not in an animation, the starting point depends on whether we're adjusted 583 // or not. If we're already in an animation, we start from where the current animation 584 // left off, so that the motion doesn't look discontinuous. 585 if (!mAnimatingForIme) { 586 mAnimationStart = mAdjustedForIme ? 1 : 0; 587 mDividerAnimationStart = mAdjustedForDivider ? 1 : 0; 588 mLastAnimationProgress = mAnimationStart; 589 mLastDividerProgress = mDividerAnimationStart; 590 } else { 591 mAnimationStart = mLastAnimationProgress; 592 mDividerAnimationStart = mLastDividerProgress; 593 } 594 mAnimatingForIme = true; 595 mAnimationStarted = false; 596 mAnimationTarget = adjustedForIme ? 1 : 0; 597 mDividerAnimationTarget = adjustedForDivider ? 1 : 0; 598 599 final ArrayList<TaskStack> stacks = mDisplayContent.getStacks(); 600 for (int i = stacks.size() - 1; i >= 0; --i) { 601 final TaskStack stack = stacks.get(i); 602 if (stack.isVisibleLocked() && stack.isAdjustedForIme()) { 603 stack.beginImeAdjustAnimation(); 604 } 605 } 606 607 // We put all tasks into drag resizing mode - wait until all of them have completed the 608 // drag resizing switch. 609 if (!mService.mWaitingForDrawn.isEmpty()) { 610 mService.mH.removeMessages(H.WAITING_FOR_DRAWN_TIMEOUT); 611 mService.mH.sendEmptyMessageDelayed(H.WAITING_FOR_DRAWN_TIMEOUT, 612 IME_ADJUST_DRAWN_TIMEOUT); 613 mAnimationStartDelayed = true; 614 if (imeWin != null) { 615 616 // There might be an old window delaying the animation start - clear it. 617 if (mDelayedImeWin != null) { 618 mDelayedImeWin.mWinAnimator.endDelayingAnimationStart(); 619 } 620 mDelayedImeWin = imeWin; 621 imeWin.mWinAnimator.startDelayingAnimationStart(); 622 } 623 mService.mWaitingForDrawnCallback = () -> { 624 mAnimationStartDelayed = false; 625 if (mDelayedImeWin != null) { 626 mDelayedImeWin.mWinAnimator.endDelayingAnimationStart(); 627 } 628 notifyAdjustedForImeChanged( 629 adjustedForIme || adjustedForDivider, IME_ADJUST_ANIM_DURATION); 630 }; 631 } else { 632 notifyAdjustedForImeChanged( 633 adjustedForIme || adjustedForDivider, IME_ADJUST_ANIM_DURATION); 634 } 635 } 636 637 private void setMinimizedDockedStack(boolean minimized) { 638 final TaskStack stack = mDisplayContent.getDockedStackVisibleForUserLocked(); 639 notifyDockedStackMinimizedChanged(minimized, 0); 640 if (stack == null) { 641 return; 642 } 643 if (stack.setAdjustedForMinimizedDock(minimized ? 1f : 0f)) { 644 mService.mWindowPlacerLocked.performSurfacePlacement(); 645 } 646 } 647 648 private boolean isAnimationMaximizing() { 649 return mAnimationTarget == 0f; 650 } 651 652 public boolean animate(long now) { 653 if (mWindow == null) { 654 return false; 655 } 656 if (mAnimatingForMinimizedDockedStack) { 657 return animateForMinimizedDockedStack(now); 658 } else if (mAnimatingForIme) { 659 return animateForIme(now); 660 } else { 661 if (mDimLayer != null && mDimLayer.isDimming()) { 662 mDimLayer.setLayer(mService.mLayersController.getResizeDimLayer()); 663 } 664 return false; 665 } 666 } 667 668 private boolean animateForIme(long now) { 669 if (!mAnimationStarted || mAnimationStartDelayed) { 670 mAnimationStarted = true; 671 mAnimationStartTime = now; 672 mAnimationDuration = (long) 673 (IME_ADJUST_ANIM_DURATION * mService.getWindowAnimationScaleLocked()); 674 } 675 float t = Math.min(1f, (float) (now - mAnimationStartTime) / mAnimationDuration); 676 t = (mAnimationTarget == 1f ? IME_ADJUST_ENTRY_INTERPOLATOR : TOUCH_RESPONSE_INTERPOLATOR) 677 .getInterpolation(t); 678 final ArrayList<TaskStack> stacks = mDisplayContent.getStacks(); 679 boolean updated = false; 680 for (int i = stacks.size() - 1; i >= 0; --i) { 681 final TaskStack stack = stacks.get(i); 682 if (stack != null && stack.isAdjustedForIme()) { 683 if (t >= 1f && mAnimationTarget == 0f && mDividerAnimationTarget == 0f) { 684 stack.resetAdjustedForIme(true /* adjustBoundsNow */); 685 updated = true; 686 } else { 687 mLastAnimationProgress = getInterpolatedAnimationValue(t); 688 mLastDividerProgress = getInterpolatedDividerValue(t); 689 updated |= stack.updateAdjustForIme( 690 mLastAnimationProgress, 691 mLastDividerProgress, 692 false /* force */); 693 } 694 if (t >= 1f) { 695 stack.endImeAdjustAnimation(); 696 } 697 } 698 } 699 if (updated) { 700 mService.mWindowPlacerLocked.performSurfacePlacement(); 701 } 702 if (t >= 1.0f) { 703 mLastAnimationProgress = mAnimationTarget; 704 mLastDividerProgress = mDividerAnimationTarget; 705 mAnimatingForIme = false; 706 return false; 707 } else { 708 return true; 709 } 710 } 711 712 private boolean animateForMinimizedDockedStack(long now) { 713 final TaskStack stack = mService.mStackIdToStack.get(DOCKED_STACK_ID); 714 if (!mAnimationStarted) { 715 mAnimationStarted = true; 716 mAnimationStartTime = now; 717 final long transitionDuration = isAnimationMaximizing() 718 ? mService.mAppTransition.getLastClipRevealTransitionDuration() 719 : DEFAULT_APP_TRANSITION_DURATION; 720 mAnimationDuration = (long) 721 (transitionDuration * mService.getTransitionAnimationScaleLocked()); 722 mMaximizeMeetFraction = getClipRevealMeetFraction(stack); 723 notifyDockedStackMinimizedChanged(mMinimizedDock, 724 (long) (mAnimationDuration * mMaximizeMeetFraction)); 725 } 726 float t = Math.min(1f, (float) (now - mAnimationStartTime) / mAnimationDuration); 727 t = (isAnimationMaximizing() ? TOUCH_RESPONSE_INTERPOLATOR : mMinimizedDockInterpolator) 728 .getInterpolation(t); 729 if (stack != null) { 730 if (stack.setAdjustedForMinimizedDock(getMinimizeAmount(stack, t))) { 731 mService.mWindowPlacerLocked.performSurfacePlacement(); 732 } 733 } 734 if (t >= 1.0f) { 735 mAnimatingForMinimizedDockedStack = false; 736 return false; 737 } else { 738 return true; 739 } 740 } 741 742 private float getInterpolatedAnimationValue(float t) { 743 return t * mAnimationTarget + (1 - t) * mAnimationStart; 744 } 745 746 private float getInterpolatedDividerValue(float t) { 747 return t * mDividerAnimationTarget + (1 - t) * mDividerAnimationStart; 748 } 749 750 /** 751 * Gets the amount how much to minimize a stack depending on the interpolated fraction t. 752 */ 753 private float getMinimizeAmount(TaskStack stack, float t) { 754 final float naturalAmount = getInterpolatedAnimationValue(t); 755 if (isAnimationMaximizing()) { 756 return adjustMaximizeAmount(stack, t, naturalAmount); 757 } else { 758 return naturalAmount; 759 } 760 } 761 762 /** 763 * When maximizing the stack during a clip reveal transition, this adjusts the minimize amount 764 * during the transition such that the edge of the clip reveal rect is met earlier in the 765 * transition so we don't create a visible "hole", but only if both the clip reveal and the 766 * docked stack divider start from about the same portion on the screen. 767 */ 768 private float adjustMaximizeAmount(TaskStack stack, float t, float naturalAmount) { 769 if (mMaximizeMeetFraction == 1f) { 770 return naturalAmount; 771 } 772 final int minimizeDistance = stack.getMinimizeDistance(); 773 float startPrime = mService.mAppTransition.getLastClipRevealMaxTranslation() 774 / (float) minimizeDistance; 775 final float amountPrime = t * mAnimationTarget + (1 - t) * startPrime; 776 final float t2 = Math.min(t / mMaximizeMeetFraction, 1); 777 return amountPrime * t2 + naturalAmount * (1 - t2); 778 } 779 780 /** 781 * Retrieves the animation fraction at which the docked stack has to meet the clip reveal 782 * edge. See {@link #adjustMaximizeAmount}. 783 */ 784 private float getClipRevealMeetFraction(TaskStack stack) { 785 if (!isAnimationMaximizing() || stack == null || 786 !mService.mAppTransition.hadClipRevealAnimation()) { 787 return 1f; 788 } 789 final int minimizeDistance = stack.getMinimizeDistance(); 790 final float fraction = Math.abs(mService.mAppTransition.getLastClipRevealMaxTranslation()) 791 / (float) minimizeDistance; 792 final float t = Math.max(0, Math.min(1, (fraction - CLIP_REVEAL_MEET_FRACTION_MIN) 793 / (CLIP_REVEAL_MEET_FRACTION_MAX - CLIP_REVEAL_MEET_FRACTION_MIN))); 794 return CLIP_REVEAL_MEET_EARLIEST 795 + (1 - t) * (CLIP_REVEAL_MEET_LAST - CLIP_REVEAL_MEET_EARLIEST); 796 } 797 798 @Override 799 public boolean dimFullscreen() { 800 return false; 801 } 802 803 @Override 804 public DisplayInfo getDisplayInfo() { 805 return mDisplayContent.getDisplayInfo(); 806 } 807 808 @Override 809 public void getDimBounds(Rect outBounds) { 810 // This dim layer user doesn't need this. 811 } 812 813 @Override 814 public String toShortString() { 815 return TAG; 816 } 817 818 WindowState getWindow() { 819 return mWindow; 820 } 821 822 void dump(String prefix, PrintWriter pw) { 823 pw.println(prefix + "DockedStackDividerController"); 824 pw.println(prefix + " mLastVisibility=" + mLastVisibility); 825 pw.println(prefix + " mMinimizedDock=" + mMinimizedDock); 826 pw.println(prefix + " mAdjustedForIme=" + mAdjustedForIme); 827 pw.println(prefix + " mAdjustedForDivider=" + mAdjustedForDivider); 828 if (mDimLayer.isDimming()) { 829 pw.println(prefix + " Dim layer is dimming: "); 830 mDimLayer.printTo(prefix + " ", pw); 831 } 832 } 833} 834