DockedStackDividerController.java revision 698e7634aa2ced554f564f588c2e878fb35757a3
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.INVALID_STACK_ID; 21import static android.view.WindowManager.DOCKED_BOTTOM; 22import static android.view.WindowManager.DOCKED_LEFT; 23import static android.view.WindowManager.DOCKED_RIGHT; 24import static android.view.WindowManager.DOCKED_TOP; 25import static com.android.server.wm.AppTransition.DEFAULT_APP_TRANSITION_DURATION; 26import static com.android.server.wm.AppTransition.TOUCH_RESPONSE_INTERPOLATOR; 27import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; 28import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; 29 30import android.content.Context; 31import android.graphics.Rect; 32import android.os.RemoteCallbackList; 33import android.os.RemoteException; 34import android.util.ArraySet; 35import android.util.Slog; 36import android.view.DisplayInfo; 37import android.view.IDockedStackListener; 38import android.view.SurfaceControl; 39import android.view.animation.AnimationUtils; 40import android.view.animation.Interpolator; 41import android.view.animation.PathInterpolator; 42 43import com.android.server.wm.DimLayer.DimLayerUser; 44 45import java.util.ArrayList; 46 47/** 48 * Keeps information about the docked stack divider. 49 */ 50public class DockedStackDividerController implements DimLayerUser { 51 52 private static final String TAG = TAG_WITH_CLASS_NAME ? "DockedStackDividerController" : TAG_WM; 53 54 /** 55 * The fraction during the maximize/clip reveal animation the divider meets the edge of the clip 56 * revealing surface at the earliest. 57 */ 58 private static final float CLIP_REVEAL_MEET_EARLIEST = 0.6f; 59 60 /** 61 * The fraction during the maximize/clip reveal animation the divider meets the edge of the clip 62 * revealing surface at the latest. 63 */ 64 private static final float CLIP_REVEAL_MEET_LAST = 1f; 65 66 /** 67 * If the app translates at least CLIP_REVEAL_MEET_FRACTION_MIN * minimize distance, we start 68 * meet somewhere between {@link #CLIP_REVEAL_MEET_LAST} and {@link #CLIP_REVEAL_MEET_EARLIEST}. 69 */ 70 private static final float CLIP_REVEAL_MEET_FRACTION_MIN = 0.4f; 71 72 /** 73 * If the app translates equals or more than CLIP_REVEAL_MEET_FRACTION_MIN * minimize distance, 74 * we meet at {@link #CLIP_REVEAL_MEET_EARLIEST}. 75 */ 76 private static final float CLIP_REVEAL_MEET_FRACTION_MAX = 0.8f; 77 78 private static final Interpolator IME_ADJUST_ENTRY_INTERPOLATOR = 79 new PathInterpolator(0.1f, 0f, 0.1f, 1f); 80 81 private static final long IME_ADJUST_ANIM_DURATION = 280; 82 83 private final WindowManagerService mService; 84 private final DisplayContent mDisplayContent; 85 private int mDividerWindowWidth; 86 private int mDividerInsets; 87 private boolean mResizing; 88 private WindowState mWindow; 89 private final Rect mTmpRect = new Rect(); 90 private final Rect mTmpRect2 = new Rect(); 91 private final Rect mLastRect = new Rect(); 92 private boolean mLastVisibility = false; 93 private final RemoteCallbackList<IDockedStackListener> mDockedStackListeners 94 = new RemoteCallbackList<>(); 95 private final DimLayer mDimLayer; 96 97 private boolean mMinimizedDock; 98 private boolean mAnimatingForMinimizedDockedStack; 99 private boolean mAnimationStarted; 100 private long mAnimationStartTime; 101 private float mAnimationStart; 102 private float mAnimationTarget; 103 private long mAnimationDuration; 104 private final Interpolator mMinimizedDockInterpolator; 105 private float mMaximizeMeetFraction; 106 private final Rect mTouchRegion = new Rect(); 107 private boolean mAnimatingForIme; 108 private boolean mAdjustedForIme; 109 110 DockedStackDividerController(WindowManagerService service, DisplayContent displayContent) { 111 mService = service; 112 mDisplayContent = displayContent; 113 final Context context = service.mContext; 114 mDimLayer = new DimLayer(displayContent.mService, this, displayContent.getDisplayId(), 115 "DockedStackDim"); 116 mMinimizedDockInterpolator = AnimationUtils.loadInterpolator( 117 context, android.R.interpolator.fast_out_slow_in); 118 loadDimens(); 119 } 120 121 private void loadDimens() { 122 final Context context = mService.mContext; 123 mDividerWindowWidth = context.getResources().getDimensionPixelSize( 124 com.android.internal.R.dimen.docked_stack_divider_thickness); 125 mDividerInsets = context.getResources().getDimensionPixelSize( 126 com.android.internal.R.dimen.docked_stack_divider_insets); 127 } 128 129 void onConfigurationChanged() { 130 loadDimens(); 131 } 132 133 boolean isResizing() { 134 return mResizing; 135 } 136 137 int getContentWidth() { 138 return mDividerWindowWidth - 2 * mDividerInsets; 139 } 140 141 int getContentInsets() { 142 return mDividerInsets; 143 } 144 145 void setResizing(boolean resizing) { 146 if (mResizing != resizing) { 147 mResizing = resizing; 148 resetDragResizingChangeReported(); 149 } 150 } 151 152 void setTouchRegion(Rect touchRegion) { 153 mTouchRegion.set(touchRegion); 154 } 155 156 void getTouchRegion(Rect outRegion) { 157 outRegion.set(mTouchRegion); 158 outRegion.offset(mWindow.getFrameLw().left, mWindow.getFrameLw().top); 159 } 160 161 private void resetDragResizingChangeReported() { 162 final WindowList windowList = mDisplayContent.getWindowList(); 163 for (int i = windowList.size() - 1; i >= 0; i--) { 164 windowList.get(i).resetDragResizingChangeReported(); 165 } 166 } 167 168 void setWindow(WindowState window) { 169 mWindow = window; 170 reevaluateVisibility(false); 171 } 172 173 void reevaluateVisibility(boolean force) { 174 if (mWindow == null) { 175 return; 176 } 177 TaskStack stack = mDisplayContent.mService.mStackIdToStack.get(DOCKED_STACK_ID); 178 179 // If the stack is invisible, we policy force hide it in WindowAnimator.shouldForceHide 180 final boolean visible = stack != null; 181 if (mLastVisibility == visible && !force) { 182 return; 183 } 184 mLastVisibility = visible; 185 notifyDockedDividerVisibilityChanged(visible); 186 if (!visible) { 187 setResizeDimLayer(false, INVALID_STACK_ID, 0f); 188 } 189 } 190 191 boolean wasVisible() { 192 return mLastVisibility; 193 } 194 195 void setAdjustedForIme(boolean adjusted, boolean animate) { 196 if (mAdjustedForIme != adjusted) { 197 mAdjustedForIme = adjusted; 198 if (animate) { 199 startImeAdjustAnimation(adjusted ? 0 : 1, adjusted ? 1 : 0); 200 } 201 notifyAdjustedForImeChanged(adjusted, animate ? IME_ADJUST_ANIM_DURATION : 0); 202 } 203 } 204 205 void positionDockedStackedDivider(Rect frame) { 206 TaskStack stack = mDisplayContent.getDockedStackLocked(); 207 if (stack == null) { 208 // Unfortunately we might end up with still having a divider, even though the underlying 209 // stack was already removed. This is because we are on AM thread and the removal of the 210 // divider was deferred to WM thread and hasn't happened yet. In that case let's just 211 // keep putting it in the same place it was before the stack was removed to have 212 // continuity and prevent it from jumping to the center. It will get hidden soon. 213 frame.set(mLastRect); 214 return; 215 } else { 216 stack.getDimBounds(mTmpRect); 217 } 218 int side = stack.getDockSide(); 219 switch (side) { 220 case DOCKED_LEFT: 221 frame.set(mTmpRect.right - mDividerInsets, frame.top, 222 mTmpRect.right + frame.width() - mDividerInsets, frame.bottom); 223 break; 224 case DOCKED_TOP: 225 frame.set(frame.left, mTmpRect.bottom - mDividerInsets, 226 mTmpRect.right, mTmpRect.bottom + frame.height() - mDividerInsets); 227 break; 228 case DOCKED_RIGHT: 229 frame.set(mTmpRect.left - frame.width() + mDividerInsets, frame.top, 230 mTmpRect.left + mDividerInsets, frame.bottom); 231 break; 232 case DOCKED_BOTTOM: 233 frame.set(frame.left, mTmpRect.top - frame.height() + mDividerInsets, 234 frame.right, mTmpRect.top + mDividerInsets); 235 break; 236 } 237 mLastRect.set(frame); 238 } 239 240 void notifyDockedDividerVisibilityChanged(boolean visible) { 241 final int size = mDockedStackListeners.beginBroadcast(); 242 for (int i = 0; i < size; ++i) { 243 final IDockedStackListener listener = mDockedStackListeners.getBroadcastItem(i); 244 try { 245 listener.onDividerVisibilityChanged(visible); 246 } catch (RemoteException e) { 247 Slog.e(TAG_WM, "Error delivering divider visibility changed event.", e); 248 } 249 } 250 mDockedStackListeners.finishBroadcast(); 251 } 252 253 void notifyDockedStackExistsChanged(boolean exists) { 254 final int size = mDockedStackListeners.beginBroadcast(); 255 for (int i = 0; i < size; ++i) { 256 final IDockedStackListener listener = mDockedStackListeners.getBroadcastItem(i); 257 try { 258 listener.onDockedStackExistsChanged(exists); 259 } catch (RemoteException e) { 260 Slog.e(TAG_WM, "Error delivering docked stack exists changed event.", e); 261 } 262 } 263 mDockedStackListeners.finishBroadcast(); 264 if (!exists) { 265 setMinimizedDockedStack(false); 266 } 267 } 268 269 void notifyDockedStackMinimizedChanged(boolean minimizedDock, long animDuration) { 270 final int size = mDockedStackListeners.beginBroadcast(); 271 for (int i = 0; i < size; ++i) { 272 final IDockedStackListener listener = mDockedStackListeners.getBroadcastItem(i); 273 try { 274 listener.onDockedStackMinimizedChanged(minimizedDock, animDuration); 275 } catch (RemoteException e) { 276 Slog.e(TAG_WM, "Error delivering minimized dock changed event.", e); 277 } 278 } 279 mDockedStackListeners.finishBroadcast(); 280 } 281 282 void notifyDockSideChanged(int newDockSide) { 283 final int size = mDockedStackListeners.beginBroadcast(); 284 for (int i = 0; i < size; ++i) { 285 final IDockedStackListener listener = mDockedStackListeners.getBroadcastItem(i); 286 try { 287 listener.onDockSideChanged(newDockSide); 288 } catch (RemoteException e) { 289 Slog.e(TAG_WM, "Error delivering dock side changed event.", e); 290 } 291 } 292 mDockedStackListeners.finishBroadcast(); 293 } 294 295 void notifyAdjustedForImeChanged(boolean adjustedForIme, long animDuration) { 296 final int size = mDockedStackListeners.beginBroadcast(); 297 for (int i = 0; i < size; ++i) { 298 final IDockedStackListener listener = mDockedStackListeners.getBroadcastItem(i); 299 try { 300 listener.onAdjustedForImeChanged(adjustedForIme, animDuration); 301 } catch (RemoteException e) { 302 Slog.e(TAG_WM, "Error delivering adjusted for ime changed event.", e); 303 } 304 } 305 mDockedStackListeners.finishBroadcast(); 306 } 307 308 void registerDockedStackListener(IDockedStackListener listener) { 309 mDockedStackListeners.register(listener); 310 notifyDockedDividerVisibilityChanged(wasVisible()); 311 notifyDockedStackExistsChanged( 312 mDisplayContent.mService.mStackIdToStack.get(DOCKED_STACK_ID) != null); 313 notifyDockedStackMinimizedChanged(mMinimizedDock, 0 /* animDuration */); 314 notifyAdjustedForImeChanged(mAdjustedForIme, 0 /* animDuration */); 315 316 } 317 318 void setResizeDimLayer(boolean visible, int targetStackId, float alpha) { 319 SurfaceControl.openTransaction(); 320 final TaskStack stack = mDisplayContent.mService.mStackIdToStack.get(targetStackId); 321 final TaskStack dockedStack = mDisplayContent.getDockedStackLocked(); 322 boolean visibleAndValid = visible && stack != null && dockedStack != null; 323 if (visibleAndValid) { 324 stack.getDimBounds(mTmpRect); 325 if (mTmpRect.height() > 0 && mTmpRect.width() > 0) { 326 mDimLayer.setBounds(mTmpRect); 327 mDimLayer.show(mDisplayContent.mService.mLayersController.getResizeDimLayer(), 328 alpha, 0 /* duration */); 329 } else { 330 visibleAndValid = false; 331 } 332 } 333 if (!visibleAndValid) { 334 mDimLayer.hide(); 335 } 336 SurfaceControl.closeTransaction(); 337 } 338 339 /** 340 * Notifies the docked stack divider controller of a visibility change that happens without 341 * an animation. 342 */ 343 void notifyAppVisibilityChanged(AppWindowToken wtoken, boolean visible) { 344 final Task task = wtoken.mTask; 345 if (!task.isHomeTask() || !task.isVisibleForUser()) { 346 return; 347 } 348 349 // If the stack is completely offscreen, this might just be an intermediate state when 350 // docking a task/launching recents at the same time, but home doesn't actually get 351 // visible after the state settles in. 352 if (isWithinDisplay(task) 353 && mDisplayContent.getDockedStackVisibleForUserLocked() != null) { 354 setMinimizedDockedStack(visible, false /* animate */); 355 } 356 } 357 358 void notifyAppTransitionStarting(ArraySet<AppWindowToken> openingApps, 359 ArraySet<AppWindowToken> closingApps) { 360 if (containsHomeTaskWithinDisplay(openingApps)) { 361 setMinimizedDockedStack(true /* minimized */, true /* animate */); 362 } else if (containsHomeTaskWithinDisplay(closingApps)) { 363 setMinimizedDockedStack(false /* minimized */, true /* animate */); 364 } 365 } 366 367 private boolean containsHomeTaskWithinDisplay(ArraySet<AppWindowToken> apps) { 368 for (int i = apps.size() - 1; i >= 0; i--) { 369 final Task task = apps.valueAt(i).mTask; 370 if (task != null && task.isHomeTask()) { 371 return isWithinDisplay(task); 372 } 373 } 374 return false; 375 } 376 377 private boolean isWithinDisplay(Task task) { 378 task.mStack.getBounds(mTmpRect); 379 mDisplayContent.getLogicalDisplayRect(mTmpRect2); 380 return mTmpRect.intersect(mTmpRect2); 381 } 382 383 /** 384 * Sets whether the docked stack is currently in a minimized state, i.e. all the tasks in the 385 * docked stack are heavily clipped so you can only see a minimal peek state. 386 * 387 * @param minimizedDock Whether the docked stack is currently minimized. 388 * @param animate Whether to animate the change. 389 */ 390 private void setMinimizedDockedStack(boolean minimizedDock, boolean animate) { 391 final boolean wasMinimized = mMinimizedDock; 392 mMinimizedDock = minimizedDock; 393 if (minimizedDock == wasMinimized 394 || mDisplayContent.getDockedStackVisibleForUserLocked() == null) { 395 return; 396 } 397 398 clearImeAdjustAnimation(); 399 if (minimizedDock) { 400 if (animate) { 401 startAdjustAnimation(0f, 1f); 402 } else { 403 setMinimizedDockedStack(true); 404 } 405 } else { 406 if (animate) { 407 startAdjustAnimation(1f, 0f); 408 } else { 409 setMinimizedDockedStack(false); 410 } 411 } 412 } 413 414 private void clearImeAdjustAnimation() { 415 final ArrayList<TaskStack> stacks = mDisplayContent.getStacks(); 416 for (int i = stacks.size() - 1; i >= 0; --i) { 417 final TaskStack stack = stacks.get(i); 418 if (stack != null && stack.isAdjustedForIme()) { 419 stack.resetAdjustedForIme(true /* adjustBoundsNow */); 420 } 421 } 422 mAnimatingForIme = false; 423 } 424 425 private void startAdjustAnimation(float from, float to) { 426 mAnimatingForMinimizedDockedStack = true; 427 mAnimationStarted = false; 428 mAnimationStart = from; 429 mAnimationTarget = to; 430 } 431 432 private void startImeAdjustAnimation(float from, float to) { 433 mAnimatingForIme = true; 434 mAnimationStarted = false; 435 mAnimationStart = from; 436 mAnimationTarget = to; 437 } 438 439 private void setMinimizedDockedStack(boolean minimized) { 440 final TaskStack stack = mDisplayContent.getDockedStackVisibleForUserLocked(); 441 notifyDockedStackMinimizedChanged(minimized, 0); 442 if (stack == null) { 443 return; 444 } 445 if (stack.setAdjustedForMinimizedDock(minimized ? 1f : 0f)) { 446 mService.mWindowPlacerLocked.performSurfacePlacement(); 447 } 448 } 449 450 private boolean isAnimationMaximizing() { 451 return mAnimationTarget == 0f; 452 } 453 454 public boolean animate(long now) { 455 if (mAnimatingForMinimizedDockedStack) { 456 return animateForMinimizedDockedStack(now); 457 } else if (mAnimatingForIme) { 458 return animateForIme(now); 459 } else { 460 return false; 461 } 462 } 463 464 private boolean animateForIme(long now) { 465 if (!mAnimationStarted) { 466 mAnimationStarted = true; 467 mAnimationStartTime = now; 468 mAnimationDuration = (long) 469 (IME_ADJUST_ANIM_DURATION * mService.getWindowAnimationScaleLocked()); 470 } 471 float t = Math.min(1f, (float) (now - mAnimationStartTime) / mAnimationDuration); 472 t = (mAnimationTarget == 1f ? IME_ADJUST_ENTRY_INTERPOLATOR : TOUCH_RESPONSE_INTERPOLATOR) 473 .getInterpolation(t); 474 final ArrayList<TaskStack> stacks = mDisplayContent.getStacks(); 475 boolean updated = false; 476 for (int i = stacks.size() - 1; i >= 0; --i) { 477 final TaskStack stack = stacks.get(i); 478 if (stack != null && stack.isAdjustedForIme()) { 479 if (t >= 1f && mAnimationTarget == 0f) { 480 stack.resetAdjustedForIme(true /* adjustBoundsNow */); 481 updated = true; 482 } else { 483 updated |= stack.updateAdjustForIme(getInterpolatedAnimationValue(t)); 484 } 485 } 486 } 487 if (updated) { 488 mService.mWindowPlacerLocked.performSurfacePlacement(); 489 } 490 if (t >= 1.0f) { 491 mAnimatingForIme = false; 492 return false; 493 } else { 494 return true; 495 } 496 } 497 498 private boolean animateForMinimizedDockedStack(long now) { 499 final TaskStack stack = mDisplayContent.getDockedStackVisibleForUserLocked(); 500 if (!mAnimationStarted) { 501 mAnimationStarted = true; 502 mAnimationStartTime = now; 503 final long transitionDuration = isAnimationMaximizing() 504 ? mService.mAppTransition.getLastClipRevealTransitionDuration() 505 : DEFAULT_APP_TRANSITION_DURATION; 506 mAnimationDuration = (long) 507 (transitionDuration * mService.getTransitionAnimationScaleLocked()); 508 mMaximizeMeetFraction = getClipRevealMeetFraction(stack); 509 notifyDockedStackMinimizedChanged(mMinimizedDock, 510 (long) (mAnimationDuration * mMaximizeMeetFraction)); 511 } 512 float t = Math.min(1f, (float) (now - mAnimationStartTime) / mAnimationDuration); 513 t = (isAnimationMaximizing() ? TOUCH_RESPONSE_INTERPOLATOR : mMinimizedDockInterpolator) 514 .getInterpolation(t); 515 if (stack != null) { 516 if (stack.setAdjustedForMinimizedDock(getMinimizeAmount(stack, t))) { 517 mService.mWindowPlacerLocked.performSurfacePlacement(); 518 } 519 } 520 if (t >= 1.0f) { 521 mAnimatingForMinimizedDockedStack = false; 522 return false; 523 } else { 524 return true; 525 } 526 } 527 528 private float getInterpolatedAnimationValue(float t) { 529 return t * mAnimationTarget + (1 - t) * mAnimationStart; 530 } 531 532 /** 533 * Gets the amount how much to minimize a stack depending on the interpolated fraction t. 534 */ 535 private float getMinimizeAmount(TaskStack stack, float t) { 536 final float naturalAmount = getInterpolatedAnimationValue(t); 537 if (isAnimationMaximizing()) { 538 return adjustMaximizeAmount(stack, t, naturalAmount); 539 } else { 540 return naturalAmount; 541 } 542 } 543 544 /** 545 * When maximizing the stack during a clip reveal transition, this adjusts the minimize amount 546 * during the transition such that the edge of the clip reveal rect is met earlier in the 547 * transition so we don't create a visible "hole", but only if both the clip reveal and the 548 * docked stack divider start from about the same portion on the screen. 549 */ 550 private float adjustMaximizeAmount(TaskStack stack, float t, float naturalAmount) { 551 if (mMaximizeMeetFraction == 1f) { 552 return naturalAmount; 553 } 554 final int minimizeDistance = stack.getMinimizeDistance(); 555 float startPrime = mService.mAppTransition.getLastClipRevealMaxTranslation() 556 / (float) minimizeDistance; 557 final float amountPrime = t * mAnimationTarget + (1 - t) * startPrime; 558 final float t2 = Math.min(t / mMaximizeMeetFraction, 1); 559 return amountPrime * t2 + naturalAmount * (1 - t2); 560 } 561 562 /** 563 * Retrieves the animation fraction at which the docked stack has to meet the clip reveal 564 * edge. See {@link #adjustMaximizeAmount}. 565 */ 566 private float getClipRevealMeetFraction(TaskStack stack) { 567 if (!isAnimationMaximizing() || stack == null || 568 !mService.mAppTransition.hadClipRevealAnimation()) { 569 return 1f; 570 } 571 final int minimizeDistance = stack.getMinimizeDistance(); 572 final float fraction = Math.abs(mService.mAppTransition.getLastClipRevealMaxTranslation()) 573 / (float) minimizeDistance; 574 final float t = Math.max(0, Math.min(1, (fraction - CLIP_REVEAL_MEET_FRACTION_MIN) 575 / (CLIP_REVEAL_MEET_FRACTION_MAX - CLIP_REVEAL_MEET_FRACTION_MIN))); 576 return CLIP_REVEAL_MEET_EARLIEST 577 + (1 - t) * (CLIP_REVEAL_MEET_LAST - CLIP_REVEAL_MEET_EARLIEST); 578 } 579 580 @Override 581 public boolean isFullscreen() { 582 return false; 583 } 584 585 @Override 586 public DisplayInfo getDisplayInfo() { 587 return mDisplayContent.getDisplayInfo(); 588 } 589 590 @Override 591 public void getDimBounds(Rect outBounds) { 592 // This dim layer user doesn't need this. 593 } 594 595 @Override 596 public String toShortString() { 597 return TAG; 598 } 599} 600