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