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