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