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