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