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