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 WindowManagerProxy.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 post(() -> { 849 final SurfaceControl sc = getWindowSurfaceControl(); 850 if (sc == null) { 851 return; 852 } 853 Transaction t = mTiles.getTransaction(); 854 t.show(sc).apply(); 855 mTiles.releaseTransaction(t); 856 }); 857 858 SnapTarget miniMid = 859 mSplitLayout.getMinimizedSnapAlgorithm(isHomeStackResizable).getMiddleTarget(); 860 if (mDockedStackMinimized) { 861 mDividerPositionY = mDividerPositionX = miniMid.position; 862 } 863 } 864 865 /** 866 * Tries to grab a surface control from ViewRootImpl. If this isn't available for some reason 867 * (ie. the window isn't ready yet), it will get the surfacecontrol that the WindowlessWM has 868 * assigned to it. 869 */ getWindowSurfaceControl()870 private SurfaceControl getWindowSurfaceControl() { 871 final ViewRootImpl root = getViewRootImpl(); 872 if (root == null) { 873 return null; 874 } 875 SurfaceControl out = root.getSurfaceControl(); 876 if (out != null && out.isValid()) { 877 return out; 878 } 879 return mWindowManager.mSystemWindows.getViewSurface(this); 880 } 881 exitSplitMode()882 void exitSplitMode() { 883 // Reset tile bounds 884 final SurfaceControl sc = getWindowSurfaceControl(); 885 if (sc == null) { 886 return; 887 } 888 Transaction t = mTiles.getTransaction(); 889 t.hide(sc).apply(); 890 mTiles.releaseTransaction(t); 891 int midPos = mSplitLayout.getSnapAlgorithm().getMiddleTarget().position; 892 WindowManagerProxy.applyResizeSplits(midPos, mSplitLayout); 893 } 894 setMinimizedDockStack(boolean minimized, long animDuration, boolean isHomeStackResizable)895 public void setMinimizedDockStack(boolean minimized, long animDuration, 896 boolean isHomeStackResizable) { 897 if (DEBUG) Slog.d(TAG, "setMinDock: " + mDockedStackMinimized + "->" + minimized); 898 mHomeStackResizable = isHomeStackResizable; 899 updateDockSide(); 900 if (mDockedStackMinimized != minimized) { 901 mIsInMinimizeInteraction = true; 902 mDockedStackMinimized = minimized; 903 stopDragging(minimized 904 ? mSnapTargetBeforeMinimized.position 905 : getCurrentPosition(), 906 minimized 907 ? mSplitLayout.getMinimizedSnapAlgorithm(mHomeStackResizable) 908 .getMiddleTarget() 909 : mSnapTargetBeforeMinimized, 910 animDuration, Interpolators.FAST_OUT_SLOW_IN, 0); 911 setAdjustedForIme(false, animDuration); 912 } 913 if (!minimized) { 914 mBackground.animate().withEndAction(mResetBackgroundRunnable); 915 } 916 mBackground.animate() 917 .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) 918 .setDuration(animDuration) 919 .start(); 920 } 921 922 // Needed to end any currently playing animations when they might compete with other anims 923 // (specifically, IME adjust animation immediately after leaving minimized). Someday maybe 924 // these can be unified, but not today. finishAnimations()925 void finishAnimations() { 926 if (mCurrentAnimator != null) { 927 mCurrentAnimator.end(); 928 } 929 } 930 setAdjustedForIme(boolean adjustedForIme, long animDuration)931 public void setAdjustedForIme(boolean adjustedForIme, long animDuration) { 932 if (mAdjustedForIme == adjustedForIme) { 933 return; 934 } 935 updateDockSide(); 936 mHandle.animate() 937 .setInterpolator(IME_ADJUST_INTERPOLATOR) 938 .setDuration(animDuration) 939 .alpha(adjustedForIme ? 0f : 1f) 940 .start(); 941 if (mDockSide == WindowManager.DOCKED_TOP) { 942 mBackground.setPivotY(0); 943 mBackground.animate() 944 .scaleY(adjustedForIme ? ADJUSTED_FOR_IME_SCALE : 1f); 945 } 946 if (!adjustedForIme) { 947 mBackground.animate().withEndAction(mResetBackgroundRunnable); 948 } 949 mBackground.animate() 950 .setInterpolator(IME_ADJUST_INTERPOLATOR) 951 .setDuration(animDuration) 952 .start(); 953 mAdjustedForIme = adjustedForIme; 954 } 955 saveSnapTargetBeforeMinimized(SnapTarget target)956 private void saveSnapTargetBeforeMinimized(SnapTarget target) { 957 mSnapTargetBeforeMinimized = target; 958 mState.mRatioPositionBeforeMinimized = (float) target.position / 959 (isHorizontalDivision() ? mSplitLayout.mDisplayLayout.height() 960 : mSplitLayout.mDisplayLayout.width()); 961 } 962 resetBackground()963 private void resetBackground() { 964 mBackground.setPivotX(mBackground.getWidth() / 2); 965 mBackground.setPivotY(mBackground.getHeight() / 2); 966 mBackground.setScaleX(1f); 967 mBackground.setScaleY(1f); 968 mMinimizedShadow.setAlpha(0f); 969 } 970 971 @Override onConfigurationChanged(Configuration newConfig)972 protected void onConfigurationChanged(Configuration newConfig) { 973 super.onConfigurationChanged(newConfig); 974 } 975 repositionSnapTargetBeforeMinimized()976 private void repositionSnapTargetBeforeMinimized() { 977 int position = (int) (mState.mRatioPositionBeforeMinimized * 978 (isHorizontalDivision() ? mSplitLayout.mDisplayLayout.height() 979 : mSplitLayout.mDisplayLayout.width())); 980 981 // Set the snap target before minimized but do not save until divider is attached and not 982 // minimized because it does not know its minimized state yet. 983 mSnapTargetBeforeMinimized = 984 mSplitLayout.getSnapAlgorithm().calculateNonDismissingSnapTarget(position); 985 } 986 calculatePosition(int touchX, int touchY)987 private int calculatePosition(int touchX, int touchY) { 988 return isHorizontalDivision() ? calculateYPosition(touchY) : calculateXPosition(touchX); 989 } 990 isHorizontalDivision()991 public boolean isHorizontalDivision() { 992 return getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT; 993 } 994 calculateXPosition(int touchX)995 private int calculateXPosition(int touchX) { 996 return mStartPosition + touchX - mStartX; 997 } 998 calculateYPosition(int touchY)999 private int calculateYPosition(int touchY) { 1000 return mStartPosition + touchY - mStartY; 1001 } 1002 alignTopLeft(Rect containingRect, Rect rect)1003 private void alignTopLeft(Rect containingRect, Rect rect) { 1004 int width = rect.width(); 1005 int height = rect.height(); 1006 rect.set(containingRect.left, containingRect.top, 1007 containingRect.left + width, containingRect.top + height); 1008 } 1009 alignBottomRight(Rect containingRect, Rect rect)1010 private void alignBottomRight(Rect containingRect, Rect rect) { 1011 int width = rect.width(); 1012 int height = rect.height(); 1013 rect.set(containingRect.right - width, containingRect.bottom - height, 1014 containingRect.right, containingRect.bottom); 1015 } 1016 calculateBoundsForPosition(int position, int dockSide, Rect outRect)1017 public void calculateBoundsForPosition(int position, int dockSide, Rect outRect) { 1018 DockedDividerUtils.calculateBoundsForPosition(position, dockSide, outRect, 1019 mSplitLayout.mDisplayLayout.width(), mSplitLayout.mDisplayLayout.height(), 1020 mDividerSize); 1021 } 1022 resizeStackSurfaces(SnapTarget taskSnapTarget, Transaction t)1023 private void resizeStackSurfaces(SnapTarget taskSnapTarget, Transaction t) { 1024 resizeStackSurfaces(taskSnapTarget.position, taskSnapTarget.position, taskSnapTarget, t); 1025 } 1026 resizeSplitSurfaces(Transaction t, Rect dockedRect, Rect otherRect)1027 void resizeSplitSurfaces(Transaction t, Rect dockedRect, Rect otherRect) { 1028 resizeSplitSurfaces(t, dockedRect, null, otherRect, null); 1029 } 1030 resizeSplitSurfaces(Transaction t, Rect dockedRect, Rect dockedTaskRect, Rect otherRect, Rect otherTaskRect)1031 private void resizeSplitSurfaces(Transaction t, Rect dockedRect, Rect dockedTaskRect, 1032 Rect otherRect, Rect otherTaskRect) { 1033 dockedTaskRect = dockedTaskRect == null ? dockedRect : dockedTaskRect; 1034 otherTaskRect = otherTaskRect == null ? otherRect : otherTaskRect; 1035 1036 mDividerPositionX = mSplitLayout.getPrimarySplitSide() == DOCKED_RIGHT 1037 ? otherRect.right : dockedRect.right; 1038 mDividerPositionY = dockedRect.bottom; 1039 1040 if (DEBUG) { 1041 Slog.d(TAG, "Resizing split surfaces: " + dockedRect + " " + dockedTaskRect 1042 + " " + otherRect + " " + otherTaskRect); 1043 } 1044 1045 t.setPosition(mTiles.mPrimarySurface, dockedTaskRect.left, dockedTaskRect.top); 1046 Rect crop = new Rect(dockedRect); 1047 crop.offsetTo(-Math.min(dockedTaskRect.left - dockedRect.left, 0), 1048 -Math.min(dockedTaskRect.top - dockedRect.top, 0)); 1049 t.setWindowCrop(mTiles.mPrimarySurface, crop); 1050 t.setPosition(mTiles.mSecondarySurface, otherTaskRect.left, otherTaskRect.top); 1051 crop.set(otherRect); 1052 crop.offsetTo(-(otherTaskRect.left - otherRect.left), 1053 -(otherTaskRect.top - otherRect.top)); 1054 t.setWindowCrop(mTiles.mSecondarySurface, crop); 1055 final SurfaceControl dividerCtrl = getWindowSurfaceControl(); 1056 if (dividerCtrl != null) { 1057 if (isHorizontalDivision()) { 1058 t.setPosition(dividerCtrl, 0, mDividerPositionY - mDividerInsets); 1059 } else { 1060 t.setPosition(dividerCtrl, mDividerPositionX - mDividerInsets, 0); 1061 } 1062 } 1063 if (getViewRootImpl() != null) { 1064 mHandler.removeCallbacks(mUpdateEmbeddedMatrix); 1065 mHandler.post(mUpdateEmbeddedMatrix); 1066 } 1067 } 1068 setResizeDimLayer(Transaction t, boolean primary, float alpha)1069 void setResizeDimLayer(Transaction t, boolean primary, float alpha) { 1070 SurfaceControl dim = primary ? mTiles.mPrimaryDim : mTiles.mSecondaryDim; 1071 if (alpha <= 0.001f) { 1072 t.hide(dim); 1073 } else { 1074 t.setAlpha(dim, alpha); 1075 t.show(dim); 1076 } 1077 } 1078 resizeStackSurfaces(int position, int taskPosition, SnapTarget taskSnapTarget, Transaction transaction)1079 void resizeStackSurfaces(int position, int taskPosition, SnapTarget taskSnapTarget, 1080 Transaction transaction) { 1081 if (mRemoved) { 1082 // This divider view has been removed so shouldn't have any additional influence. 1083 return; 1084 } 1085 calculateBoundsForPosition(position, mDockSide, mDockedRect); 1086 calculateBoundsForPosition(position, DockedDividerUtils.invertDockSide(mDockSide), 1087 mOtherRect); 1088 1089 if (mDockedRect.equals(mLastResizeRect) && !mEntranceAnimationRunning) { 1090 return; 1091 } 1092 1093 // Make sure shadows are updated 1094 if (mBackground.getZ() > 0f) { 1095 mBackground.invalidate(); 1096 } 1097 1098 final boolean ownTransaction = transaction == null; 1099 final Transaction t = ownTransaction ? mTiles.getTransaction() : transaction; 1100 mLastResizeRect.set(mDockedRect); 1101 if (mIsInMinimizeInteraction) { 1102 calculateBoundsForPosition(mSnapTargetBeforeMinimized.position, mDockSide, 1103 mDockedTaskRect); 1104 calculateBoundsForPosition(mSnapTargetBeforeMinimized.position, 1105 DockedDividerUtils.invertDockSide(mDockSide), mOtherTaskRect); 1106 1107 // Move a right-docked-app to line up with the divider while dragging it 1108 if (mDockSide == DOCKED_RIGHT) { 1109 mDockedTaskRect.offset(Math.max(position, -mDividerSize) 1110 - mDockedTaskRect.left + mDividerSize, 0); 1111 } 1112 resizeSplitSurfaces(t, mDockedRect, mDockedTaskRect, mOtherRect, mOtherTaskRect); 1113 if (ownTransaction) { 1114 t.apply(); 1115 mTiles.releaseTransaction(t); 1116 } 1117 return; 1118 } 1119 1120 if (mEntranceAnimationRunning && taskPosition != TASK_POSITION_SAME) { 1121 calculateBoundsForPosition(taskPosition, mDockSide, mDockedTaskRect); 1122 1123 // Move a docked app if from the right in position with the divider up to insets 1124 if (mDockSide == DOCKED_RIGHT) { 1125 mDockedTaskRect.offset(Math.max(position, -mDividerSize) 1126 - mDockedTaskRect.left + mDividerSize, 0); 1127 } 1128 calculateBoundsForPosition(taskPosition, DockedDividerUtils.invertDockSide(mDockSide), 1129 mOtherTaskRect); 1130 resizeSplitSurfaces(t, mDockedRect, mDockedTaskRect, mOtherRect, mOtherTaskRect); 1131 } else if (mExitAnimationRunning && taskPosition != TASK_POSITION_SAME) { 1132 calculateBoundsForPosition(taskPosition, mDockSide, mDockedTaskRect); 1133 mDockedInsetRect.set(mDockedTaskRect); 1134 calculateBoundsForPosition(mExitStartPosition, 1135 DockedDividerUtils.invertDockSide(mDockSide), mOtherTaskRect); 1136 mOtherInsetRect.set(mOtherTaskRect); 1137 applyExitAnimationParallax(mOtherTaskRect, position); 1138 1139 // Move a right-docked-app to line up with the divider while dragging it 1140 if (mDockSide == DOCKED_RIGHT) { 1141 mDockedTaskRect.offset(position + mDividerSize, 0); 1142 } 1143 resizeSplitSurfaces(t, mDockedRect, mDockedTaskRect, mOtherRect, mOtherTaskRect); 1144 } else if (taskPosition != TASK_POSITION_SAME) { 1145 calculateBoundsForPosition(position, DockedDividerUtils.invertDockSide(mDockSide), 1146 mOtherRect); 1147 int dockSideInverted = DockedDividerUtils.invertDockSide(mDockSide); 1148 int taskPositionDocked = 1149 restrictDismissingTaskPosition(taskPosition, mDockSide, taskSnapTarget); 1150 int taskPositionOther = 1151 restrictDismissingTaskPosition(taskPosition, dockSideInverted, taskSnapTarget); 1152 calculateBoundsForPosition(taskPositionDocked, mDockSide, mDockedTaskRect); 1153 calculateBoundsForPosition(taskPositionOther, dockSideInverted, mOtherTaskRect); 1154 mTmpRect.set(0, 0, mSplitLayout.mDisplayLayout.width(), 1155 mSplitLayout.mDisplayLayout.height()); 1156 alignTopLeft(mDockedRect, mDockedTaskRect); 1157 alignTopLeft(mOtherRect, mOtherTaskRect); 1158 mDockedInsetRect.set(mDockedTaskRect); 1159 mOtherInsetRect.set(mOtherTaskRect); 1160 if (dockSideTopLeft(mDockSide)) { 1161 alignTopLeft(mTmpRect, mDockedInsetRect); 1162 alignBottomRight(mTmpRect, mOtherInsetRect); 1163 } else { 1164 alignBottomRight(mTmpRect, mDockedInsetRect); 1165 alignTopLeft(mTmpRect, mOtherInsetRect); 1166 } 1167 applyDismissingParallax(mDockedTaskRect, mDockSide, taskSnapTarget, position, 1168 taskPositionDocked); 1169 applyDismissingParallax(mOtherTaskRect, dockSideInverted, taskSnapTarget, position, 1170 taskPositionOther); 1171 resizeSplitSurfaces(t, mDockedRect, mDockedTaskRect, mOtherRect, mOtherTaskRect); 1172 } else { 1173 resizeSplitSurfaces(t, mDockedRect, null, mOtherRect, null); 1174 } 1175 SnapTarget closestDismissTarget = getSnapAlgorithm().getClosestDismissTarget(position); 1176 float dimFraction = getDimFraction(position, closestDismissTarget); 1177 setResizeDimLayer(t, isDismissTargetPrimary(closestDismissTarget), dimFraction); 1178 if (ownTransaction) { 1179 t.apply(); 1180 mTiles.releaseTransaction(t); 1181 } 1182 } 1183 applyExitAnimationParallax(Rect taskRect, int position)1184 private void applyExitAnimationParallax(Rect taskRect, int position) { 1185 if (mDockSide == WindowManager.DOCKED_TOP) { 1186 taskRect.offset(0, (int) ((position - mExitStartPosition) * 0.25f)); 1187 } else if (mDockSide == WindowManager.DOCKED_LEFT) { 1188 taskRect.offset((int) ((position - mExitStartPosition) * 0.25f), 0); 1189 } else if (mDockSide == WindowManager.DOCKED_RIGHT) { 1190 taskRect.offset((int) ((mExitStartPosition - position) * 0.25f), 0); 1191 } 1192 } 1193 getDimFraction(int position, SnapTarget dismissTarget)1194 private float getDimFraction(int position, SnapTarget dismissTarget) { 1195 if (mEntranceAnimationRunning) { 1196 return 0f; 1197 } 1198 float fraction = getSnapAlgorithm().calculateDismissingFraction(position); 1199 fraction = Math.max(0, Math.min(fraction, 1f)); 1200 fraction = DIM_INTERPOLATOR.getInterpolation(fraction); 1201 return fraction; 1202 } 1203 1204 /** 1205 * When the snap target is dismissing one side, make sure that the dismissing side doesn't get 1206 * 0 size. 1207 */ restrictDismissingTaskPosition(int taskPosition, int dockSide, SnapTarget snapTarget)1208 private int restrictDismissingTaskPosition(int taskPosition, int dockSide, 1209 SnapTarget snapTarget) { 1210 if (snapTarget.flag == SnapTarget.FLAG_DISMISS_START && dockSideTopLeft(dockSide)) { 1211 return Math.max(mSplitLayout.getSnapAlgorithm().getFirstSplitTarget().position, 1212 mStartPosition); 1213 } else if (snapTarget.flag == SnapTarget.FLAG_DISMISS_END 1214 && dockSideBottomRight(dockSide)) { 1215 return Math.min(mSplitLayout.getSnapAlgorithm().getLastSplitTarget().position, 1216 mStartPosition); 1217 } else { 1218 return taskPosition; 1219 } 1220 } 1221 1222 /** 1223 * Applies a parallax to the task when dismissing. 1224 */ applyDismissingParallax(Rect taskRect, int dockSide, SnapTarget snapTarget, int position, int taskPosition)1225 private void applyDismissingParallax(Rect taskRect, int dockSide, SnapTarget snapTarget, 1226 int position, int taskPosition) { 1227 float fraction = Math.min(1, Math.max(0, 1228 mSplitLayout.getSnapAlgorithm().calculateDismissingFraction(position))); 1229 SnapTarget dismissTarget = null; 1230 SnapTarget splitTarget = null; 1231 int start = 0; 1232 if (position <= mSplitLayout.getSnapAlgorithm().getLastSplitTarget().position 1233 && dockSideTopLeft(dockSide)) { 1234 dismissTarget = mSplitLayout.getSnapAlgorithm().getDismissStartTarget(); 1235 splitTarget = mSplitLayout.getSnapAlgorithm().getFirstSplitTarget(); 1236 start = taskPosition; 1237 } else if (position >= mSplitLayout.getSnapAlgorithm().getLastSplitTarget().position 1238 && dockSideBottomRight(dockSide)) { 1239 dismissTarget = mSplitLayout.getSnapAlgorithm().getDismissEndTarget(); 1240 splitTarget = mSplitLayout.getSnapAlgorithm().getLastSplitTarget(); 1241 start = splitTarget.position; 1242 } 1243 if (dismissTarget != null && fraction > 0f 1244 && isDismissing(splitTarget, position, dockSide)) { 1245 fraction = calculateParallaxDismissingFraction(fraction, dockSide); 1246 int offsetPosition = (int) (start + 1247 fraction * (dismissTarget.position - splitTarget.position)); 1248 int width = taskRect.width(); 1249 int height = taskRect.height(); 1250 switch (dockSide) { 1251 case WindowManager.DOCKED_LEFT: 1252 taskRect.left = offsetPosition - width; 1253 taskRect.right = offsetPosition; 1254 break; 1255 case WindowManager.DOCKED_RIGHT: 1256 taskRect.left = offsetPosition + mDividerSize; 1257 taskRect.right = offsetPosition + width + mDividerSize; 1258 break; 1259 case WindowManager.DOCKED_TOP: 1260 taskRect.top = offsetPosition - height; 1261 taskRect.bottom = offsetPosition; 1262 break; 1263 case WindowManager.DOCKED_BOTTOM: 1264 taskRect.top = offsetPosition + mDividerSize; 1265 taskRect.bottom = offsetPosition + height + mDividerSize; 1266 break; 1267 } 1268 } 1269 } 1270 1271 /** 1272 * @return for a specified {@code fraction}, this returns an adjusted value that simulates a 1273 * slowing down parallax effect 1274 */ calculateParallaxDismissingFraction(float fraction, int dockSide)1275 private static float calculateParallaxDismissingFraction(float fraction, int dockSide) { 1276 float result = SLOWDOWN_INTERPOLATOR.getInterpolation(fraction) / 3.5f; 1277 1278 // Less parallax at the top, just because. 1279 if (dockSide == WindowManager.DOCKED_TOP) { 1280 result /= 2f; 1281 } 1282 return result; 1283 } 1284 isDismissing(SnapTarget snapTarget, int position, int dockSide)1285 private static boolean isDismissing(SnapTarget snapTarget, int position, int dockSide) { 1286 if (dockSide == WindowManager.DOCKED_TOP || dockSide == WindowManager.DOCKED_LEFT) { 1287 return position < snapTarget.position; 1288 } else { 1289 return position > snapTarget.position; 1290 } 1291 } 1292 isDismissTargetPrimary(SnapTarget dismissTarget)1293 private boolean isDismissTargetPrimary(SnapTarget dismissTarget) { 1294 return (dismissTarget.flag == SnapTarget.FLAG_DISMISS_START && dockSideTopLeft(mDockSide)) 1295 || (dismissTarget.flag == SnapTarget.FLAG_DISMISS_END 1296 && dockSideBottomRight(mDockSide)); 1297 } 1298 1299 /** 1300 * @return true if and only if {@code dockSide} is top or left 1301 */ dockSideTopLeft(int dockSide)1302 private static boolean dockSideTopLeft(int dockSide) { 1303 return dockSide == WindowManager.DOCKED_TOP || dockSide == WindowManager.DOCKED_LEFT; 1304 } 1305 1306 /** 1307 * @return true if and only if {@code dockSide} is bottom or right 1308 */ dockSideBottomRight(int dockSide)1309 private static boolean dockSideBottomRight(int dockSide) { 1310 return dockSide == WindowManager.DOCKED_BOTTOM || dockSide == WindowManager.DOCKED_RIGHT; 1311 } 1312 1313 @Override onComputeInternalInsets(InternalInsetsInfo inoutInfo)1314 public void onComputeInternalInsets(InternalInsetsInfo inoutInfo) { 1315 inoutInfo.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_REGION); 1316 inoutInfo.touchableRegion.set(mHandle.getLeft(), mHandle.getTop(), mHandle.getRight(), 1317 mHandle.getBottom()); 1318 inoutInfo.touchableRegion.op(mBackground.getLeft(), mBackground.getTop(), 1319 mBackground.getRight(), mBackground.getBottom(), Op.UNION); 1320 } 1321 1322 /** 1323 * Checks whether recents will grow when invoked. This happens in multi-window when recents is 1324 * very small. When invoking recents, we shrink the docked stack so recents has more space. 1325 * 1326 * @return the position of the divider when recents grows, or 1327 * {@link #INVALID_RECENTS_GROW_TARGET} if recents won't grow 1328 */ growsRecents()1329 public int growsRecents() { 1330 boolean result = mGrowRecents 1331 && mDockSide == WindowManager.DOCKED_TOP 1332 && getCurrentPosition() == getSnapAlgorithm().getLastSplitTarget().position; 1333 if (result) { 1334 return getSnapAlgorithm().getMiddleTarget().position; 1335 } else { 1336 return INVALID_RECENTS_GROW_TARGET; 1337 } 1338 } 1339 onRecentsActivityStarting()1340 void onRecentsActivityStarting() { 1341 if (mGrowRecents && mDockSide == WindowManager.DOCKED_TOP 1342 && getSnapAlgorithm().getMiddleTarget() != getSnapAlgorithm().getLastSplitTarget() 1343 && getCurrentPosition() == getSnapAlgorithm().getLastSplitTarget().position) { 1344 mState.growAfterRecentsDrawn = true; 1345 startDragging(false /* animate */, false /* touching */); 1346 } 1347 } 1348 onDockedFirstAnimationFrame()1349 void onDockedFirstAnimationFrame() { 1350 saveSnapTargetBeforeMinimized(mSplitLayout.getSnapAlgorithm().getMiddleTarget()); 1351 } 1352 onDockedTopTask()1353 void onDockedTopTask() { 1354 mState.growAfterRecentsDrawn = false; 1355 mState.animateAfterRecentsDrawn = true; 1356 startDragging(false /* animate */, false /* touching */); 1357 updateDockSide(); 1358 mEntranceAnimationRunning = true; 1359 1360 resizeStackSurfaces(calculatePositionForInsetBounds(), 1361 mSplitLayout.getSnapAlgorithm().getMiddleTarget().position, 1362 mSplitLayout.getSnapAlgorithm().getMiddleTarget(), 1363 null /* transaction */); 1364 } 1365 onRecentsDrawn()1366 void onRecentsDrawn() { 1367 updateDockSide(); 1368 final int position = calculatePositionForInsetBounds(); 1369 if (mState.animateAfterRecentsDrawn) { 1370 mState.animateAfterRecentsDrawn = false; 1371 1372 mHandler.post(() -> { 1373 // Delay switching resizing mode because this might cause jank in recents animation 1374 // that's longer than this animation. 1375 stopDragging(position, getSnapAlgorithm().getMiddleTarget(), 1376 mLongPressEntraceAnimDuration, Interpolators.FAST_OUT_SLOW_IN, 1377 200 /* endDelay */); 1378 }); 1379 } 1380 if (mState.growAfterRecentsDrawn) { 1381 mState.growAfterRecentsDrawn = false; 1382 updateDockSide(); 1383 if (mCallback != null) { 1384 mCallback.growRecents(); 1385 } 1386 stopDragging(position, getSnapAlgorithm().getMiddleTarget(), 336, 1387 Interpolators.FAST_OUT_SLOW_IN); 1388 } 1389 } 1390 onUndockingTask()1391 void onUndockingTask() { 1392 int dockSide = mSplitLayout.getPrimarySplitSide(); 1393 if (inSplitMode()) { 1394 startDragging(false /* animate */, false /* touching */); 1395 SnapTarget target = dockSideTopLeft(dockSide) 1396 ? mSplitLayout.getSnapAlgorithm().getDismissEndTarget() 1397 : mSplitLayout.getSnapAlgorithm().getDismissStartTarget(); 1398 1399 // Don't start immediately - give a little bit time to settle the drag resize change. 1400 mExitAnimationRunning = true; 1401 mExitStartPosition = getCurrentPosition(); 1402 stopDragging(mExitStartPosition, target, 336 /* duration */, 100 /* startDelay */, 1403 0 /* endDelay */, Interpolators.FAST_OUT_SLOW_IN); 1404 } 1405 } 1406 calculatePositionForInsetBounds()1407 private int calculatePositionForInsetBounds() { 1408 mSplitLayout.mDisplayLayout.getStableBounds(mTmpRect); 1409 return DockedDividerUtils.calculatePositionForBounds(mTmpRect, mDockSide, mDividerSize); 1410 } 1411 } 1412