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