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