DividerView.java revision fc34b73aeff4a8bf84b86e015fa89f484e73ec09
1/* 2 * Copyright (C) 2015 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.systemui.stackdivider; 18 19import static android.view.PointerIcon.STYLE_HORIZONTAL_DOUBLE_ARROW; 20import static android.view.PointerIcon.STYLE_VERTICAL_DOUBLE_ARROW; 21 22import android.animation.Animator; 23import android.animation.AnimatorListenerAdapter; 24import android.animation.ValueAnimator; 25import android.animation.ValueAnimator.AnimatorUpdateListener; 26import android.annotation.Nullable; 27import android.app.ActivityManager.StackId; 28import android.content.Context; 29import android.content.res.Configuration; 30import android.graphics.Rect; 31import android.graphics.Region.Op; 32import android.hardware.display.DisplayManager; 33import android.os.Bundle; 34import android.util.AttributeSet; 35import android.view.Display; 36import android.view.DisplayInfo; 37import android.view.GestureDetector; 38import android.view.GestureDetector.OnDoubleTapListener; 39import android.view.GestureDetector.SimpleOnGestureListener; 40import android.view.MotionEvent; 41import android.view.PointerIcon; 42import android.view.VelocityTracker; 43import android.view.View; 44import android.view.View.OnTouchListener; 45import android.view.ViewConfiguration; 46import android.view.ViewTreeObserver.InternalInsetsInfo; 47import android.view.ViewTreeObserver.OnComputeInternalInsetsListener; 48import android.view.ViewTreeObserver.OnGlobalLayoutListener; 49import android.view.WindowInsets; 50import android.view.WindowManager; 51import android.view.accessibility.AccessibilityNodeInfo; 52import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; 53import android.view.animation.Interpolator; 54import android.view.animation.PathInterpolator; 55import android.widget.FrameLayout; 56 57import com.android.internal.policy.DividerSnapAlgorithm; 58import com.android.internal.policy.DividerSnapAlgorithm.SnapTarget; 59import com.android.internal.policy.DockedDividerUtils; 60import com.android.systemui.Interpolators; 61import com.android.systemui.R; 62import com.android.systemui.recents.Recents; 63import com.android.systemui.recents.events.EventBus; 64import com.android.systemui.recents.events.activity.DockedTopTaskEvent; 65import com.android.systemui.recents.events.activity.RecentsActivityStartingEvent; 66import com.android.systemui.recents.events.activity.UndockingTaskEvent; 67import com.android.systemui.recents.events.ui.RecentsDrawnEvent; 68import com.android.systemui.recents.misc.SystemServicesProxy; 69import com.android.systemui.statusbar.FlingAnimationUtils; 70import com.android.systemui.statusbar.phone.NavigationBarGestureHelper; 71 72/** 73 * Docked stack divider. 74 */ 75public class DividerView extends FrameLayout implements OnTouchListener, 76 OnComputeInternalInsetsListener { 77 78 static final long TOUCH_ANIMATION_DURATION = 150; 79 static final long TOUCH_RELEASE_ANIMATION_DURATION = 200; 80 81 private static final String TAG = "DividerView"; 82 83 private static final int TASK_POSITION_SAME = Integer.MAX_VALUE; 84 85 /** 86 * How much the background gets scaled when we are in the minimized dock state. 87 */ 88 private static final float MINIMIZE_DOCK_SCALE = 0.375f; 89 90 private static final PathInterpolator SLOWDOWN_INTERPOLATOR = 91 new PathInterpolator(0.5f, 1f, 0.5f, 1f); 92 private static final PathInterpolator DIM_INTERPOLATOR = 93 new PathInterpolator(.23f, .87f, .52f, -0.11f); 94 95 private DividerHandleView mHandle; 96 private View mBackground; 97 private int mStartX; 98 private int mStartY; 99 private int mStartPosition; 100 private int mDockSide; 101 private final int[] mTempInt2 = new int[2]; 102 private boolean mMoving; 103 private int mTouchSlop; 104 private boolean mBackgroundLifted; 105 106 private int mDividerInsets; 107 private int mDisplayWidth; 108 private int mDisplayHeight; 109 private int mDividerWindowWidth; 110 private int mDividerSize; 111 private int mTouchElevation; 112 113 private final Rect mDockedRect = new Rect(); 114 private final Rect mDockedTaskRect = new Rect(); 115 private final Rect mOtherTaskRect = new Rect(); 116 private final Rect mOtherRect = new Rect(); 117 private final Rect mDockedInsetRect = new Rect(); 118 private final Rect mOtherInsetRect = new Rect(); 119 private final Rect mLastResizeRect = new Rect(); 120 private final Rect mDisplayRect = new Rect(); 121 private final WindowManagerProxy mWindowManagerProxy = WindowManagerProxy.getInstance(); 122 private DividerWindowManager mWindowManager; 123 private VelocityTracker mVelocityTracker; 124 private FlingAnimationUtils mFlingAnimationUtils; 125 private DividerSnapAlgorithm mSnapAlgorithm; 126 private final Rect mStableInsets = new Rect(); 127 128 private boolean mAnimateAfterRecentsDrawn; 129 private boolean mGrowAfterRecentsDrawn; 130 private boolean mGrowRecents; 131 private ValueAnimator mCurrentAnimator; 132 private boolean mEntranceAnimationRunning; 133 private GestureDetector mGestureDetector; 134 135 private final AccessibilityDelegate mHandleDelegate = new AccessibilityDelegate() { 136 @Override 137 public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) { 138 super.onInitializeAccessibilityNodeInfo(host, info); 139 if (isHorizontalDivision()) { 140 info.addAction(new AccessibilityAction(R.id.action_move_up, 141 mContext.getString(R.string.accessibility_action_divider_move_up))); 142 info.addAction(new AccessibilityAction(R.id.action_move_down, 143 mContext.getString(R.string.accessibility_action_divider_move_down))); 144 } else { 145 info.addAction(new AccessibilityAction(R.id.action_move_left, 146 mContext.getString(R.string.accessibility_action_divider_move_left))); 147 info.addAction(new AccessibilityAction(R.id.action_move_right, 148 mContext.getString(R.string.accessibility_action_divider_move_right))); 149 } 150 } 151 152 @Override 153 public boolean performAccessibilityAction(View host, int action, Bundle args) { 154 if (action == R.id.action_move_up || action == R.id.action_move_down 155 || action == R.id.action_move_left || action == R.id.action_move_right) { 156 int position = getCurrentPosition(); 157 SnapTarget currentTarget = mSnapAlgorithm.calculateSnapTarget( 158 position, 0 /* velocity */); 159 SnapTarget nextTarget = 160 action == R.id.action_move_up || action == R.id.action_move_left 161 ? mSnapAlgorithm.getPreviousTarget(currentTarget) 162 : mSnapAlgorithm.getNextTarget(currentTarget); 163 startDragging(true /* animate */, false /* touching */); 164 stopDragging(getCurrentPosition(), nextTarget, 250, Interpolators.FAST_OUT_SLOW_IN); 165 return true; 166 } 167 return super.performAccessibilityAction(host, action, args); 168 } 169 }; 170 171 public DividerView(Context context) { 172 super(context); 173 } 174 175 public DividerView(Context context, @Nullable AttributeSet attrs) { 176 super(context, attrs); 177 } 178 179 public DividerView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 180 super(context, attrs, defStyleAttr); 181 } 182 183 public DividerView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, 184 int defStyleRes) { 185 super(context, attrs, defStyleAttr, defStyleRes); 186 } 187 188 @Override 189 protected void onFinishInflate() { 190 super.onFinishInflate(); 191 mHandle = (DividerHandleView) findViewById(R.id.docked_divider_handle); 192 mBackground = findViewById(R.id.docked_divider_background); 193 mHandle.setOnTouchListener(this); 194 mDividerWindowWidth = getResources().getDimensionPixelSize( 195 com.android.internal.R.dimen.docked_stack_divider_thickness); 196 mDividerInsets = getResources().getDimensionPixelSize( 197 com.android.internal.R.dimen.docked_stack_divider_insets); 198 mDividerSize = mDividerWindowWidth - 2 * mDividerInsets; 199 mTouchElevation = getResources().getDimensionPixelSize( 200 R.dimen.docked_stack_divider_lift_elevation); 201 mGrowRecents = getResources().getBoolean(R.bool.recents_grow_in_multiwindow); 202 mTouchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop(); 203 mFlingAnimationUtils = new FlingAnimationUtils(getContext(), 0.3f); 204 updateDisplayInfo(); 205 boolean landscape = getResources().getConfiguration().orientation 206 == Configuration.ORIENTATION_LANDSCAPE; 207 mHandle.setPointerIcon(PointerIcon.getSystemIcon(getContext(), 208 landscape ? STYLE_HORIZONTAL_DOUBLE_ARROW : STYLE_VERTICAL_DOUBLE_ARROW)); 209 getViewTreeObserver().addOnComputeInternalInsetsListener(this); 210 mHandle.setAccessibilityDelegate(mHandleDelegate); 211 mGestureDetector = new GestureDetector(mContext, new SimpleOnGestureListener() { 212 @Override 213 public boolean onSingleTapUp(MotionEvent e) { 214 updateDockSide(); 215 SystemServicesProxy ssp = Recents.getSystemServices(); 216 if (mDockSide != WindowManager.DOCKED_INVALID 217 && !ssp.isRecentsTopMost(ssp.getTopMostTask(), null /* isTopHome */)) { 218 mWindowManagerProxy.swapTasks(); 219 return true; 220 } 221 return false; 222 } 223 }); 224 } 225 226 @Override 227 protected void onAttachedToWindow() { 228 super.onAttachedToWindow(); 229 EventBus.getDefault().register(this); 230 getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() { 231 232 @Override 233 public void onGlobalLayout() { 234 getViewTreeObserver().removeOnGlobalLayoutListener(this); 235 mWindowManagerProxy.setTouchRegion(new Rect(mHandle.getLeft(), mHandle.getTop(), 236 mHandle.getLeft() + mHandle.getWidth(), 237 mHandle.getTop() + mHandle.getHeight())); 238 } 239 }); 240 } 241 242 @Override 243 protected void onDetachedFromWindow() { 244 super.onDetachedFromWindow(); 245 EventBus.getDefault().unregister(this); 246 } 247 248 @Override 249 public WindowInsets onApplyWindowInsets(WindowInsets insets) { 250 if (mStableInsets.left != insets.getStableInsetLeft() 251 || mStableInsets.top != insets.getStableInsetTop() 252 || mStableInsets.right != insets.getStableInsetRight() 253 || mStableInsets.bottom != insets.getStableInsetBottom()) { 254 mStableInsets.set(insets.getStableInsetLeft(), insets.getStableInsetTop(), 255 insets.getStableInsetRight(), insets.getStableInsetBottom()); 256 if (mSnapAlgorithm != null) { 257 mSnapAlgorithm = null; 258 initializeSnapAlgorithm(); 259 } 260 } 261 return super.onApplyWindowInsets(insets); 262 } 263 264 public void setWindowManager(DividerWindowManager windowManager) { 265 mWindowManager = windowManager; 266 } 267 268 public WindowManagerProxy getWindowManagerProxy() { 269 return mWindowManagerProxy; 270 } 271 272 public boolean startDragging(boolean animate, boolean touching) { 273 cancelFlingAnimation(); 274 if (touching) { 275 mHandle.setTouching(true, animate); 276 } 277 mDockSide = mWindowManagerProxy.getDockSide(); 278 initializeSnapAlgorithm(); 279 mWindowManagerProxy.setResizing(true); 280 if (touching) { 281 mWindowManager.setSlippery(false); 282 liftBackground(); 283 } 284 return mDockSide != WindowManager.DOCKED_INVALID; 285 } 286 287 public void stopDragging(int position, float velocity, boolean avoidDismissStart) { 288 mHandle.setTouching(false, true /* animate */); 289 fling(position, velocity, avoidDismissStart); 290 mWindowManager.setSlippery(true); 291 releaseBackground(); 292 } 293 294 public void stopDragging(int position, SnapTarget target, long duration, 295 Interpolator interpolator) { 296 stopDragging(position, target, duration, 0 /* startDelay*/, interpolator); 297 } 298 299 public void stopDragging(int position, SnapTarget target, long duration, long startDelay, 300 Interpolator interpolator) { 301 mHandle.setTouching(false, true /* animate */); 302 flingTo(position, target, duration, startDelay, interpolator); 303 mWindowManager.setSlippery(true); 304 releaseBackground(); 305 } 306 307 private void stopDragging() { 308 mHandle.setTouching(false, true /* animate */); 309 mWindowManager.setSlippery(true); 310 releaseBackground(); 311 } 312 313 private void updateDockSide() { 314 mDockSide = mWindowManagerProxy.getDockSide(); 315 } 316 317 private void initializeSnapAlgorithm() { 318 if (mSnapAlgorithm == null) { 319 mSnapAlgorithm = new DividerSnapAlgorithm(getContext().getResources(), mDisplayWidth, 320 mDisplayHeight, mDividerSize, isHorizontalDivision(), mStableInsets); 321 } 322 } 323 324 public DividerSnapAlgorithm getSnapAlgorithm() { 325 initializeSnapAlgorithm(); 326 return mSnapAlgorithm; 327 } 328 329 public int getCurrentPosition() { 330 getLocationOnScreen(mTempInt2); 331 if (isHorizontalDivision()) { 332 return mTempInt2[1] + mDividerInsets; 333 } else { 334 return mTempInt2[0] + mDividerInsets; 335 } 336 } 337 338 @Override 339 public boolean onTouch(View v, MotionEvent event) { 340 convertToScreenCoordinates(event); 341 mGestureDetector.onTouchEvent(event); 342 final int action = event.getAction() & MotionEvent.ACTION_MASK; 343 switch (action) { 344 case MotionEvent.ACTION_DOWN: 345 mVelocityTracker = VelocityTracker.obtain(); 346 mVelocityTracker.addMovement(event); 347 mStartX = (int) event.getX(); 348 mStartY = (int) event.getY(); 349 boolean result = startDragging(true /* animate */, true /* touching */); 350 if (!result) { 351 352 // Weren't able to start dragging successfully, so cancel it again. 353 stopDragging(); 354 } 355 mStartPosition = getCurrentPosition(); 356 mMoving = false; 357 return result; 358 case MotionEvent.ACTION_MOVE: 359 mVelocityTracker.addMovement(event); 360 int x = (int) event.getX(); 361 int y = (int) event.getY(); 362 boolean exceededTouchSlop = 363 isHorizontalDivision() && Math.abs(y - mStartY) > mTouchSlop 364 || (!isHorizontalDivision() && Math.abs(x - mStartX) > mTouchSlop); 365 if (!mMoving && exceededTouchSlop) { 366 mStartX = x; 367 mStartY = y; 368 mMoving = true; 369 } 370 if (mMoving && mDockSide != WindowManager.DOCKED_INVALID) { 371 SnapTarget snapTarget = mSnapAlgorithm.calculateSnapTarget( 372 mStartPosition, 0 /* velocity */, false /* hardDismiss */); 373 resizeStack(calculatePosition(x, y), mStartPosition, snapTarget); 374 } 375 break; 376 case MotionEvent.ACTION_UP: 377 case MotionEvent.ACTION_CANCEL: 378 mVelocityTracker.addMovement(event); 379 380 x = (int) event.getRawX(); 381 y = (int) event.getRawY(); 382 383 if (mMoving && mDockSide != WindowManager.DOCKED_INVALID) { 384 int position = calculatePosition(x, y); 385 SnapTarget snapTarget = mSnapAlgorithm.calculateSnapTarget(position, 386 0 /* velocity */, false /* hardDismiss */); 387 resizeStack(calculatePosition(x, y), snapTarget.position, snapTarget); 388 } 389 390 mVelocityTracker.computeCurrentVelocity(1000); 391 int position = calculatePosition(x, y); 392 stopDragging(position, isHorizontalDivision() ? mVelocityTracker.getYVelocity() 393 : mVelocityTracker.getXVelocity(), false /* avoidDismissStart */); 394 mMoving = false; 395 break; 396 } 397 return true; 398 } 399 400 private void convertToScreenCoordinates(MotionEvent event) { 401 event.setLocation(event.getRawX(), event.getRawY()); 402 } 403 404 private void fling(int position, float velocity, boolean avoidDismissStart) { 405 SnapTarget snapTarget = mSnapAlgorithm.calculateSnapTarget(position, velocity); 406 if (avoidDismissStart && snapTarget == mSnapAlgorithm.getDismissStartTarget()) { 407 snapTarget = mSnapAlgorithm.getFirstSplitTarget(); 408 } 409 ValueAnimator anim = getFlingAnimator(position, snapTarget); 410 mFlingAnimationUtils.apply(anim, position, snapTarget.position, velocity); 411 anim.start(); 412 } 413 414 private void flingTo(int position, SnapTarget target, long duration, long startDelay, 415 Interpolator interpolator) { 416 ValueAnimator anim = getFlingAnimator(position, target); 417 anim.setDuration(duration); 418 anim.setStartDelay(startDelay); 419 anim.setInterpolator(interpolator); 420 anim.start(); 421 } 422 423 private ValueAnimator getFlingAnimator(int position, final SnapTarget snapTarget) { 424 ValueAnimator anim = ValueAnimator.ofInt(position, snapTarget.position); 425 anim.addUpdateListener(new AnimatorUpdateListener() { 426 @Override 427 public void onAnimationUpdate(ValueAnimator animation) { 428 resizeStack((Integer) animation.getAnimatedValue(), 429 animation.getAnimatedFraction() == 1f 430 ? TASK_POSITION_SAME 431 : snapTarget.position, snapTarget); 432 } 433 }); 434 anim.addListener(new AnimatorListenerAdapter() { 435 @Override 436 public void onAnimationEnd(Animator animation) { 437 commitSnapFlags(snapTarget); 438 mWindowManagerProxy.setResizing(false); 439 mDockSide = WindowManager.DOCKED_INVALID; 440 mCurrentAnimator = null; 441 mEntranceAnimationRunning = false; 442 } 443 }); 444 mCurrentAnimator = anim; 445 return anim; 446 } 447 448 private void cancelFlingAnimation() { 449 if (mCurrentAnimator != null) { 450 mCurrentAnimator.cancel(); 451 } 452 } 453 454 private void commitSnapFlags(SnapTarget target) { 455 if (target.flag == SnapTarget.FLAG_NONE) { 456 return; 457 } 458 boolean dismissOrMaximize; 459 if (target.flag == SnapTarget.FLAG_DISMISS_START) { 460 dismissOrMaximize = mDockSide == WindowManager.DOCKED_LEFT 461 || mDockSide == WindowManager.DOCKED_TOP; 462 } else { 463 dismissOrMaximize = mDockSide == WindowManager.DOCKED_RIGHT 464 || mDockSide == WindowManager.DOCKED_BOTTOM; 465 } 466 if (dismissOrMaximize) { 467 mWindowManagerProxy.dismissDockedStack(); 468 } else { 469 mWindowManagerProxy.maximizeDockedStack(); 470 } 471 mWindowManagerProxy.setResizeDimLayer(false, -1, 0f); 472 } 473 474 private void liftBackground() { 475 if (mBackgroundLifted) { 476 return; 477 } 478 if (isHorizontalDivision()) { 479 mBackground.animate().scaleY(1.4f); 480 } else { 481 mBackground.animate().scaleX(1.4f); 482 } 483 mBackground.animate() 484 .setInterpolator(Interpolators.TOUCH_RESPONSE) 485 .setDuration(TOUCH_ANIMATION_DURATION) 486 .translationZ(mTouchElevation) 487 .start(); 488 489 // Lift handle as well so it doesn't get behind the background, even though it doesn't 490 // cast shadow. 491 mHandle.animate() 492 .setInterpolator(Interpolators.TOUCH_RESPONSE) 493 .setDuration(TOUCH_ANIMATION_DURATION) 494 .translationZ(mTouchElevation) 495 .start(); 496 mBackgroundLifted = true; 497 } 498 499 private void releaseBackground() { 500 if (!mBackgroundLifted) { 501 return; 502 } 503 mBackground.animate() 504 .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) 505 .setDuration(TOUCH_RELEASE_ANIMATION_DURATION) 506 .translationZ(0) 507 .scaleX(1f) 508 .scaleY(1f) 509 .start(); 510 mHandle.animate() 511 .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) 512 .setDuration(TOUCH_RELEASE_ANIMATION_DURATION) 513 .translationZ(0) 514 .start(); 515 mBackgroundLifted = false; 516 } 517 518 519 public void setMinimizedDockStack(boolean minimized) { 520 updateDockSide(); 521 mHandle.setAlpha(minimized ? 0f : 1f); 522 if (mDockSide == WindowManager.DOCKED_TOP) { 523 mBackground.setPivotY(0); 524 mBackground.setScaleY(minimized ? MINIMIZE_DOCK_SCALE : 1f); 525 } else if (mDockSide == WindowManager.DOCKED_LEFT 526 || mDockSide == WindowManager.DOCKED_RIGHT) { 527 mBackground.setPivotX(mDockSide == WindowManager.DOCKED_LEFT 528 ? 0 529 : mBackground.getWidth()); 530 mBackground.setScaleX(minimized ? MINIMIZE_DOCK_SCALE : 1f); 531 } 532 } 533 534 public void setMinimizedDockStack(boolean minimized, long animDuration) { 535 updateDockSide(); 536 mHandle.animate() 537 .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) 538 .setDuration(animDuration) 539 .alpha(minimized ? 0f : 1f) 540 .start(); 541 if (mDockSide == WindowManager.DOCKED_TOP) { 542 mBackground.setPivotY(0); 543 mBackground.animate() 544 .scaleY(minimized ? MINIMIZE_DOCK_SCALE : 1f); 545 } else if (mDockSide == WindowManager.DOCKED_LEFT 546 || mDockSide == WindowManager.DOCKED_RIGHT) { 547 mBackground.setPivotX(mDockSide == WindowManager.DOCKED_LEFT 548 ? 0 549 : mBackground.getWidth()); 550 mBackground.animate() 551 .scaleX(minimized ? MINIMIZE_DOCK_SCALE : 1f); 552 } 553 mBackground.animate() 554 .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) 555 .setDuration(animDuration) 556 .start(); 557 } 558 559 @Override 560 protected void onConfigurationChanged(Configuration newConfig) { 561 super.onConfigurationChanged(newConfig); 562 updateDisplayInfo(); 563 } 564 565 private void updateDisplayInfo() { 566 final DisplayManager displayManager = 567 (DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE); 568 Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY); 569 final DisplayInfo info = new DisplayInfo(); 570 display.getDisplayInfo(info); 571 mDisplayWidth = info.logicalWidth; 572 mDisplayHeight = info.logicalHeight; 573 mSnapAlgorithm = null; 574 initializeSnapAlgorithm(); 575 } 576 577 private int calculatePosition(int touchX, int touchY) { 578 return isHorizontalDivision() ? calculateYPosition(touchY) : calculateXPosition(touchX); 579 } 580 581 public boolean isHorizontalDivision() { 582 return getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT; 583 } 584 585 private int calculateXPosition(int touchX) { 586 return mStartPosition + touchX - mStartX; 587 } 588 589 private int calculateYPosition(int touchY) { 590 return mStartPosition + touchY - mStartY; 591 } 592 593 private void alignTopLeft(Rect containingRect, Rect rect) { 594 int width = rect.width(); 595 int height = rect.height(); 596 rect.set(containingRect.left, containingRect.top, 597 containingRect.left + width, containingRect.top + height); 598 } 599 600 private void alignBottomRight(Rect containingRect, Rect rect) { 601 int width = rect.width(); 602 int height = rect.height(); 603 rect.set(containingRect.right - width, containingRect.bottom - height, 604 containingRect.right, containingRect.bottom); 605 } 606 607 public void calculateBoundsForPosition(int position, int dockSide, Rect outRect) { 608 DockedDividerUtils.calculateBoundsForPosition(position, dockSide, outRect, mDisplayWidth, 609 mDisplayHeight, mDividerSize); 610 } 611 612 public void resizeStack(int position, int taskPosition, SnapTarget taskSnapTarget) { 613 calculateBoundsForPosition(position, mDockSide, mDockedRect); 614 615 if (mDockedRect.equals(mLastResizeRect)) { 616 return; 617 } 618 619 // Make sure shadows are updated 620 if (mBackground.getZ() > 0f) { 621 mBackground.invalidate(); 622 } 623 624 mLastResizeRect.set(mDockedRect); 625 if (mEntranceAnimationRunning && taskPosition != TASK_POSITION_SAME) { 626 if (mCurrentAnimator != null) { 627 calculateBoundsForPosition(taskPosition, mDockSide, mDockedTaskRect); 628 } else { 629 calculateBoundsForPosition(isHorizontalDivision() ? mDisplayHeight : mDisplayWidth, 630 mDockSide, mDockedTaskRect); 631 } 632 calculateBoundsForPosition(taskPosition, DockedDividerUtils.invertDockSide(mDockSide), 633 mOtherTaskRect); 634 mWindowManagerProxy.resizeDockedStack(mDockedRect, mDockedTaskRect, null, 635 mOtherTaskRect, null); 636 } else if (taskPosition != TASK_POSITION_SAME) { 637 calculateBoundsForPosition(position, DockedDividerUtils.invertDockSide(mDockSide), 638 mOtherRect); 639 int dockSideInverted = DockedDividerUtils.invertDockSide(mDockSide); 640 int taskPositionDocked = 641 restrictDismissingTaskPosition(taskPosition, mDockSide, taskSnapTarget); 642 int taskPositionOther = 643 restrictDismissingTaskPosition(taskPosition, dockSideInverted, taskSnapTarget); 644 calculateBoundsForPosition(taskPositionDocked, mDockSide, mDockedTaskRect); 645 calculateBoundsForPosition(taskPositionOther, dockSideInverted, mOtherTaskRect); 646 mDisplayRect.set(0, 0, mDisplayWidth, mDisplayHeight); 647 alignTopLeft(mDockedRect, mDockedTaskRect); 648 alignTopLeft(mOtherRect, mOtherTaskRect); 649 mDockedInsetRect.set(mDockedTaskRect); 650 mOtherInsetRect.set(mOtherTaskRect); 651 if (dockSideTopLeft(mDockSide)) { 652 alignTopLeft(mDisplayRect, mDockedInsetRect); 653 alignBottomRight(mDisplayRect, mOtherInsetRect); 654 } else { 655 alignBottomRight(mDisplayRect, mDockedInsetRect); 656 alignTopLeft(mDisplayRect, mOtherInsetRect); 657 } 658 applyDismissingParallax(mDockedTaskRect, mDockSide, taskSnapTarget, position, 659 taskPositionDocked); 660 applyDismissingParallax(mOtherTaskRect, dockSideInverted, taskSnapTarget, position, 661 taskPositionOther); 662 mWindowManagerProxy.resizeDockedStack(mDockedRect, mDockedTaskRect, mDockedInsetRect, 663 mOtherTaskRect, mOtherInsetRect); 664 } else { 665 mWindowManagerProxy.resizeDockedStack(mDockedRect, null, null, null, null); 666 } 667 SnapTarget closestDismissTarget = mSnapAlgorithm.getClosestDismissTarget(position); 668 float dimFraction = getDimFraction(position, closestDismissTarget); 669 mWindowManagerProxy.setResizeDimLayer(dimFraction != 0f, 670 getStackIdForDismissTarget(closestDismissTarget), 671 dimFraction); 672 } 673 674 private float getDimFraction(int position, SnapTarget dismissTarget) { 675 if (mEntranceAnimationRunning) { 676 return 0f; 677 } 678 float fraction = mSnapAlgorithm.calculateDismissingFraction(position); 679 fraction = Math.max(0, Math.min(fraction, 1f)); 680 fraction = DIM_INTERPOLATOR.getInterpolation(fraction); 681 if (hasInsetsAtDismissTarget(dismissTarget)) { 682 683 // Less darkening with system insets. 684 fraction *= 0.8f; 685 } 686 return fraction; 687 } 688 689 /** 690 * @return true if and only if there are system insets at the location of the dismiss target 691 */ 692 private boolean hasInsetsAtDismissTarget(SnapTarget dismissTarget) { 693 if (isHorizontalDivision()) { 694 if (dismissTarget == mSnapAlgorithm.getDismissStartTarget()) { 695 return mStableInsets.top != 0; 696 } else { 697 return mStableInsets.bottom != 0; 698 } 699 } else { 700 if (dismissTarget == mSnapAlgorithm.getDismissStartTarget()) { 701 return mStableInsets.left != 0; 702 } else { 703 return mStableInsets.right != 0; 704 } 705 } 706 } 707 708 /** 709 * When the snap target is dismissing one side, make sure that the dismissing side doesn't get 710 * 0 size. 711 */ 712 private int restrictDismissingTaskPosition(int taskPosition, int dockSide, 713 SnapTarget snapTarget) { 714 if (snapTarget.flag == SnapTarget.FLAG_DISMISS_START && dockSideTopLeft(dockSide)) { 715 return mSnapAlgorithm.getFirstSplitTarget().position; 716 } else if (snapTarget.flag == SnapTarget.FLAG_DISMISS_END 717 && dockSideBottomRight(dockSide)) { 718 return mSnapAlgorithm.getLastSplitTarget().position; 719 } else { 720 return taskPosition; 721 } 722 } 723 724 /** 725 * Applies a parallax to the task when dismissing. 726 */ 727 private void applyDismissingParallax(Rect taskRect, int dockSide, SnapTarget snapTarget, 728 int position, int taskPosition) { 729 float fraction = Math.min(1, Math.max(0, 730 mSnapAlgorithm.calculateDismissingFraction(position))); 731 SnapTarget dismissTarget = null; 732 SnapTarget splitTarget = null; 733 if ((snapTarget.flag == SnapTarget.FLAG_DISMISS_START 734 || snapTarget == mSnapAlgorithm.getFirstSplitTarget()) 735 && dockSideTopLeft(dockSide)) { 736 dismissTarget = mSnapAlgorithm.getDismissStartTarget(); 737 splitTarget = mSnapAlgorithm.getFirstSplitTarget(); 738 } else if ((snapTarget.flag == SnapTarget.FLAG_DISMISS_END 739 || snapTarget == mSnapAlgorithm.getLastSplitTarget()) 740 && dockSideBottomRight(dockSide)) { 741 dismissTarget = mSnapAlgorithm.getDismissEndTarget(); 742 splitTarget = mSnapAlgorithm.getLastSplitTarget(); 743 } 744 if (dismissTarget != null && fraction > 0f 745 && isDismissing(splitTarget, position, dockSide)) { 746 fraction = calculateParallaxDismissingFraction(fraction, dockSide); 747 int offsetPosition = (int) (taskPosition + 748 fraction * (dismissTarget.position - splitTarget.position)); 749 int width = taskRect.width(); 750 int height = taskRect.height(); 751 switch (dockSide) { 752 case WindowManager.DOCKED_LEFT: 753 taskRect.left = offsetPosition - width; 754 taskRect.right = offsetPosition; 755 break; 756 case WindowManager.DOCKED_RIGHT: 757 taskRect.left = offsetPosition + mDividerSize; 758 taskRect.right = offsetPosition + width + mDividerSize; 759 break; 760 case WindowManager.DOCKED_TOP: 761 taskRect.top = offsetPosition - height; 762 taskRect.bottom = offsetPosition; 763 break; 764 case WindowManager.DOCKED_BOTTOM: 765 taskRect.top = offsetPosition + mDividerSize; 766 taskRect.bottom = offsetPosition + height + mDividerSize; 767 break; 768 } 769 } 770 } 771 772 /** 773 * @return for a specified {@code fraction}, this returns an adjusted value that simulates a 774 * slowing down parallax effect 775 */ 776 private static float calculateParallaxDismissingFraction(float fraction, int dockSide) { 777 float result = SLOWDOWN_INTERPOLATOR.getInterpolation(fraction) / 3.5f; 778 779 // Less parallax at the top, just because. 780 if (dockSide == WindowManager.DOCKED_TOP) { 781 result /= 2f; 782 } 783 return result; 784 } 785 786 private static boolean isDismissing(SnapTarget snapTarget, int position, int dockSide) { 787 if (dockSide == WindowManager.DOCKED_TOP || dockSide == WindowManager.DOCKED_LEFT) { 788 return position < snapTarget.position; 789 } else { 790 return position > snapTarget.position; 791 } 792 } 793 794 private int getStackIdForDismissTarget(SnapTarget dismissTarget) { 795 if (dismissTarget.flag == SnapTarget.FLAG_DISMISS_START && 796 (mDockSide == WindowManager.DOCKED_LEFT || mDockSide == WindowManager.DOCKED_TOP)) { 797 return StackId.DOCKED_STACK_ID; 798 } else { 799 return StackId.FULLSCREEN_WORKSPACE_STACK_ID; 800 } 801 } 802 803 /** 804 * @return true if and only if {@code dockSide} is top or left 805 */ 806 private static boolean dockSideTopLeft(int dockSide) { 807 return dockSide == WindowManager.DOCKED_TOP || dockSide == WindowManager.DOCKED_LEFT; 808 } 809 810 /** 811 * @return true if and only if {@code dockSide} is bottom or right 812 */ 813 private static boolean dockSideBottomRight(int dockSide) { 814 return dockSide == WindowManager.DOCKED_BOTTOM || dockSide == WindowManager.DOCKED_RIGHT; 815 } 816 817 @Override 818 public void onComputeInternalInsets(InternalInsetsInfo inoutInfo) { 819 inoutInfo.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_REGION); 820 inoutInfo.touchableRegion.set(mHandle.getLeft(), mHandle.getTop(), mHandle.getRight(), 821 mHandle.getBottom()); 822 inoutInfo.touchableRegion.op(mBackground.getLeft(), mBackground.getTop(), 823 mBackground.getRight(), mBackground.getBottom(), Op.UNION); 824 } 825 826 public final void onBusEvent(RecentsActivityStartingEvent recentsActivityStartingEvent) { 827 if (mGrowRecents && getWindowManagerProxy().getDockSide() == WindowManager.DOCKED_TOP 828 && getCurrentPosition() == getSnapAlgorithm().getLastSplitTarget().position) { 829 mGrowAfterRecentsDrawn = true; 830 startDragging(false /* animate */, false /* touching */); 831 } 832 } 833 834 public final void onBusEvent(DockedTopTaskEvent event) { 835 if (event.dragMode == NavigationBarGestureHelper.DRAG_MODE_NONE) { 836 mGrowAfterRecentsDrawn = false; 837 mAnimateAfterRecentsDrawn = true; 838 startDragging(false /* animate */, false /* touching */); 839 } 840 updateDockSide(); 841 int position = DockedDividerUtils.calculatePositionForBounds(event.initialRect, 842 mDockSide, mDividerSize); 843 mEntranceAnimationRunning = true; 844 resizeStack(position, mSnapAlgorithm.getMiddleTarget().position, 845 mSnapAlgorithm.getMiddleTarget()); 846 } 847 848 public final void onBusEvent(RecentsDrawnEvent drawnEvent) { 849 if (mAnimateAfterRecentsDrawn) { 850 mAnimateAfterRecentsDrawn = false; 851 updateDockSide(); 852 stopDragging(getCurrentPosition(), mSnapAlgorithm.getMiddleTarget(), 250, 853 Interpolators.TOUCH_RESPONSE); 854 } 855 if (mGrowAfterRecentsDrawn) { 856 mGrowAfterRecentsDrawn = false; 857 updateDockSide(); 858 stopDragging(getCurrentPosition(), mSnapAlgorithm.getMiddleTarget(), 250, 859 Interpolators.TOUCH_RESPONSE); 860 } 861 } 862 863 public final void onBusEvent(UndockingTaskEvent undockingTaskEvent) { 864 int dockSide = mWindowManagerProxy.getDockSide(); 865 if (dockSide != WindowManager.DOCKED_INVALID) { 866 startDragging(false /* animate */, false /* touching */); 867 SnapTarget target = dockSideTopLeft(dockSide) 868 ? mSnapAlgorithm.getDismissEndTarget() 869 : mSnapAlgorithm.getDismissStartTarget(); 870 871 // Don't start immediately - give a little bit time to settle the drag resize change. 872 stopDragging(getCurrentPosition(), target, 336 /* duration */, 100 /* startDelay */, 873 Interpolators.TOUCH_RESPONSE); 874 } 875 } 876} 877