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