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