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