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 17 package com.android.systemui.stackdivider; 18 19 import static android.view.PointerIcon.TYPE_HORIZONTAL_DOUBLE_ARROW; 20 import static android.view.PointerIcon.TYPE_VERTICAL_DOUBLE_ARROW; 21 22 import android.animation.Animator; 23 import android.animation.AnimatorListenerAdapter; 24 import android.animation.ValueAnimator; 25 import android.annotation.Nullable; 26 import android.app.ActivityManager.StackId; 27 import android.content.Context; 28 import android.content.res.Configuration; 29 import android.graphics.Rect; 30 import android.graphics.Region.Op; 31 import android.hardware.display.DisplayManager; 32 import android.os.Bundle; 33 import android.os.Handler; 34 import android.os.Message; 35 import android.util.AttributeSet; 36 import android.view.Display; 37 import android.view.DisplayInfo; 38 import android.view.GestureDetector; 39 import android.view.GestureDetector.SimpleOnGestureListener; 40 import android.view.MotionEvent; 41 import android.view.PointerIcon; 42 import android.view.VelocityTracker; 43 import android.view.View; 44 import android.view.View.OnTouchListener; 45 import android.view.ViewConfiguration; 46 import android.view.ViewTreeObserver.InternalInsetsInfo; 47 import android.view.ViewTreeObserver.OnComputeInternalInsetsListener; 48 import android.view.WindowInsets; 49 import android.view.WindowManager; 50 import android.view.accessibility.AccessibilityNodeInfo; 51 import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; 52 import android.view.animation.Interpolator; 53 import android.view.animation.PathInterpolator; 54 import android.widget.FrameLayout; 55 56 import com.android.internal.logging.MetricsLogger; 57 import com.android.internal.logging.MetricsProto.MetricsEvent; 58 import com.android.internal.policy.DividerSnapAlgorithm; 59 import com.android.internal.policy.DividerSnapAlgorithm.SnapTarget; 60 import com.android.internal.policy.DockedDividerUtils; 61 import com.android.systemui.Interpolators; 62 import com.android.systemui.R; 63 import com.android.systemui.recents.Recents; 64 import com.android.systemui.recents.events.EventBus; 65 import com.android.systemui.recents.events.activity.DockedTopTaskEvent; 66 import com.android.systemui.recents.events.activity.RecentsActivityStartingEvent; 67 import com.android.systemui.recents.events.activity.UndockingTaskEvent; 68 import com.android.systemui.recents.events.ui.RecentsDrawnEvent; 69 import com.android.systemui.recents.events.ui.RecentsGrowingEvent; 70 import com.android.systemui.recents.misc.SystemServicesProxy; 71 import com.android.systemui.stackdivider.events.StartedDragingEvent; 72 import com.android.systemui.stackdivider.events.StoppedDragingEvent; 73 import com.android.systemui.statusbar.FlingAnimationUtils; 74 import com.android.systemui.statusbar.phone.NavigationBarGestureHelper; 75 76 /** 77 * Docked stack divider. 78 */ 79 public class DividerView extends FrameLayout implements OnTouchListener, 80 OnComputeInternalInsetsListener { 81 82 static final long TOUCH_ANIMATION_DURATION = 150; 83 static final long TOUCH_RELEASE_ANIMATION_DURATION = 200; 84 85 public static final int INVALID_RECENTS_GROW_TARGET = -1; 86 87 private static final int LOG_VALUE_RESIZE_50_50 = 0; 88 private static final int LOG_VALUE_RESIZE_DOCKED_SMALLER = 1; 89 private static final int LOG_VALUE_RESIZE_DOCKED_LARGER = 2; 90 91 private static final int LOG_VALUE_UNDOCK_MAX_DOCKED = 0; 92 private static final int LOG_VALUE_UNDOCK_MAX_OTHER = 1; 93 94 private static final int TASK_POSITION_SAME = Integer.MAX_VALUE; 95 private static final boolean SWAPPING_ENABLED = false; 96 97 /** 98 * How much the background gets scaled when we are in the minimized dock state. 99 */ 100 private static final float MINIMIZE_DOCK_SCALE = 0f; 101 private static final float ADJUSTED_FOR_IME_SCALE = 0.5f; 102 103 private static final PathInterpolator SLOWDOWN_INTERPOLATOR = 104 new PathInterpolator(0.5f, 1f, 0.5f, 1f); 105 private static final PathInterpolator DIM_INTERPOLATOR = 106 new PathInterpolator(.23f, .87f, .52f, -0.11f); 107 private static final Interpolator IME_ADJUST_INTERPOLATOR = 108 new PathInterpolator(0.2f, 0f, 0.1f, 1f); 109 110 private static final long ONE_MS_IN_NS = 1000000; 111 private static final long ONE_S_IN_NS = ONE_MS_IN_NS * 1000; 112 113 private static final int MSG_RESIZE_STACK = 0; 114 115 private DividerHandleView mHandle; 116 private View mBackground; 117 private MinimizedDockShadow mMinimizedShadow; 118 private int mStartX; 119 private int mStartY; 120 private int mStartPosition; 121 private int mDockSide; 122 private final int[] mTempInt2 = new int[2]; 123 private boolean mMoving; 124 private int mTouchSlop; 125 private boolean mBackgroundLifted; 126 127 private int mDividerInsets; 128 private int mDisplayWidth; 129 private int mDisplayHeight; 130 private int mDividerWindowWidth; 131 private int mDividerSize; 132 private int mTouchElevation; 133 private int mLongPressEntraceAnimDuration; 134 135 private final Rect mDockedRect = new Rect(); 136 private final Rect mDockedTaskRect = new Rect(); 137 private final Rect mOtherTaskRect = new Rect(); 138 private final Rect mOtherRect = new Rect(); 139 private final Rect mDockedInsetRect = new Rect(); 140 private final Rect mOtherInsetRect = new Rect(); 141 private final Rect mLastResizeRect = new Rect(); 142 private final Rect mDisplayRect = new Rect(); 143 private final WindowManagerProxy mWindowManagerProxy = WindowManagerProxy.getInstance(); 144 private DividerWindowManager mWindowManager; 145 private VelocityTracker mVelocityTracker; 146 private FlingAnimationUtils mFlingAnimationUtils; 147 private DividerSnapAlgorithm mSnapAlgorithm; 148 private final Rect mStableInsets = new Rect(); 149 150 private boolean mGrowRecents; 151 private ValueAnimator mCurrentAnimator; 152 private boolean mEntranceAnimationRunning; 153 private boolean mExitAnimationRunning; 154 private int mExitStartPosition; 155 private GestureDetector mGestureDetector; 156 private boolean mDockedStackMinimized; 157 private boolean mAdjustedForIme; 158 private DividerState mState; 159 160 /** 161 * The offset between vsync-app and vsync-surfaceflinger. See 162 * {@link #calculateAppSurfaceFlingerVsyncOffsetMs} why this is necessary. 163 */ 164 private long mSurfaceFlingerOffsetMs; 165 166 private final Handler mHandler = new Handler() { 167 @Override 168 public void handleMessage(Message msg) { 169 switch (msg.what) { 170 case MSG_RESIZE_STACK: 171 resizeStack(msg.arg1, msg.arg2, (SnapTarget) msg.obj); 172 break; 173 default: 174 super.handleMessage(msg); 175 } 176 } 177 }; 178 179 private final AccessibilityDelegate mHandleDelegate = new AccessibilityDelegate() { 180 @Override 181 public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) { 182 super.onInitializeAccessibilityNodeInfo(host, info); 183 if (isHorizontalDivision()) { 184 info.addAction(new AccessibilityAction(R.id.action_move_tl_full, 185 mContext.getString(R.string.accessibility_action_divider_top_full))); 186 if (mSnapAlgorithm.isFirstSplitTargetAvailable()) { 187 info.addAction(new AccessibilityAction(R.id.action_move_tl_70, 188 mContext.getString(R.string.accessibility_action_divider_top_70))); 189 } 190 info.addAction(new AccessibilityAction(R.id.action_move_tl_50, 191 mContext.getString(R.string.accessibility_action_divider_top_50))); 192 if (mSnapAlgorithm.isLastSplitTargetAvailable()) { 193 info.addAction(new AccessibilityAction(R.id.action_move_tl_30, 194 mContext.getString(R.string.accessibility_action_divider_top_30))); 195 } 196 info.addAction(new AccessibilityAction(R.id.action_move_rb_full, 197 mContext.getString(R.string.accessibility_action_divider_bottom_full))); 198 } else { 199 info.addAction(new AccessibilityAction(R.id.action_move_tl_full, 200 mContext.getString(R.string.accessibility_action_divider_left_full))); 201 if (mSnapAlgorithm.isFirstSplitTargetAvailable()) { 202 info.addAction(new AccessibilityAction(R.id.action_move_tl_70, 203 mContext.getString(R.string.accessibility_action_divider_left_70))); 204 } 205 info.addAction(new AccessibilityAction(R.id.action_move_tl_50, 206 mContext.getString(R.string.accessibility_action_divider_left_50))); 207 if (mSnapAlgorithm.isLastSplitTargetAvailable()) { 208 info.addAction(new AccessibilityAction(R.id.action_move_tl_30, 209 mContext.getString(R.string.accessibility_action_divider_left_30))); 210 } 211 info.addAction(new AccessibilityAction(R.id.action_move_rb_full, 212 mContext.getString(R.string.accessibility_action_divider_right_full))); 213 } 214 } 215 216 @Override 217 public boolean performAccessibilityAction(View host, int action, Bundle args) { 218 int currentPosition = getCurrentPosition(); 219 SnapTarget nextTarget = null; 220 switch (action) { 221 case R.id.action_move_tl_full: 222 nextTarget = mSnapAlgorithm.getDismissEndTarget(); 223 break; 224 case R.id.action_move_tl_70: 225 nextTarget = mSnapAlgorithm.getLastSplitTarget(); 226 break; 227 case R.id.action_move_tl_50: 228 nextTarget = mSnapAlgorithm.getMiddleTarget(); 229 break; 230 case R.id.action_move_tl_30: 231 nextTarget = mSnapAlgorithm.getFirstSplitTarget(); 232 break; 233 case R.id.action_move_rb_full: 234 nextTarget = mSnapAlgorithm.getDismissStartTarget(); 235 break; 236 } 237 if (nextTarget != null) { 238 startDragging(true /* animate */, false /* touching */); 239 stopDragging(currentPosition, nextTarget, 250, Interpolators.FAST_OUT_SLOW_IN); 240 return true; 241 } 242 return super.performAccessibilityAction(host, action, args); 243 } 244 }; 245 246 private final Runnable mResetBackgroundRunnable = new Runnable() { 247 @Override 248 public void run() { 249 resetBackground(); 250 } 251 }; 252 253 public DividerView(Context context) { 254 super(context); 255 } 256 257 public DividerView(Context context, @Nullable AttributeSet attrs) { 258 super(context, attrs); 259 } 260 261 public DividerView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 262 super(context, attrs, defStyleAttr); 263 } 264 265 public DividerView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, 266 int defStyleRes) { 267 super(context, attrs, defStyleAttr, defStyleRes); 268 } 269 270 @Override 271 protected void onFinishInflate() { 272 super.onFinishInflate(); 273 mHandle = (DividerHandleView) findViewById(R.id.docked_divider_handle); 274 mBackground = findViewById(R.id.docked_divider_background); 275 mMinimizedShadow = (MinimizedDockShadow) findViewById(R.id.minimized_dock_shadow); 276 mHandle.setOnTouchListener(this); 277 mDividerWindowWidth = getResources().getDimensionPixelSize( 278 com.android.internal.R.dimen.docked_stack_divider_thickness); 279 mDividerInsets = getResources().getDimensionPixelSize( 280 com.android.internal.R.dimen.docked_stack_divider_insets); 281 mDividerSize = mDividerWindowWidth - 2 * mDividerInsets; 282 mTouchElevation = getResources().getDimensionPixelSize( 283 R.dimen.docked_stack_divider_lift_elevation); 284 mLongPressEntraceAnimDuration = getResources().getInteger( 285 R.integer.long_press_dock_anim_duration); 286 mGrowRecents = getResources().getBoolean(R.bool.recents_grow_in_multiwindow); 287 mTouchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop(); 288 mFlingAnimationUtils = new FlingAnimationUtils(getContext(), 0.3f); 289 updateDisplayInfo(); 290 boolean landscape = getResources().getConfiguration().orientation 291 == Configuration.ORIENTATION_LANDSCAPE; 292 mHandle.setPointerIcon(PointerIcon.getSystemIcon(getContext(), 293 landscape ? TYPE_HORIZONTAL_DOUBLE_ARROW : TYPE_VERTICAL_DOUBLE_ARROW)); 294 getViewTreeObserver().addOnComputeInternalInsetsListener(this); 295 mHandle.setAccessibilityDelegate(mHandleDelegate); 296 mGestureDetector = new GestureDetector(mContext, new SimpleOnGestureListener() { 297 @Override 298 public boolean onSingleTapUp(MotionEvent e) { 299 if (SWAPPING_ENABLED) { 300 updateDockSide(); 301 SystemServicesProxy ssp = Recents.getSystemServices(); 302 if (mDockSide != WindowManager.DOCKED_INVALID 303 && !ssp.isRecentsActivityVisible()) { 304 mWindowManagerProxy.swapTasks(); 305 return true; 306 } 307 } 308 return false; 309 } 310 }); 311 } 312 313 @Override 314 protected void onAttachedToWindow() { 315 super.onAttachedToWindow(); 316 EventBus.getDefault().register(this); 317 mSurfaceFlingerOffsetMs = calculateAppSurfaceFlingerVsyncOffsetMs(); 318 } 319 320 @Override 321 protected void onDetachedFromWindow() { 322 super.onDetachedFromWindow(); 323 EventBus.getDefault().unregister(this); 324 } 325 326 /** 327 * This method calculates the offset between vsync-surfaceflinger and vsync-app. If vsync-app 328 * is a couple of milliseconds before vsync-sf, a touch or animation event that causes the 329 * stacks to be resized are sometimes processed before the vsync-sf tick, and sometimes after, 330 * which leads to jank. Figure out this difference here and then post all the touch/animation 331 * events to start being processed at vsync-sf. 332 * 333 * @return The offset between vsync-app and vsync-sf, or 0 if vsync app happens after vsync-sf. 334 */ 335 private long calculateAppSurfaceFlingerVsyncOffsetMs() { 336 Display display = getDisplay(); 337 338 // Calculate vsync offset from SurfaceFlinger. 339 // See frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp:getDisplayConfigs 340 long vsyncPeriod = (long) (ONE_S_IN_NS / display.getRefreshRate()); 341 long sfVsyncOffset = vsyncPeriod - (display.getPresentationDeadlineNanos() - ONE_MS_IN_NS); 342 return Math.max(0, (sfVsyncOffset - display.getAppVsyncOffsetNanos()) / ONE_MS_IN_NS); 343 } 344 345 @Override 346 public WindowInsets onApplyWindowInsets(WindowInsets insets) { 347 if (mStableInsets.left != insets.getStableInsetLeft() 348 || mStableInsets.top != insets.getStableInsetTop() 349 || mStableInsets.right != insets.getStableInsetRight() 350 || mStableInsets.bottom != insets.getStableInsetBottom()) { 351 mStableInsets.set(insets.getStableInsetLeft(), insets.getStableInsetTop(), 352 insets.getStableInsetRight(), insets.getStableInsetBottom()); 353 if (mSnapAlgorithm != null) { 354 mSnapAlgorithm = null; 355 initializeSnapAlgorithm(); 356 } 357 } 358 return super.onApplyWindowInsets(insets); 359 } 360 361 @Override 362 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 363 super.onLayout(changed, left, top, right, bottom); 364 int minimizeLeft = 0; 365 int minimizeTop = 0; 366 if (mDockSide == WindowManager.DOCKED_TOP) { 367 minimizeTop = mBackground.getTop(); 368 } else if (mDockSide == WindowManager.DOCKED_LEFT) { 369 minimizeLeft = mBackground.getLeft(); 370 } else if (mDockSide == WindowManager.DOCKED_RIGHT) { 371 minimizeLeft = mBackground.getRight() - mMinimizedShadow.getWidth(); 372 } 373 mMinimizedShadow.layout(minimizeLeft, minimizeTop, 374 minimizeLeft + mMinimizedShadow.getMeasuredWidth(), 375 minimizeTop + mMinimizedShadow.getMeasuredHeight()); 376 if (changed) { 377 mWindowManagerProxy.setTouchRegion(new Rect(mHandle.getLeft(), mHandle.getTop(), 378 mHandle.getRight(), mHandle.getBottom())); 379 } 380 } 381 382 public void injectDependencies(DividerWindowManager windowManager, DividerState dividerState) { 383 mWindowManager = windowManager; 384 mState = dividerState; 385 } 386 387 public WindowManagerProxy getWindowManagerProxy() { 388 return mWindowManagerProxy; 389 } 390 391 public boolean startDragging(boolean animate, boolean touching) { 392 cancelFlingAnimation(); 393 if (touching) { 394 mHandle.setTouching(true, animate); 395 } 396 mDockSide = mWindowManagerProxy.getDockSide(); 397 initializeSnapAlgorithm(); 398 mWindowManagerProxy.setResizing(true); 399 if (touching) { 400 mWindowManager.setSlippery(false); 401 liftBackground(); 402 } 403 EventBus.getDefault().send(new StartedDragingEvent()); 404 return mDockSide != WindowManager.DOCKED_INVALID; 405 } 406 407 public void stopDragging(int position, float velocity, boolean avoidDismissStart, 408 boolean logMetrics) { 409 mHandle.setTouching(false, true /* animate */); 410 fling(position, velocity, avoidDismissStart, logMetrics); 411 mWindowManager.setSlippery(true); 412 releaseBackground(); 413 } 414 415 public void stopDragging(int position, SnapTarget target, long duration, 416 Interpolator interpolator) { 417 stopDragging(position, target, duration, 0 /* startDelay*/, 0 /* endDelay */, interpolator); 418 } 419 420 public void stopDragging(int position, SnapTarget target, long duration, 421 Interpolator interpolator, long endDelay) { 422 stopDragging(position, target, duration, 0 /* startDelay*/, endDelay, interpolator); 423 } 424 425 public void stopDragging(int position, SnapTarget target, long duration, long startDelay, 426 long endDelay, Interpolator interpolator) { 427 mHandle.setTouching(false, true /* animate */); 428 flingTo(position, target, duration, startDelay, endDelay, interpolator); 429 mWindowManager.setSlippery(true); 430 releaseBackground(); 431 } 432 433 private void stopDragging() { 434 mHandle.setTouching(false, true /* animate */); 435 mWindowManager.setSlippery(true); 436 releaseBackground(); 437 } 438 439 private void updateDockSide() { 440 mDockSide = mWindowManagerProxy.getDockSide(); 441 mMinimizedShadow.setDockSide(mDockSide); 442 } 443 444 private void initializeSnapAlgorithm() { 445 if (mSnapAlgorithm == null) { 446 mSnapAlgorithm = new DividerSnapAlgorithm(getContext().getResources(), mDisplayWidth, 447 mDisplayHeight, mDividerSize, isHorizontalDivision(), mStableInsets); 448 } 449 } 450 451 public DividerSnapAlgorithm getSnapAlgorithm() { 452 initializeSnapAlgorithm(); 453 return mSnapAlgorithm; 454 } 455 456 public int getCurrentPosition() { 457 getLocationOnScreen(mTempInt2); 458 if (isHorizontalDivision()) { 459 return mTempInt2[1] + mDividerInsets; 460 } else { 461 return mTempInt2[0] + mDividerInsets; 462 } 463 } 464 465 @Override 466 public boolean onTouch(View v, MotionEvent event) { 467 convertToScreenCoordinates(event); 468 mGestureDetector.onTouchEvent(event); 469 final int action = event.getAction() & MotionEvent.ACTION_MASK; 470 switch (action) { 471 case MotionEvent.ACTION_DOWN: 472 mVelocityTracker = VelocityTracker.obtain(); 473 mVelocityTracker.addMovement(event); 474 mStartX = (int) event.getX(); 475 mStartY = (int) event.getY(); 476 boolean result = startDragging(true /* animate */, true /* touching */); 477 if (!result) { 478 479 // Weren't able to start dragging successfully, so cancel it again. 480 stopDragging(); 481 } 482 mStartPosition = getCurrentPosition(); 483 mMoving = false; 484 return result; 485 case MotionEvent.ACTION_MOVE: 486 mVelocityTracker.addMovement(event); 487 int x = (int) event.getX(); 488 int y = (int) event.getY(); 489 boolean exceededTouchSlop = 490 isHorizontalDivision() && Math.abs(y - mStartY) > mTouchSlop 491 || (!isHorizontalDivision() && Math.abs(x - mStartX) > mTouchSlop); 492 if (!mMoving && exceededTouchSlop) { 493 mStartX = x; 494 mStartY = y; 495 mMoving = true; 496 } 497 if (mMoving && mDockSide != WindowManager.DOCKED_INVALID) { 498 SnapTarget snapTarget = mSnapAlgorithm.calculateSnapTarget( 499 mStartPosition, 0 /* velocity */, false /* hardDismiss */); 500 resizeStackDelayed(calculatePosition(x, y), mStartPosition, snapTarget); 501 } 502 break; 503 case MotionEvent.ACTION_UP: 504 case MotionEvent.ACTION_CANCEL: 505 mVelocityTracker.addMovement(event); 506 507 x = (int) event.getRawX(); 508 y = (int) event.getRawY(); 509 510 mVelocityTracker.computeCurrentVelocity(1000); 511 int position = calculatePosition(x, y); 512 stopDragging(position, isHorizontalDivision() ? mVelocityTracker.getYVelocity() 513 : mVelocityTracker.getXVelocity(), false /* avoidDismissStart */, 514 true /* log */); 515 mMoving = false; 516 break; 517 } 518 return true; 519 } 520 521 private void logResizeEvent(SnapTarget snapTarget) { 522 if (snapTarget == mSnapAlgorithm.getDismissStartTarget()) { 523 MetricsLogger.action( 524 mContext, MetricsEvent.ACTION_WINDOW_UNDOCK_MAX, dockSideTopLeft(mDockSide) 525 ? LOG_VALUE_UNDOCK_MAX_OTHER 526 : LOG_VALUE_UNDOCK_MAX_DOCKED); 527 } else if (snapTarget == mSnapAlgorithm.getDismissEndTarget()) { 528 MetricsLogger.action( 529 mContext, MetricsEvent.ACTION_WINDOW_UNDOCK_MAX, dockSideBottomRight(mDockSide) 530 ? LOG_VALUE_UNDOCK_MAX_OTHER 531 : LOG_VALUE_UNDOCK_MAX_DOCKED); 532 } else if (snapTarget == mSnapAlgorithm.getMiddleTarget()) { 533 MetricsLogger.action(mContext, MetricsEvent.ACTION_WINDOW_DOCK_RESIZE, 534 LOG_VALUE_RESIZE_50_50); 535 } else if (snapTarget == mSnapAlgorithm.getFirstSplitTarget()) { 536 MetricsLogger.action(mContext, MetricsEvent.ACTION_WINDOW_DOCK_RESIZE, 537 dockSideTopLeft(mDockSide) 538 ? LOG_VALUE_RESIZE_DOCKED_SMALLER 539 : LOG_VALUE_RESIZE_DOCKED_LARGER); 540 } else if (snapTarget == mSnapAlgorithm.getLastSplitTarget()) { 541 MetricsLogger.action(mContext, MetricsEvent.ACTION_WINDOW_DOCK_RESIZE, 542 dockSideTopLeft(mDockSide) 543 ? LOG_VALUE_RESIZE_DOCKED_LARGER 544 : LOG_VALUE_RESIZE_DOCKED_SMALLER); 545 } 546 } 547 548 private void convertToScreenCoordinates(MotionEvent event) { 549 event.setLocation(event.getRawX(), event.getRawY()); 550 } 551 552 private void fling(int position, float velocity, boolean avoidDismissStart, 553 boolean logMetrics) { 554 SnapTarget snapTarget = mSnapAlgorithm.calculateSnapTarget(position, velocity); 555 if (avoidDismissStart && snapTarget == mSnapAlgorithm.getDismissStartTarget()) { 556 snapTarget = mSnapAlgorithm.getFirstSplitTarget(); 557 } 558 if (logMetrics) { 559 logResizeEvent(snapTarget); 560 } 561 ValueAnimator anim = getFlingAnimator(position, snapTarget, 0 /* endDelay */); 562 mFlingAnimationUtils.apply(anim, position, snapTarget.position, velocity); 563 anim.start(); 564 } 565 566 private void flingTo(int position, SnapTarget target, long duration, long startDelay, 567 long endDelay, Interpolator interpolator) { 568 ValueAnimator anim = getFlingAnimator(position, target, endDelay); 569 anim.setDuration(duration); 570 anim.setStartDelay(startDelay); 571 anim.setInterpolator(interpolator); 572 anim.start(); 573 } 574 575 private ValueAnimator getFlingAnimator(int position, final SnapTarget snapTarget, 576 final long endDelay) { 577 final boolean taskPositionSameAtEnd = snapTarget.flag == SnapTarget.FLAG_NONE; 578 ValueAnimator anim = ValueAnimator.ofInt(position, snapTarget.position); 579 anim.addUpdateListener(animation -> resizeStackDelayed((int) animation.getAnimatedValue(), 580 taskPositionSameAtEnd && animation.getAnimatedFraction() == 1f 581 ? TASK_POSITION_SAME 582 : snapTarget.taskPosition, 583 snapTarget)); 584 Runnable endAction = () -> { 585 commitSnapFlags(snapTarget); 586 mWindowManagerProxy.setResizing(false); 587 mDockSide = WindowManager.DOCKED_INVALID; 588 mCurrentAnimator = null; 589 mEntranceAnimationRunning = false; 590 mExitAnimationRunning = false; 591 EventBus.getDefault().send(new StoppedDragingEvent()); 592 }; 593 anim.addListener(new AnimatorListenerAdapter() { 594 595 private boolean mCancelled; 596 597 @Override 598 public void onAnimationCancel(Animator animation) { 599 mHandler.removeMessages(MSG_RESIZE_STACK); 600 mCancelled = true; 601 } 602 603 @Override 604 public void onAnimationEnd(Animator animation) { 605 long delay = 0; 606 if (endDelay != 0) { 607 delay = endDelay; 608 } else if (mCancelled) { 609 delay = 0; 610 } else if (mSurfaceFlingerOffsetMs != 0) { 611 delay = mSurfaceFlingerOffsetMs; 612 } 613 if (delay == 0) { 614 endAction.run(); 615 } else { 616 mHandler.postDelayed(endAction, delay); 617 } 618 } 619 }); 620 mCurrentAnimator = anim; 621 return anim; 622 } 623 624 private void cancelFlingAnimation() { 625 if (mCurrentAnimator != null) { 626 mCurrentAnimator.cancel(); 627 } 628 } 629 630 private void commitSnapFlags(SnapTarget target) { 631 if (target.flag == SnapTarget.FLAG_NONE) { 632 return; 633 } 634 boolean dismissOrMaximize; 635 if (target.flag == SnapTarget.FLAG_DISMISS_START) { 636 dismissOrMaximize = mDockSide == WindowManager.DOCKED_LEFT 637 || mDockSide == WindowManager.DOCKED_TOP; 638 } else { 639 dismissOrMaximize = mDockSide == WindowManager.DOCKED_RIGHT 640 || mDockSide == WindowManager.DOCKED_BOTTOM; 641 } 642 if (dismissOrMaximize) { 643 mWindowManagerProxy.dismissDockedStack(); 644 } else { 645 mWindowManagerProxy.maximizeDockedStack(); 646 } 647 mWindowManagerProxy.setResizeDimLayer(false, -1, 0f); 648 } 649 650 private void liftBackground() { 651 if (mBackgroundLifted) { 652 return; 653 } 654 if (isHorizontalDivision()) { 655 mBackground.animate().scaleY(1.4f); 656 } else { 657 mBackground.animate().scaleX(1.4f); 658 } 659 mBackground.animate() 660 .setInterpolator(Interpolators.TOUCH_RESPONSE) 661 .setDuration(TOUCH_ANIMATION_DURATION) 662 .translationZ(mTouchElevation) 663 .start(); 664 665 // Lift handle as well so it doesn't get behind the background, even though it doesn't 666 // cast shadow. 667 mHandle.animate() 668 .setInterpolator(Interpolators.TOUCH_RESPONSE) 669 .setDuration(TOUCH_ANIMATION_DURATION) 670 .translationZ(mTouchElevation) 671 .start(); 672 mBackgroundLifted = true; 673 } 674 675 private void releaseBackground() { 676 if (!mBackgroundLifted) { 677 return; 678 } 679 mBackground.animate() 680 .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) 681 .setDuration(TOUCH_RELEASE_ANIMATION_DURATION) 682 .translationZ(0) 683 .scaleX(1f) 684 .scaleY(1f) 685 .start(); 686 mHandle.animate() 687 .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) 688 .setDuration(TOUCH_RELEASE_ANIMATION_DURATION) 689 .translationZ(0) 690 .start(); 691 mBackgroundLifted = false; 692 } 693 694 695 public void setMinimizedDockStack(boolean minimized) { 696 updateDockSide(); 697 mHandle.setAlpha(minimized ? 0f : 1f); 698 if (!minimized) { 699 resetBackground(); 700 } else if (mDockSide == WindowManager.DOCKED_TOP) { 701 mBackground.setPivotY(0); 702 mBackground.setScaleY(MINIMIZE_DOCK_SCALE); 703 } else if (mDockSide == WindowManager.DOCKED_LEFT 704 || mDockSide == WindowManager.DOCKED_RIGHT) { 705 mBackground.setPivotX(mDockSide == WindowManager.DOCKED_LEFT 706 ? 0 707 : mBackground.getWidth()); 708 mBackground.setScaleX(MINIMIZE_DOCK_SCALE); 709 } 710 mMinimizedShadow.setAlpha(minimized ? 1f : 0f); 711 mDockedStackMinimized = minimized; 712 } 713 714 public void setMinimizedDockStack(boolean minimized, long animDuration) { 715 updateDockSide(); 716 mHandle.animate() 717 .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) 718 .setDuration(animDuration) 719 .alpha(minimized ? 0f : 1f) 720 .start(); 721 if (mDockSide == WindowManager.DOCKED_TOP) { 722 mBackground.setPivotY(0); 723 mBackground.animate() 724 .scaleY(minimized ? MINIMIZE_DOCK_SCALE : 1f); 725 } else if (mDockSide == WindowManager.DOCKED_LEFT 726 || mDockSide == WindowManager.DOCKED_RIGHT) { 727 mBackground.setPivotX(mDockSide == WindowManager.DOCKED_LEFT 728 ? 0 729 : mBackground.getWidth()); 730 mBackground.animate() 731 .scaleX(minimized ? MINIMIZE_DOCK_SCALE : 1f); 732 } 733 if (!minimized) { 734 mBackground.animate().withEndAction(mResetBackgroundRunnable); 735 } 736 mMinimizedShadow.animate() 737 .alpha(minimized ? 1f : 0f) 738 .setInterpolator(Interpolators.ALPHA_IN) 739 .setDuration(animDuration) 740 .start(); 741 mBackground.animate() 742 .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) 743 .setDuration(animDuration) 744 .start(); 745 mDockedStackMinimized = minimized; 746 } 747 748 public void setAdjustedForIme(boolean adjustedForIme) { 749 updateDockSide(); 750 mHandle.setAlpha(adjustedForIme ? 0f : 1f); 751 if (!adjustedForIme) { 752 resetBackground(); 753 } else if (mDockSide == WindowManager.DOCKED_TOP) { 754 mBackground.setPivotY(0); 755 mBackground.setScaleY(ADJUSTED_FOR_IME_SCALE); 756 } 757 mAdjustedForIme = adjustedForIme; 758 } 759 760 public void setAdjustedForIme(boolean adjustedForIme, long animDuration) { 761 updateDockSide(); 762 mHandle.animate() 763 .setInterpolator(IME_ADJUST_INTERPOLATOR) 764 .setDuration(animDuration) 765 .alpha(adjustedForIme ? 0f : 1f) 766 .start(); 767 if (mDockSide == WindowManager.DOCKED_TOP) { 768 mBackground.setPivotY(0); 769 mBackground.animate() 770 .scaleY(adjustedForIme ? ADJUSTED_FOR_IME_SCALE : 1f); 771 } 772 if (!adjustedForIme) { 773 mBackground.animate().withEndAction(mResetBackgroundRunnable); 774 } 775 mBackground.animate() 776 .setInterpolator(IME_ADJUST_INTERPOLATOR) 777 .setDuration(animDuration) 778 .start(); 779 mAdjustedForIme = adjustedForIme; 780 } 781 782 private void resetBackground() { 783 mBackground.setPivotX(mBackground.getWidth() / 2); 784 mBackground.setPivotY(mBackground.getHeight() / 2); 785 mBackground.setScaleX(1f); 786 mBackground.setScaleY(1f); 787 mMinimizedShadow.setAlpha(0f); 788 } 789 790 @Override 791 protected void onConfigurationChanged(Configuration newConfig) { 792 super.onConfigurationChanged(newConfig); 793 updateDisplayInfo(); 794 } 795 796 797 public void notifyDockSideChanged(int newDockSide) { 798 mDockSide = newDockSide; 799 mMinimizedShadow.setDockSide(mDockSide); 800 requestLayout(); 801 } 802 803 private void updateDisplayInfo() { 804 final DisplayManager displayManager = 805 (DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE); 806 Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY); 807 final DisplayInfo info = new DisplayInfo(); 808 display.getDisplayInfo(info); 809 mDisplayWidth = info.logicalWidth; 810 mDisplayHeight = info.logicalHeight; 811 mSnapAlgorithm = null; 812 initializeSnapAlgorithm(); 813 } 814 815 private int calculatePosition(int touchX, int touchY) { 816 return isHorizontalDivision() ? calculateYPosition(touchY) : calculateXPosition(touchX); 817 } 818 819 public boolean isHorizontalDivision() { 820 return getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT; 821 } 822 823 private int calculateXPosition(int touchX) { 824 return mStartPosition + touchX - mStartX; 825 } 826 827 private int calculateYPosition(int touchY) { 828 return mStartPosition + touchY - mStartY; 829 } 830 831 private void alignTopLeft(Rect containingRect, Rect rect) { 832 int width = rect.width(); 833 int height = rect.height(); 834 rect.set(containingRect.left, containingRect.top, 835 containingRect.left + width, containingRect.top + height); 836 } 837 838 private void alignBottomRight(Rect containingRect, Rect rect) { 839 int width = rect.width(); 840 int height = rect.height(); 841 rect.set(containingRect.right - width, containingRect.bottom - height, 842 containingRect.right, containingRect.bottom); 843 } 844 845 public void calculateBoundsForPosition(int position, int dockSide, Rect outRect) { 846 DockedDividerUtils.calculateBoundsForPosition(position, dockSide, outRect, mDisplayWidth, 847 mDisplayHeight, mDividerSize); 848 } 849 850 public void resizeStackDelayed(int position, int taskPosition, SnapTarget taskSnapTarget) { 851 if (mSurfaceFlingerOffsetMs != 0) { 852 Message message = mHandler.obtainMessage(MSG_RESIZE_STACK, position, taskPosition, 853 taskSnapTarget); 854 message.setAsynchronous(true); 855 mHandler.sendMessageDelayed(message, mSurfaceFlingerOffsetMs); 856 } else { 857 resizeStack(position, taskPosition, taskSnapTarget); 858 } 859 } 860 861 public void resizeStack(int position, int taskPosition, SnapTarget taskSnapTarget) { 862 calculateBoundsForPosition(position, mDockSide, mDockedRect); 863 864 if (mDockedRect.equals(mLastResizeRect) && !mEntranceAnimationRunning) { 865 return; 866 } 867 868 // Make sure shadows are updated 869 if (mBackground.getZ() > 0f) { 870 mBackground.invalidate(); 871 } 872 873 mLastResizeRect.set(mDockedRect); 874 if (mEntranceAnimationRunning && taskPosition != TASK_POSITION_SAME) { 875 if (mCurrentAnimator != null) { 876 calculateBoundsForPosition(taskPosition, mDockSide, mDockedTaskRect); 877 } else { 878 calculateBoundsForPosition(isHorizontalDivision() ? mDisplayHeight : mDisplayWidth, 879 mDockSide, mDockedTaskRect); 880 } 881 calculateBoundsForPosition(taskPosition, DockedDividerUtils.invertDockSide(mDockSide), 882 mOtherTaskRect); 883 mWindowManagerProxy.resizeDockedStack(mDockedRect, mDockedTaskRect, null, 884 mOtherTaskRect, null); 885 } else if (mExitAnimationRunning && taskPosition != TASK_POSITION_SAME) { 886 calculateBoundsForPosition(taskPosition, 887 mDockSide, mDockedTaskRect); 888 calculateBoundsForPosition(mExitStartPosition, 889 DockedDividerUtils.invertDockSide(mDockSide), mOtherTaskRect); 890 mOtherInsetRect.set(mOtherTaskRect); 891 applyExitAnimationParallax(mOtherTaskRect, position); 892 mWindowManagerProxy.resizeDockedStack(mDockedRect, mDockedTaskRect, null, 893 mOtherTaskRect, mOtherInsetRect); 894 } else if (taskPosition != TASK_POSITION_SAME) { 895 calculateBoundsForPosition(position, DockedDividerUtils.invertDockSide(mDockSide), 896 mOtherRect); 897 int dockSideInverted = DockedDividerUtils.invertDockSide(mDockSide); 898 int taskPositionDocked = 899 restrictDismissingTaskPosition(taskPosition, mDockSide, taskSnapTarget); 900 int taskPositionOther = 901 restrictDismissingTaskPosition(taskPosition, dockSideInverted, taskSnapTarget); 902 calculateBoundsForPosition(taskPositionDocked, mDockSide, mDockedTaskRect); 903 calculateBoundsForPosition(taskPositionOther, dockSideInverted, mOtherTaskRect); 904 mDisplayRect.set(0, 0, mDisplayWidth, mDisplayHeight); 905 alignTopLeft(mDockedRect, mDockedTaskRect); 906 alignTopLeft(mOtherRect, mOtherTaskRect); 907 mDockedInsetRect.set(mDockedTaskRect); 908 mOtherInsetRect.set(mOtherTaskRect); 909 if (dockSideTopLeft(mDockSide)) { 910 alignTopLeft(mDisplayRect, mDockedInsetRect); 911 alignBottomRight(mDisplayRect, mOtherInsetRect); 912 } else { 913 alignBottomRight(mDisplayRect, mDockedInsetRect); 914 alignTopLeft(mDisplayRect, mOtherInsetRect); 915 } 916 applyDismissingParallax(mDockedTaskRect, mDockSide, taskSnapTarget, position, 917 taskPositionDocked); 918 applyDismissingParallax(mOtherTaskRect, dockSideInverted, taskSnapTarget, position, 919 taskPositionOther); 920 mWindowManagerProxy.resizeDockedStack(mDockedRect, mDockedTaskRect, mDockedInsetRect, 921 mOtherTaskRect, mOtherInsetRect); 922 } else { 923 mWindowManagerProxy.resizeDockedStack(mDockedRect, null, null, null, null); 924 } 925 SnapTarget closestDismissTarget = mSnapAlgorithm.getClosestDismissTarget(position); 926 float dimFraction = getDimFraction(position, closestDismissTarget); 927 mWindowManagerProxy.setResizeDimLayer(dimFraction != 0f, 928 getStackIdForDismissTarget(closestDismissTarget), 929 dimFraction); 930 } 931 932 private void applyExitAnimationParallax(Rect taskRect, int position) { 933 if (mDockSide == WindowManager.DOCKED_TOP) { 934 taskRect.offset(0, (int) ((position - mExitStartPosition) * 0.25f)); 935 } else if (mDockSide == WindowManager.DOCKED_LEFT) { 936 taskRect.offset((int) ((position - mExitStartPosition) * 0.25f), 0); 937 } else if (mDockSide == WindowManager.DOCKED_RIGHT) { 938 taskRect.offset((int) ((mExitStartPosition - position) * 0.25f), 0); 939 } 940 } 941 942 private float getDimFraction(int position, SnapTarget dismissTarget) { 943 if (mEntranceAnimationRunning) { 944 return 0f; 945 } 946 float fraction = mSnapAlgorithm.calculateDismissingFraction(position); 947 fraction = Math.max(0, Math.min(fraction, 1f)); 948 fraction = DIM_INTERPOLATOR.getInterpolation(fraction); 949 if (hasInsetsAtDismissTarget(dismissTarget)) { 950 951 // Less darkening with system insets. 952 fraction *= 0.8f; 953 } 954 return fraction; 955 } 956 957 /** 958 * @return true if and only if there are system insets at the location of the dismiss target 959 */ 960 private boolean hasInsetsAtDismissTarget(SnapTarget dismissTarget) { 961 if (isHorizontalDivision()) { 962 if (dismissTarget == mSnapAlgorithm.getDismissStartTarget()) { 963 return mStableInsets.top != 0; 964 } else { 965 return mStableInsets.bottom != 0; 966 } 967 } else { 968 if (dismissTarget == mSnapAlgorithm.getDismissStartTarget()) { 969 return mStableInsets.left != 0; 970 } else { 971 return mStableInsets.right != 0; 972 } 973 } 974 } 975 976 /** 977 * When the snap target is dismissing one side, make sure that the dismissing side doesn't get 978 * 0 size. 979 */ 980 private int restrictDismissingTaskPosition(int taskPosition, int dockSide, 981 SnapTarget snapTarget) { 982 if (snapTarget.flag == SnapTarget.FLAG_DISMISS_START && dockSideTopLeft(dockSide)) { 983 return Math.max(mSnapAlgorithm.getFirstSplitTarget().position, mStartPosition); 984 } else if (snapTarget.flag == SnapTarget.FLAG_DISMISS_END 985 && dockSideBottomRight(dockSide)) { 986 return Math.min(mSnapAlgorithm.getLastSplitTarget().position, mStartPosition); 987 } else { 988 return taskPosition; 989 } 990 } 991 992 /** 993 * Applies a parallax to the task when dismissing. 994 */ 995 private void applyDismissingParallax(Rect taskRect, int dockSide, SnapTarget snapTarget, 996 int position, int taskPosition) { 997 float fraction = Math.min(1, Math.max(0, 998 mSnapAlgorithm.calculateDismissingFraction(position))); 999 SnapTarget dismissTarget = null; 1000 SnapTarget splitTarget = null; 1001 int start = 0; 1002 if (position <= mSnapAlgorithm.getLastSplitTarget().position 1003 && dockSideTopLeft(dockSide)) { 1004 dismissTarget = mSnapAlgorithm.getDismissStartTarget(); 1005 splitTarget = mSnapAlgorithm.getFirstSplitTarget(); 1006 start = taskPosition; 1007 } else if (position >= mSnapAlgorithm.getLastSplitTarget().position 1008 && dockSideBottomRight(dockSide)) { 1009 dismissTarget = mSnapAlgorithm.getDismissEndTarget(); 1010 splitTarget = mSnapAlgorithm.getLastSplitTarget(); 1011 start = splitTarget.position; 1012 } 1013 if (dismissTarget != null && fraction > 0f 1014 && isDismissing(splitTarget, position, dockSide)) { 1015 fraction = calculateParallaxDismissingFraction(fraction, dockSide); 1016 int offsetPosition = (int) (start + 1017 fraction * (dismissTarget.position - splitTarget.position)); 1018 int width = taskRect.width(); 1019 int height = taskRect.height(); 1020 switch (dockSide) { 1021 case WindowManager.DOCKED_LEFT: 1022 taskRect.left = offsetPosition - width; 1023 taskRect.right = offsetPosition; 1024 break; 1025 case WindowManager.DOCKED_RIGHT: 1026 taskRect.left = offsetPosition + mDividerSize; 1027 taskRect.right = offsetPosition + width + mDividerSize; 1028 break; 1029 case WindowManager.DOCKED_TOP: 1030 taskRect.top = offsetPosition - height; 1031 taskRect.bottom = offsetPosition; 1032 break; 1033 case WindowManager.DOCKED_BOTTOM: 1034 taskRect.top = offsetPosition + mDividerSize; 1035 taskRect.bottom = offsetPosition + height + mDividerSize; 1036 break; 1037 } 1038 } 1039 } 1040 1041 /** 1042 * @return for a specified {@code fraction}, this returns an adjusted value that simulates a 1043 * slowing down parallax effect 1044 */ 1045 private static float calculateParallaxDismissingFraction(float fraction, int dockSide) { 1046 float result = SLOWDOWN_INTERPOLATOR.getInterpolation(fraction) / 3.5f; 1047 1048 // Less parallax at the top, just because. 1049 if (dockSide == WindowManager.DOCKED_TOP) { 1050 result /= 2f; 1051 } 1052 return result; 1053 } 1054 1055 private static boolean isDismissing(SnapTarget snapTarget, int position, int dockSide) { 1056 if (dockSide == WindowManager.DOCKED_TOP || dockSide == WindowManager.DOCKED_LEFT) { 1057 return position < snapTarget.position; 1058 } else { 1059 return position > snapTarget.position; 1060 } 1061 } 1062 1063 private int getStackIdForDismissTarget(SnapTarget dismissTarget) { 1064 if ((dismissTarget.flag == SnapTarget.FLAG_DISMISS_START && dockSideTopLeft(mDockSide)) 1065 || (dismissTarget.flag == SnapTarget.FLAG_DISMISS_END 1066 && dockSideBottomRight(mDockSide))) { 1067 return StackId.DOCKED_STACK_ID; 1068 } else { 1069 return StackId.HOME_STACK_ID; 1070 } 1071 } 1072 1073 /** 1074 * @return true if and only if {@code dockSide} is top or left 1075 */ 1076 private static boolean dockSideTopLeft(int dockSide) { 1077 return dockSide == WindowManager.DOCKED_TOP || dockSide == WindowManager.DOCKED_LEFT; 1078 } 1079 1080 /** 1081 * @return true if and only if {@code dockSide} is bottom or right 1082 */ 1083 private static boolean dockSideBottomRight(int dockSide) { 1084 return dockSide == WindowManager.DOCKED_BOTTOM || dockSide == WindowManager.DOCKED_RIGHT; 1085 } 1086 1087 @Override 1088 public void onComputeInternalInsets(InternalInsetsInfo inoutInfo) { 1089 inoutInfo.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_REGION); 1090 inoutInfo.touchableRegion.set(mHandle.getLeft(), mHandle.getTop(), mHandle.getRight(), 1091 mHandle.getBottom()); 1092 inoutInfo.touchableRegion.op(mBackground.getLeft(), mBackground.getTop(), 1093 mBackground.getRight(), mBackground.getBottom(), Op.UNION); 1094 } 1095 1096 /** 1097 * Checks whether recents will grow when invoked. This happens in multi-window when recents is 1098 * very small. When invoking recents, we shrink the docked stack so recents has more space. 1099 * 1100 * @return the position of the divider when recents grows, or 1101 * {@link #INVALID_RECENTS_GROW_TARGET} if recents won't grow 1102 */ 1103 public int growsRecents() { 1104 boolean result = mGrowRecents 1105 && mWindowManagerProxy.getDockSide() == WindowManager.DOCKED_TOP 1106 && getCurrentPosition() == getSnapAlgorithm().getLastSplitTarget().position; 1107 if (result) { 1108 return getSnapAlgorithm().getMiddleTarget().position; 1109 } else { 1110 return INVALID_RECENTS_GROW_TARGET; 1111 } 1112 } 1113 1114 public final void onBusEvent(RecentsActivityStartingEvent recentsActivityStartingEvent) { 1115 if (mGrowRecents && getWindowManagerProxy().getDockSide() == WindowManager.DOCKED_TOP 1116 && getCurrentPosition() == getSnapAlgorithm().getLastSplitTarget().position) { 1117 mState.growAfterRecentsDrawn = true; 1118 startDragging(false /* animate */, false /* touching */); 1119 } 1120 } 1121 1122 public final void onBusEvent(DockedTopTaskEvent event) { 1123 if (event.dragMode == NavigationBarGestureHelper.DRAG_MODE_NONE) { 1124 mState.growAfterRecentsDrawn = false; 1125 mState.animateAfterRecentsDrawn = true; 1126 startDragging(false /* animate */, false /* touching */); 1127 } 1128 updateDockSide(); 1129 int position = DockedDividerUtils.calculatePositionForBounds(event.initialRect, 1130 mDockSide, mDividerSize); 1131 mEntranceAnimationRunning = true; 1132 1133 // Insets might not have been fetched yet, so fetch manually if needed. 1134 if (mStableInsets.isEmpty()) { 1135 SystemServicesProxy.getInstance(mContext).getStableInsets(mStableInsets); 1136 mSnapAlgorithm = null; 1137 initializeSnapAlgorithm(); 1138 } 1139 1140 resizeStack(position, mSnapAlgorithm.getMiddleTarget().position, 1141 mSnapAlgorithm.getMiddleTarget()); 1142 } 1143 1144 public final void onBusEvent(RecentsDrawnEvent drawnEvent) { 1145 if (mState.animateAfterRecentsDrawn) { 1146 mState.animateAfterRecentsDrawn = false; 1147 updateDockSide(); 1148 1149 mHandler.post(() -> { 1150 // Delay switching resizing mode because this might cause jank in recents animation 1151 // that's longer than this animation. 1152 stopDragging(getCurrentPosition(), mSnapAlgorithm.getMiddleTarget(), 1153 mLongPressEntraceAnimDuration, Interpolators.FAST_OUT_SLOW_IN, 1154 200 /* endDelay */); 1155 }); 1156 } 1157 if (mState.growAfterRecentsDrawn) { 1158 mState.growAfterRecentsDrawn = false; 1159 updateDockSide(); 1160 EventBus.getDefault().send(new RecentsGrowingEvent()); 1161 stopDragging(getCurrentPosition(), mSnapAlgorithm.getMiddleTarget(), 336, 1162 Interpolators.FAST_OUT_SLOW_IN); 1163 } 1164 } 1165 1166 public final void onBusEvent(UndockingTaskEvent undockingTaskEvent) { 1167 int dockSide = mWindowManagerProxy.getDockSide(); 1168 if (dockSide != WindowManager.DOCKED_INVALID && !mDockedStackMinimized) { 1169 startDragging(false /* animate */, false /* touching */); 1170 SnapTarget target = dockSideTopLeft(dockSide) 1171 ? mSnapAlgorithm.getDismissEndTarget() 1172 : mSnapAlgorithm.getDismissStartTarget(); 1173 1174 // Don't start immediately - give a little bit time to settle the drag resize change. 1175 mExitAnimationRunning = true; 1176 mExitStartPosition = getCurrentPosition(); 1177 stopDragging(mExitStartPosition, target, 336 /* duration */, 100 /* startDelay */, 1178 0 /* endDelay */, Interpolators.FAST_OUT_SLOW_IN); 1179 } 1180 } 1181 } 1182