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 
22 import android.animation.Animator;
23 import android.animation.AnimatorListenerAdapter;
24 import android.animation.ValueAnimator;
25 import android.annotation.Nullable;
26 import android.app.ActivityManager.StackId;
27 import android.content.Context;
28 import android.content.res.Configuration;
29 import android.graphics.Rect;
30 import android.graphics.Region.Op;
31 import android.hardware.display.DisplayManager;
32 import android.os.Bundle;
33 import android.os.Handler;
34 import android.os.Message;
35 import android.util.AttributeSet;
36 import android.view.Choreographer;
37 import android.view.Display;
38 import android.view.DisplayInfo;
39 import android.view.GestureDetector;
40 import android.view.GestureDetector.SimpleOnGestureListener;
41 import android.view.MotionEvent;
42 import android.view.PointerIcon;
43 import android.view.VelocityTracker;
44 import android.view.View;
45 import android.view.View.OnTouchListener;
46 import android.view.ViewConfiguration;
47 import android.view.ViewTreeObserver.InternalInsetsInfo;
48 import android.view.ViewTreeObserver.OnComputeInternalInsetsListener;
49 import android.view.WindowInsets;
50 import android.view.WindowManager;
51 import android.view.accessibility.AccessibilityNodeInfo;
52 import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
53 import android.view.animation.Interpolator;
54 import android.view.animation.PathInterpolator;
55 import android.widget.FrameLayout;
56 
57 import com.android.internal.logging.MetricsLogger;
58 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
59 import com.android.internal.policy.DividerSnapAlgorithm;
60 import com.android.internal.policy.DividerSnapAlgorithm.SnapTarget;
61 import com.android.internal.policy.DockedDividerUtils;
62 import com.android.internal.view.SurfaceFlingerVsyncChoreographer;
63 import com.android.systemui.Interpolators;
64 import com.android.systemui.R;
65 import com.android.systemui.recents.Recents;
66 import com.android.systemui.recents.events.EventBus;
67 import com.android.systemui.recents.events.activity.DockedTopTaskEvent;
68 import com.android.systemui.recents.events.activity.RecentsActivityStartingEvent;
69 import com.android.systemui.recents.events.activity.ToggleRecentsEvent;
70 import com.android.systemui.recents.events.activity.UndockingTaskEvent;
71 import com.android.systemui.recents.events.ui.RecentsDrawnEvent;
72 import com.android.systemui.recents.events.ui.RecentsGrowingEvent;
73 import com.android.systemui.recents.misc.SystemServicesProxy;
74 import com.android.systemui.stackdivider.events.StartedDragingEvent;
75 import com.android.systemui.stackdivider.events.StoppedDragingEvent;
76 import com.android.systemui.statusbar.FlingAnimationUtils;
77 import com.android.systemui.statusbar.phone.NavigationBarGestureHelper;
78 
79 /**
80  * Docked stack divider.
81  */
82 public class DividerView extends FrameLayout implements OnTouchListener,
83         OnComputeInternalInsetsListener {
84 
85     static final long TOUCH_ANIMATION_DURATION = 150;
86     static final long TOUCH_RELEASE_ANIMATION_DURATION = 200;
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     private static final boolean SWAPPING_ENABLED = false;
99 
100     /**
101      * How much the background gets scaled when we are in the minimized dock state.
102      */
103     private static final float MINIMIZE_DOCK_SCALE = 0f;
104     private static final float ADJUSTED_FOR_IME_SCALE = 0.5f;
105 
106     private static final PathInterpolator SLOWDOWN_INTERPOLATOR =
107             new PathInterpolator(0.5f, 1f, 0.5f, 1f);
108     private static final PathInterpolator DIM_INTERPOLATOR =
109             new PathInterpolator(.23f, .87f, .52f, -0.11f);
110     private static final Interpolator IME_ADJUST_INTERPOLATOR =
111             new PathInterpolator(0.2f, 0f, 0.1f, 1f);
112 
113     private static final int MSG_RESIZE_STACK = 0;
114 
115     private DividerHandleView mHandle;
116     private View mBackground;
117     private MinimizedDockShadow mMinimizedShadow;
118     private int mStartX;
119     private int mStartY;
120     private int mStartPosition;
121     private int mDockSide;
122     private final int[] mTempInt2 = new int[2];
123     private boolean mMoving;
124     private int mTouchSlop;
125     private boolean mBackgroundLifted;
126     private boolean mIsInMinimizeInteraction;
127     private int mDividerPositionBeforeMinimized;
128 
129     private int mDividerInsets;
130     private int mDisplayWidth;
131     private int mDisplayHeight;
132     private int mDividerWindowWidth;
133     private int mDividerSize;
134     private int mTouchElevation;
135     private int mLongPressEntraceAnimDuration;
136 
137     private final Rect mDockedRect = new Rect();
138     private final Rect mDockedTaskRect = new Rect();
139     private final Rect mOtherTaskRect = new Rect();
140     private final Rect mOtherRect = new Rect();
141     private final Rect mDockedInsetRect = new Rect();
142     private final Rect mOtherInsetRect = new Rect();
143     private final Rect mLastResizeRect = new Rect();
144     private final Rect mDisplayRect = new Rect();
145     private final WindowManagerProxy mWindowManagerProxy = WindowManagerProxy.getInstance();
146     private DividerWindowManager mWindowManager;
147     private VelocityTracker mVelocityTracker;
148     private FlingAnimationUtils mFlingAnimationUtils;
149     private DividerSnapAlgorithm mSnapAlgorithm;
150     private DividerSnapAlgorithm mMinimizedSnapAlgorithm;
151     private final Rect mStableInsets = new Rect();
152 
153     private boolean mGrowRecents;
154     private ValueAnimator mCurrentAnimator;
155     private boolean mEntranceAnimationRunning;
156     private boolean mExitAnimationRunning;
157     private int mExitStartPosition;
158     private GestureDetector mGestureDetector;
159     private boolean mDockedStackMinimized;
160     private boolean mHomeStackResizable;
161     private boolean mAdjustedForIme;
162     private DividerState mState;
163     private final SurfaceFlingerVsyncChoreographer mSfChoreographer;
164 
165     // The view is removed or in the process of been removed from the system.
166     private boolean mRemoved;
167 
168     private final Handler mHandler = new Handler() {
169         @Override
170         public void handleMessage(Message msg) {
171             switch (msg.what) {
172                 case MSG_RESIZE_STACK:
173                     resizeStack(msg.arg1, msg.arg2, (SnapTarget) msg.obj);
174                     break;
175                 default:
176                     super.handleMessage(msg);
177             }
178         }
179     };
180 
181     private final AccessibilityDelegate mHandleDelegate = new AccessibilityDelegate() {
182         @Override
183         public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
184             super.onInitializeAccessibilityNodeInfo(host, info);
185             if (isHorizontalDivision()) {
186                 info.addAction(new AccessibilityAction(R.id.action_move_tl_full,
187                         mContext.getString(R.string.accessibility_action_divider_top_full)));
188                 if (mSnapAlgorithm.isFirstSplitTargetAvailable()) {
189                     info.addAction(new AccessibilityAction(R.id.action_move_tl_70,
190                             mContext.getString(R.string.accessibility_action_divider_top_70)));
191                 }
192                 info.addAction(new AccessibilityAction(R.id.action_move_tl_50,
193                         mContext.getString(R.string.accessibility_action_divider_top_50)));
194                 if (mSnapAlgorithm.isLastSplitTargetAvailable()) {
195                     info.addAction(new AccessibilityAction(R.id.action_move_tl_30,
196                             mContext.getString(R.string.accessibility_action_divider_top_30)));
197                 }
198                 info.addAction(new AccessibilityAction(R.id.action_move_rb_full,
199                         mContext.getString(R.string.accessibility_action_divider_bottom_full)));
200             } else {
201                 info.addAction(new AccessibilityAction(R.id.action_move_tl_full,
202                         mContext.getString(R.string.accessibility_action_divider_left_full)));
203                 if (mSnapAlgorithm.isFirstSplitTargetAvailable()) {
204                     info.addAction(new AccessibilityAction(R.id.action_move_tl_70,
205                             mContext.getString(R.string.accessibility_action_divider_left_70)));
206                 }
207                 info.addAction(new AccessibilityAction(R.id.action_move_tl_50,
208                         mContext.getString(R.string.accessibility_action_divider_left_50)));
209                 if (mSnapAlgorithm.isLastSplitTargetAvailable()) {
210                     info.addAction(new AccessibilityAction(R.id.action_move_tl_30,
211                             mContext.getString(R.string.accessibility_action_divider_left_30)));
212                 }
213                 info.addAction(new AccessibilityAction(R.id.action_move_rb_full,
214                         mContext.getString(R.string.accessibility_action_divider_right_full)));
215             }
216         }
217 
218         @Override
219         public boolean performAccessibilityAction(View host, int action, Bundle args) {
220             int currentPosition = getCurrentPosition();
221             SnapTarget nextTarget = null;
222             switch (action) {
223                 case R.id.action_move_tl_full:
224                     nextTarget = mSnapAlgorithm.getDismissEndTarget();
225                     break;
226                 case R.id.action_move_tl_70:
227                     nextTarget = mSnapAlgorithm.getLastSplitTarget();
228                     break;
229                 case R.id.action_move_tl_50:
230                     nextTarget = mSnapAlgorithm.getMiddleTarget();
231                     break;
232                 case R.id.action_move_tl_30:
233                     nextTarget = mSnapAlgorithm.getFirstSplitTarget();
234                     break;
235                 case R.id.action_move_rb_full:
236                     nextTarget = mSnapAlgorithm.getDismissStartTarget();
237                     break;
238             }
239             if (nextTarget != null) {
240                 startDragging(true /* animate */, false /* touching */);
241                 stopDragging(currentPosition, nextTarget, 250, Interpolators.FAST_OUT_SLOW_IN);
242                 return true;
243             }
244             return super.performAccessibilityAction(host, action, args);
245         }
246     };
247 
248     private final Runnable mResetBackgroundRunnable = new Runnable() {
249         @Override
250         public void run() {
251             resetBackground();
252         }
253     };
254 
DividerView(Context context)255     public DividerView(Context context) {
256         this(context, null);
257     }
258 
DividerView(Context context, @Nullable AttributeSet attrs)259     public DividerView(Context context, @Nullable AttributeSet attrs) {
260         this(context, attrs, 0);
261     }
262 
DividerView(Context context, @Nullable AttributeSet attrs, int defStyleAttr)263     public DividerView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
264         this(context, attrs, defStyleAttr, 0);
265     }
266 
DividerView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes)267     public DividerView(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
268             int defStyleRes) {
269         super(context, attrs, defStyleAttr, defStyleRes);
270         mSfChoreographer = new SurfaceFlingerVsyncChoreographer(mHandler, context.getDisplay(),
271                 Choreographer.getInstance());
272     }
273 
274     @Override
onFinishInflate()275     protected void onFinishInflate() {
276         super.onFinishInflate();
277         mHandle = findViewById(R.id.docked_divider_handle);
278         mBackground = findViewById(R.id.docked_divider_background);
279         mMinimizedShadow = findViewById(R.id.minimized_dock_shadow);
280         mHandle.setOnTouchListener(this);
281         mDividerWindowWidth = getResources().getDimensionPixelSize(
282                 com.android.internal.R.dimen.docked_stack_divider_thickness);
283         mDividerInsets = getResources().getDimensionPixelSize(
284                 com.android.internal.R.dimen.docked_stack_divider_insets);
285         mDividerSize = mDividerWindowWidth - 2 * mDividerInsets;
286         mTouchElevation = getResources().getDimensionPixelSize(
287                 R.dimen.docked_stack_divider_lift_elevation);
288         mLongPressEntraceAnimDuration = getResources().getInteger(
289                 R.integer.long_press_dock_anim_duration);
290         mGrowRecents = getResources().getBoolean(R.bool.recents_grow_in_multiwindow);
291         mTouchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop();
292         mFlingAnimationUtils = new FlingAnimationUtils(getContext(), 0.3f);
293         updateDisplayInfo();
294         boolean landscape = getResources().getConfiguration().orientation
295                 == Configuration.ORIENTATION_LANDSCAPE;
296         mHandle.setPointerIcon(PointerIcon.getSystemIcon(getContext(),
297                 landscape ? TYPE_HORIZONTAL_DOUBLE_ARROW : TYPE_VERTICAL_DOUBLE_ARROW));
298         getViewTreeObserver().addOnComputeInternalInsetsListener(this);
299         mHandle.setAccessibilityDelegate(mHandleDelegate);
300         mGestureDetector = new GestureDetector(mContext, new SimpleOnGestureListener() {
301             @Override
302             public boolean onSingleTapUp(MotionEvent e) {
303                 if (SWAPPING_ENABLED) {
304                     updateDockSide();
305                     SystemServicesProxy ssp = Recents.getSystemServices();
306                     if (mDockSide != WindowManager.DOCKED_INVALID
307                             && !ssp.isRecentsActivityVisible()) {
308                         mWindowManagerProxy.swapTasks();
309                         return true;
310                     }
311                 }
312                 return false;
313             }
314         });
315     }
316 
317     @Override
onAttachedToWindow()318     protected void onAttachedToWindow() {
319         super.onAttachedToWindow();
320         EventBus.getDefault().register(this);
321     }
322 
323     @Override
onDetachedFromWindow()324     protected void onDetachedFromWindow() {
325         super.onDetachedFromWindow();
326         EventBus.getDefault().unregister(this);
327     }
328 
onDividerRemoved()329     void onDividerRemoved() {
330         mRemoved = true;
331         mHandler.removeMessages(MSG_RESIZE_STACK);
332     }
333 
334     @Override
onApplyWindowInsets(WindowInsets insets)335     public WindowInsets onApplyWindowInsets(WindowInsets insets) {
336         if (mStableInsets.left != insets.getStableInsetLeft()
337                 || mStableInsets.top != insets.getStableInsetTop()
338                 || mStableInsets.right != insets.getStableInsetRight()
339                 || mStableInsets.bottom != insets.getStableInsetBottom()) {
340             mStableInsets.set(insets.getStableInsetLeft(), insets.getStableInsetTop(),
341                     insets.getStableInsetRight(), insets.getStableInsetBottom());
342             if (mSnapAlgorithm != null || mMinimizedSnapAlgorithm != null) {
343                 mSnapAlgorithm = null;
344                 mMinimizedSnapAlgorithm = null;
345                 initializeSnapAlgorithm();
346             }
347         }
348         return super.onApplyWindowInsets(insets);
349     }
350 
351     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)352     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
353         super.onLayout(changed, left, top, right, bottom);
354         int minimizeLeft = 0;
355         int minimizeTop = 0;
356         if (mDockSide == WindowManager.DOCKED_TOP) {
357             minimizeTop = mBackground.getTop();
358         } else if (mDockSide == WindowManager.DOCKED_LEFT) {
359             minimizeLeft = mBackground.getLeft();
360         } else if (mDockSide == WindowManager.DOCKED_RIGHT) {
361             minimizeLeft = mBackground.getRight() - mMinimizedShadow.getWidth();
362         }
363         mMinimizedShadow.layout(minimizeLeft, minimizeTop,
364                 minimizeLeft + mMinimizedShadow.getMeasuredWidth(),
365                 minimizeTop + mMinimizedShadow.getMeasuredHeight());
366         if (changed) {
367             mWindowManagerProxy.setTouchRegion(new Rect(mHandle.getLeft(), mHandle.getTop(),
368                     mHandle.getRight(), mHandle.getBottom()));
369         }
370     }
371 
injectDependencies(DividerWindowManager windowManager, DividerState dividerState)372     public void injectDependencies(DividerWindowManager windowManager, DividerState dividerState) {
373         mWindowManager = windowManager;
374         mState = dividerState;
375     }
376 
getWindowManagerProxy()377     public WindowManagerProxy getWindowManagerProxy() {
378         return mWindowManagerProxy;
379     }
380 
startDragging(boolean animate, boolean touching)381     public boolean startDragging(boolean animate, boolean touching) {
382         cancelFlingAnimation();
383         if (touching) {
384             mHandle.setTouching(true, animate);
385         }
386         mDockSide = mWindowManagerProxy.getDockSide();
387         initializeSnapAlgorithm();
388         mWindowManagerProxy.setResizing(true);
389         if (touching) {
390             mWindowManager.setSlippery(false);
391             liftBackground();
392         }
393         EventBus.getDefault().send(new StartedDragingEvent());
394         return mDockSide != WindowManager.DOCKED_INVALID;
395     }
396 
stopDragging(int position, float velocity, boolean avoidDismissStart, boolean logMetrics)397     public void stopDragging(int position, float velocity, boolean avoidDismissStart,
398             boolean logMetrics) {
399         mHandle.setTouching(false, true /* animate */);
400         fling(position, velocity, avoidDismissStart, logMetrics);
401         mWindowManager.setSlippery(true);
402         releaseBackground();
403     }
404 
stopDragging(int position, SnapTarget target, long duration, Interpolator interpolator)405     public void stopDragging(int position, SnapTarget target, long duration,
406             Interpolator interpolator) {
407         stopDragging(position, target, duration, 0 /* startDelay*/, 0 /* endDelay */, interpolator);
408     }
409 
stopDragging(int position, SnapTarget target, long duration, Interpolator interpolator, long endDelay)410     public void stopDragging(int position, SnapTarget target, long duration,
411             Interpolator interpolator, long endDelay) {
412         stopDragging(position, target, duration, 0 /* startDelay*/, endDelay, interpolator);
413     }
414 
stopDragging(int position, SnapTarget target, long duration, long startDelay, long endDelay, Interpolator interpolator)415     public void stopDragging(int position, SnapTarget target, long duration, long startDelay,
416             long endDelay, Interpolator interpolator) {
417         mHandle.setTouching(false, true /* animate */);
418         flingTo(position, target, duration, startDelay, endDelay, interpolator);
419         mWindowManager.setSlippery(true);
420         releaseBackground();
421     }
422 
stopDragging()423     private void stopDragging() {
424         mHandle.setTouching(false, true /* animate */);
425         mWindowManager.setSlippery(true);
426         releaseBackground();
427     }
428 
updateDockSide()429     private void updateDockSide() {
430         mDockSide = mWindowManagerProxy.getDockSide();
431         mMinimizedShadow.setDockSide(mDockSide);
432     }
433 
initializeSnapAlgorithm()434     private void initializeSnapAlgorithm() {
435         if (mSnapAlgorithm == null) {
436             mSnapAlgorithm = new DividerSnapAlgorithm(getContext().getResources(), mDisplayWidth,
437                     mDisplayHeight, mDividerSize, isHorizontalDivision(), mStableInsets);
438         }
439         if (mMinimizedSnapAlgorithm == null) {
440             mMinimizedSnapAlgorithm = new DividerSnapAlgorithm(getContext().getResources(),
441                     mDisplayWidth, mDisplayHeight, mDividerSize, isHorizontalDivision(),
442                     mStableInsets, mDockedStackMinimized && mHomeStackResizable);
443         }
444     }
445 
getSnapAlgorithm()446     public DividerSnapAlgorithm getSnapAlgorithm() {
447         initializeSnapAlgorithm();
448         return mDockedStackMinimized && mHomeStackResizable ? mMinimizedSnapAlgorithm :
449                 mSnapAlgorithm;
450     }
451 
getCurrentPosition()452     public int getCurrentPosition() {
453         getLocationOnScreen(mTempInt2);
454         if (isHorizontalDivision()) {
455             return mTempInt2[1] + mDividerInsets;
456         } else {
457             return mTempInt2[0] + mDividerInsets;
458         }
459     }
460 
461     @Override
onTouch(View v, MotionEvent event)462     public boolean onTouch(View v, MotionEvent event) {
463         convertToScreenCoordinates(event);
464         mGestureDetector.onTouchEvent(event);
465         final int action = event.getAction() & MotionEvent.ACTION_MASK;
466         switch (action) {
467             case MotionEvent.ACTION_DOWN:
468                 mVelocityTracker = VelocityTracker.obtain();
469                 mVelocityTracker.addMovement(event);
470                 mStartX = (int) event.getX();
471                 mStartY = (int) event.getY();
472                 boolean result = startDragging(true /* animate */, true /* touching */);
473                 if (!result) {
474 
475                     // Weren't able to start dragging successfully, so cancel it again.
476                     stopDragging();
477                 }
478                 mStartPosition = getCurrentPosition();
479                 mMoving = false;
480                 return result;
481             case MotionEvent.ACTION_MOVE:
482                 mVelocityTracker.addMovement(event);
483                 int x = (int) event.getX();
484                 int y = (int) event.getY();
485                 boolean exceededTouchSlop =
486                         isHorizontalDivision() && Math.abs(y - mStartY) > mTouchSlop
487                                 || (!isHorizontalDivision() && Math.abs(x - mStartX) > mTouchSlop);
488                 if (!mMoving && exceededTouchSlop) {
489                     mStartX = x;
490                     mStartY = y;
491                     mMoving = true;
492                 }
493                 if (mMoving && mDockSide != WindowManager.DOCKED_INVALID) {
494                     SnapTarget snapTarget = getSnapAlgorithm().calculateSnapTarget(
495                             mStartPosition, 0 /* velocity */, false /* hardDismiss */);
496                     resizeStackDelayed(calculatePosition(x, y), mStartPosition, snapTarget);
497                 }
498                 break;
499             case MotionEvent.ACTION_UP:
500             case MotionEvent.ACTION_CANCEL:
501                 mVelocityTracker.addMovement(event);
502 
503                 x = (int) event.getRawX();
504                 y = (int) event.getRawY();
505 
506                 mVelocityTracker.computeCurrentVelocity(1000);
507                 int position = calculatePosition(x, y);
508                 stopDragging(position, isHorizontalDivision() ? mVelocityTracker.getYVelocity()
509                         : mVelocityTracker.getXVelocity(), false /* avoidDismissStart */,
510                         true /* log */);
511                 mMoving = false;
512                 break;
513         }
514         return true;
515     }
516 
logResizeEvent(SnapTarget snapTarget)517     private void logResizeEvent(SnapTarget snapTarget) {
518         if (snapTarget == mSnapAlgorithm.getDismissStartTarget()) {
519             MetricsLogger.action(
520                     mContext, MetricsEvent.ACTION_WINDOW_UNDOCK_MAX, dockSideTopLeft(mDockSide)
521                             ? LOG_VALUE_UNDOCK_MAX_OTHER
522                             : LOG_VALUE_UNDOCK_MAX_DOCKED);
523         } else if (snapTarget == mSnapAlgorithm.getDismissEndTarget()) {
524             MetricsLogger.action(
525                     mContext, MetricsEvent.ACTION_WINDOW_UNDOCK_MAX, dockSideBottomRight(mDockSide)
526                             ? LOG_VALUE_UNDOCK_MAX_OTHER
527                             : LOG_VALUE_UNDOCK_MAX_DOCKED);
528         } else if (snapTarget == mSnapAlgorithm.getMiddleTarget()) {
529             MetricsLogger.action(mContext, MetricsEvent.ACTION_WINDOW_DOCK_RESIZE,
530                     LOG_VALUE_RESIZE_50_50);
531         } else if (snapTarget == mSnapAlgorithm.getFirstSplitTarget()) {
532             MetricsLogger.action(mContext, MetricsEvent.ACTION_WINDOW_DOCK_RESIZE,
533                     dockSideTopLeft(mDockSide)
534                             ? LOG_VALUE_RESIZE_DOCKED_SMALLER
535                             : LOG_VALUE_RESIZE_DOCKED_LARGER);
536         } else if (snapTarget == mSnapAlgorithm.getLastSplitTarget()) {
537             MetricsLogger.action(mContext, MetricsEvent.ACTION_WINDOW_DOCK_RESIZE,
538                     dockSideTopLeft(mDockSide)
539                             ? LOG_VALUE_RESIZE_DOCKED_LARGER
540                             : LOG_VALUE_RESIZE_DOCKED_SMALLER);
541         }
542     }
543 
convertToScreenCoordinates(MotionEvent event)544     private void convertToScreenCoordinates(MotionEvent event) {
545         event.setLocation(event.getRawX(), event.getRawY());
546     }
547 
fling(int position, float velocity, boolean avoidDismissStart, boolean logMetrics)548     private void fling(int position, float velocity, boolean avoidDismissStart,
549             boolean logMetrics) {
550         DividerSnapAlgorithm currentSnapAlgorithm = getSnapAlgorithm();
551         SnapTarget snapTarget = currentSnapAlgorithm.calculateSnapTarget(position, velocity);
552         if (avoidDismissStart && snapTarget == currentSnapAlgorithm.getDismissStartTarget()) {
553             snapTarget = currentSnapAlgorithm.getFirstSplitTarget();
554         }
555         if (logMetrics) {
556             logResizeEvent(snapTarget);
557         }
558         ValueAnimator anim = getFlingAnimator(position, snapTarget, 0 /* endDelay */);
559         mFlingAnimationUtils.apply(anim, position, snapTarget.position, velocity);
560         anim.start();
561     }
562 
flingTo(int position, SnapTarget target, long duration, long startDelay, long endDelay, Interpolator interpolator)563     private void flingTo(int position, SnapTarget target, long duration, long startDelay,
564             long endDelay, Interpolator interpolator) {
565         ValueAnimator anim = getFlingAnimator(position, target, endDelay);
566         anim.setDuration(duration);
567         anim.setStartDelay(startDelay);
568         anim.setInterpolator(interpolator);
569         anim.start();
570     }
571 
getFlingAnimator(int position, final SnapTarget snapTarget, final long endDelay)572     private ValueAnimator getFlingAnimator(int position, final SnapTarget snapTarget,
573             final long endDelay) {
574         if (mCurrentAnimator != null) {
575             cancelFlingAnimation();
576             updateDockSide();
577         }
578         final boolean taskPositionSameAtEnd = snapTarget.flag == SnapTarget.FLAG_NONE;
579         ValueAnimator anim = ValueAnimator.ofInt(position, snapTarget.position);
580         anim.addUpdateListener(animation -> resizeStackDelayed((int) animation.getAnimatedValue(),
581                 taskPositionSameAtEnd && animation.getAnimatedFraction() == 1f
582                         ? TASK_POSITION_SAME
583                         : snapTarget.taskPosition,
584                 snapTarget));
585         Runnable endAction = () -> {
586             commitSnapFlags(snapTarget);
587             mWindowManagerProxy.setResizing(false);
588             mDockSide = WindowManager.DOCKED_INVALID;
589             mCurrentAnimator = null;
590             mEntranceAnimationRunning = false;
591             mExitAnimationRunning = false;
592             EventBus.getDefault().send(new StoppedDragingEvent());
593         };
594         Runnable notCancelledEndAction = () -> {
595             // Reset minimized divider position after unminimized state animation finishes
596             if (!mDockedStackMinimized && mIsInMinimizeInteraction) {
597                 mIsInMinimizeInteraction = false;
598             }
599         };
600         anim.addListener(new AnimatorListenerAdapter() {
601 
602             private boolean mCancelled;
603 
604             @Override
605             public void onAnimationCancel(Animator animation) {
606                 mHandler.removeMessages(MSG_RESIZE_STACK);
607                 mCancelled = true;
608             }
609 
610             @Override
611             public void onAnimationEnd(Animator animation) {
612                 long delay = 0;
613                 if (endDelay != 0) {
614                     delay = endDelay;
615                 } else if (mCancelled) {
616                     delay = 0;
617                 } else if (mSfChoreographer.getSurfaceFlingerOffsetMs() > 0) {
618                     delay = mSfChoreographer.getSurfaceFlingerOffsetMs();
619                 }
620                 if (delay == 0) {
621                     endAction.run();
622                     if (!mCancelled) {
623                         notCancelledEndAction.run();
624                     }
625                 } else {
626                     mHandler.postDelayed(endAction, delay);
627                     if (!mCancelled) {
628                         mHandler.postDelayed(notCancelledEndAction, delay);
629                     }
630                 }
631             }
632         });
633         mCurrentAnimator = anim;
634         return anim;
635     }
636 
cancelFlingAnimation()637     private void cancelFlingAnimation() {
638         if (mCurrentAnimator != null) {
639             mCurrentAnimator.cancel();
640         }
641     }
642 
commitSnapFlags(SnapTarget target)643     private void commitSnapFlags(SnapTarget target) {
644         if (target.flag == SnapTarget.FLAG_NONE) {
645             return;
646         }
647         boolean dismissOrMaximize;
648         if (target.flag == SnapTarget.FLAG_DISMISS_START) {
649             dismissOrMaximize = mDockSide == WindowManager.DOCKED_LEFT
650                     || mDockSide == WindowManager.DOCKED_TOP;
651         } else {
652             dismissOrMaximize = mDockSide == WindowManager.DOCKED_RIGHT
653                     || mDockSide == WindowManager.DOCKED_BOTTOM;
654         }
655         if (dismissOrMaximize) {
656             mWindowManagerProxy.dismissDockedStack();
657         } else {
658             mWindowManagerProxy.maximizeDockedStack();
659         }
660         mWindowManagerProxy.setResizeDimLayer(false, -1, 0f);
661     }
662 
liftBackground()663     private void liftBackground() {
664         if (mBackgroundLifted) {
665             return;
666         }
667         if (isHorizontalDivision()) {
668             mBackground.animate().scaleY(1.4f);
669         } else {
670             mBackground.animate().scaleX(1.4f);
671         }
672         mBackground.animate()
673                 .setInterpolator(Interpolators.TOUCH_RESPONSE)
674                 .setDuration(TOUCH_ANIMATION_DURATION)
675                 .translationZ(mTouchElevation)
676                 .start();
677 
678         // Lift handle as well so it doesn't get behind the background, even though it doesn't
679         // cast shadow.
680         mHandle.animate()
681                 .setInterpolator(Interpolators.TOUCH_RESPONSE)
682                 .setDuration(TOUCH_ANIMATION_DURATION)
683                 .translationZ(mTouchElevation)
684                 .start();
685         mBackgroundLifted = true;
686     }
687 
releaseBackground()688     private void releaseBackground() {
689         if (!mBackgroundLifted) {
690             return;
691         }
692         mBackground.animate()
693                 .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
694                 .setDuration(TOUCH_RELEASE_ANIMATION_DURATION)
695                 .translationZ(0)
696                 .scaleX(1f)
697                 .scaleY(1f)
698                 .start();
699         mHandle.animate()
700                 .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
701                 .setDuration(TOUCH_RELEASE_ANIMATION_DURATION)
702                 .translationZ(0)
703                 .start();
704         mBackgroundLifted = false;
705     }
706 
707 
setMinimizedDockStack(boolean minimized, boolean isHomeStackResizable)708     public void setMinimizedDockStack(boolean minimized, boolean isHomeStackResizable) {
709         mHomeStackResizable = isHomeStackResizable;
710         updateDockSide();
711         if (!minimized) {
712             resetBackground();
713         } else if (!isHomeStackResizable) {
714             if (mDockSide == WindowManager.DOCKED_TOP) {
715                 mBackground.setPivotY(0);
716                 mBackground.setScaleY(MINIMIZE_DOCK_SCALE);
717             } else if (mDockSide == WindowManager.DOCKED_LEFT
718                     || mDockSide == WindowManager.DOCKED_RIGHT) {
719                 mBackground.setPivotX(mDockSide == WindowManager.DOCKED_LEFT
720                         ? 0
721                         : mBackground.getWidth());
722                 mBackground.setScaleX(MINIMIZE_DOCK_SCALE);
723             }
724         }
725         mMinimizedShadow.setAlpha(minimized ? 1f : 0f);
726         if (!isHomeStackResizable) {
727             mHandle.setAlpha(minimized ? 0f : 1f);
728             mDockedStackMinimized = minimized;
729         } else if (mDockedStackMinimized != minimized) {
730             if (mStableInsets.isEmpty()) {
731                 SystemServicesProxy.getInstance(mContext).getStableInsets(mStableInsets);
732             }
733             mMinimizedSnapAlgorithm = null;
734             mDockedStackMinimized = minimized;
735             initializeSnapAlgorithm();
736             if (mIsInMinimizeInteraction != minimized) {
737                 if (minimized) {
738                     mIsInMinimizeInteraction = true;
739                     mDividerPositionBeforeMinimized = DockedDividerUtils.calculateMiddlePosition(
740                             isHorizontalDivision(), mStableInsets, mDisplayWidth, mDisplayHeight,
741                             mDividerSize);
742 
743                     int position = mMinimizedSnapAlgorithm.getMiddleTarget().position;
744                     resizeStack(position, position, mMinimizedSnapAlgorithm.getMiddleTarget());
745                 } else {
746                     resizeStack(mDividerPositionBeforeMinimized, mDividerPositionBeforeMinimized,
747                             mSnapAlgorithm.calculateNonDismissingSnapTarget(
748                                     mDividerPositionBeforeMinimized));
749                     mIsInMinimizeInteraction = false;
750                 }
751             }
752         }
753     }
754 
setMinimizedDockStack(boolean minimized, long animDuration, boolean isHomeStackResizable)755     public void setMinimizedDockStack(boolean minimized, long animDuration,
756             boolean isHomeStackResizable) {
757         mHomeStackResizable = isHomeStackResizable;
758         updateDockSide();
759         if (!isHomeStackResizable) {
760             mMinimizedShadow.animate()
761                     .alpha(minimized ? 1f : 0f)
762                     .setInterpolator(Interpolators.ALPHA_IN)
763                     .setDuration(animDuration)
764                     .start();
765             mHandle.animate()
766                     .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
767                     .setDuration(animDuration)
768                     .alpha(minimized ? 0f : 1f)
769                     .start();
770             if (mDockSide == WindowManager.DOCKED_TOP) {
771                 mBackground.setPivotY(0);
772                 mBackground.animate()
773                         .scaleY(minimized ? MINIMIZE_DOCK_SCALE : 1f);
774             } else if (mDockSide == WindowManager.DOCKED_LEFT
775                     || mDockSide == WindowManager.DOCKED_RIGHT) {
776                 mBackground.setPivotX(mDockSide == WindowManager.DOCKED_LEFT
777                         ? 0
778                         : mBackground.getWidth());
779                 mBackground.animate()
780                         .scaleX(minimized ? MINIMIZE_DOCK_SCALE : 1f);
781             }
782             mDockedStackMinimized = minimized;
783         } else if (mDockedStackMinimized != minimized) {
784             mIsInMinimizeInteraction = true;
785             if (minimized && (mCurrentAnimator == null || !mCurrentAnimator.isRunning())
786                     && (mDividerPositionBeforeMinimized <= 0 || !mAdjustedForIme)) {
787                 mDividerPositionBeforeMinimized = getCurrentPosition();
788             }
789             mMinimizedSnapAlgorithm = null;
790             mDockedStackMinimized = minimized;
791             initializeSnapAlgorithm();
792             stopDragging(minimized
793                             ? mDividerPositionBeforeMinimized
794                             : getCurrentPosition(),
795                     minimized
796                             ? mMinimizedSnapAlgorithm.getMiddleTarget()
797                             : mSnapAlgorithm.calculateNonDismissingSnapTarget(
798                                     mDividerPositionBeforeMinimized),
799                     animDuration, Interpolators.FAST_OUT_SLOW_IN, 0);
800             setAdjustedForIme(false, animDuration);
801         }
802         if (!minimized) {
803             mBackground.animate().withEndAction(mResetBackgroundRunnable);
804         }
805         mBackground.animate()
806                 .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
807                 .setDuration(animDuration)
808                 .start();
809     }
810 
setAdjustedForIme(boolean adjustedForIme)811     public void setAdjustedForIme(boolean adjustedForIme) {
812         updateDockSide();
813         mHandle.setAlpha(adjustedForIme ? 0f : 1f);
814         if (!adjustedForIme) {
815             resetBackground();
816         } else if (mDockSide == WindowManager.DOCKED_TOP) {
817             mBackground.setPivotY(0);
818             mBackground.setScaleY(ADJUSTED_FOR_IME_SCALE);
819         }
820         mAdjustedForIme = adjustedForIme;
821     }
822 
setAdjustedForIme(boolean adjustedForIme, long animDuration)823     public void setAdjustedForIme(boolean adjustedForIme, long animDuration) {
824         updateDockSide();
825         mHandle.animate()
826                 .setInterpolator(IME_ADJUST_INTERPOLATOR)
827                 .setDuration(animDuration)
828                 .alpha(adjustedForIme ? 0f : 1f)
829                 .start();
830         if (mDockSide == WindowManager.DOCKED_TOP) {
831             mBackground.setPivotY(0);
832             mBackground.animate()
833                     .scaleY(adjustedForIme ? ADJUSTED_FOR_IME_SCALE : 1f);
834         }
835         if (!adjustedForIme) {
836             mBackground.animate().withEndAction(mResetBackgroundRunnable);
837         }
838         mBackground.animate()
839                 .setInterpolator(IME_ADJUST_INTERPOLATOR)
840                 .setDuration(animDuration)
841                 .start();
842         mAdjustedForIme = adjustedForIme;
843 
844         // Only get new position if home stack is resizable, ime is open and not minimized
845         // (including the animation)
846         if (mHomeStackResizable && adjustedForIme && !mIsInMinimizeInteraction) {
847             mDividerPositionBeforeMinimized = getCurrentPosition();
848         }
849     }
850 
resetBackground()851     private void resetBackground() {
852         mBackground.setPivotX(mBackground.getWidth() / 2);
853         mBackground.setPivotY(mBackground.getHeight() / 2);
854         mBackground.setScaleX(1f);
855         mBackground.setScaleY(1f);
856         mMinimizedShadow.setAlpha(0f);
857     }
858 
859     @Override
onConfigurationChanged(Configuration newConfig)860     protected void onConfigurationChanged(Configuration newConfig) {
861         super.onConfigurationChanged(newConfig);
862         updateDisplayInfo();
863     }
864 
865 
notifyDockSideChanged(int newDockSide)866     public void notifyDockSideChanged(int newDockSide) {
867         mDockSide = newDockSide;
868         mMinimizedShadow.setDockSide(mDockSide);
869         requestLayout();
870     }
871 
updateDisplayInfo()872     private void updateDisplayInfo() {
873         final DisplayManager displayManager =
874                 (DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE);
875         Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
876         final DisplayInfo info = new DisplayInfo();
877         display.getDisplayInfo(info);
878         mDisplayWidth = info.logicalWidth;
879         mDisplayHeight = info.logicalHeight;
880         mSnapAlgorithm = null;
881         mMinimizedSnapAlgorithm = null;
882         initializeSnapAlgorithm();
883     }
884 
calculatePosition(int touchX, int touchY)885     private int calculatePosition(int touchX, int touchY) {
886         return isHorizontalDivision() ? calculateYPosition(touchY) : calculateXPosition(touchX);
887     }
888 
isHorizontalDivision()889     public boolean isHorizontalDivision() {
890         return getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
891     }
892 
calculateXPosition(int touchX)893     private int calculateXPosition(int touchX) {
894         return mStartPosition + touchX - mStartX;
895     }
896 
calculateYPosition(int touchY)897     private int calculateYPosition(int touchY) {
898         return mStartPosition + touchY - mStartY;
899     }
900 
alignTopLeft(Rect containingRect, Rect rect)901     private void alignTopLeft(Rect containingRect, Rect rect) {
902         int width = rect.width();
903         int height = rect.height();
904         rect.set(containingRect.left, containingRect.top,
905                 containingRect.left + width, containingRect.top + height);
906     }
907 
alignBottomRight(Rect containingRect, Rect rect)908     private void alignBottomRight(Rect containingRect, Rect rect) {
909         int width = rect.width();
910         int height = rect.height();
911         rect.set(containingRect.right - width, containingRect.bottom - height,
912                 containingRect.right, containingRect.bottom);
913     }
914 
calculateBoundsForPosition(int position, int dockSide, Rect outRect)915     public void calculateBoundsForPosition(int position, int dockSide, Rect outRect) {
916         DockedDividerUtils.calculateBoundsForPosition(position, dockSide, outRect, mDisplayWidth,
917                 mDisplayHeight, mDividerSize);
918     }
919 
resizeStackDelayed(int position, int taskPosition, SnapTarget taskSnapTarget)920     public void resizeStackDelayed(int position, int taskPosition, SnapTarget taskSnapTarget) {
921         Message message = mHandler.obtainMessage(MSG_RESIZE_STACK, position, taskPosition,
922                 taskSnapTarget);
923         message.setAsynchronous(true);
924         mSfChoreographer.scheduleAtSfVsync(mHandler, message);
925     }
926 
resizeStack(int position, int taskPosition, SnapTarget taskSnapTarget)927     public void resizeStack(int position, int taskPosition, SnapTarget taskSnapTarget) {
928         if (mRemoved) {
929             // This divider view has been removed so shouldn't have any additional influence.
930             return;
931         }
932         calculateBoundsForPosition(position, mDockSide, mDockedRect);
933 
934         if (mDockedRect.equals(mLastResizeRect) && !mEntranceAnimationRunning) {
935             return;
936         }
937 
938         // Make sure shadows are updated
939         if (mBackground.getZ() > 0f) {
940             mBackground.invalidate();
941         }
942 
943         mLastResizeRect.set(mDockedRect);
944         if (mHomeStackResizable && mIsInMinimizeInteraction) {
945             calculateBoundsForPosition(mDividerPositionBeforeMinimized, mDockSide, mDockedTaskRect);
946             calculateBoundsForPosition(mDividerPositionBeforeMinimized,
947                     DockedDividerUtils.invertDockSide(mDockSide), mOtherTaskRect);
948             mWindowManagerProxy.resizeDockedStack(mDockedRect, mDockedTaskRect, mDockedTaskRect,
949                     mOtherTaskRect, null);
950             return;
951         }
952 
953         if (mEntranceAnimationRunning && taskPosition != TASK_POSITION_SAME) {
954             if (mCurrentAnimator != null) {
955                 calculateBoundsForPosition(taskPosition, mDockSide, mDockedTaskRect);
956             } else {
957                 calculateBoundsForPosition(isHorizontalDivision() ? mDisplayHeight : mDisplayWidth,
958                         mDockSide, mDockedTaskRect);
959             }
960             calculateBoundsForPosition(taskPosition, DockedDividerUtils.invertDockSide(mDockSide),
961                     mOtherTaskRect);
962             mWindowManagerProxy.resizeDockedStack(mDockedRect, mDockedTaskRect, null,
963                     mOtherTaskRect, null);
964         } else if (mExitAnimationRunning && taskPosition != TASK_POSITION_SAME) {
965             calculateBoundsForPosition(taskPosition,
966                     mDockSide, mDockedTaskRect);
967             calculateBoundsForPosition(mExitStartPosition,
968                     DockedDividerUtils.invertDockSide(mDockSide), mOtherTaskRect);
969             mOtherInsetRect.set(mOtherTaskRect);
970             applyExitAnimationParallax(mOtherTaskRect, position);
971             mWindowManagerProxy.resizeDockedStack(mDockedRect, mDockedTaskRect, null,
972                     mOtherTaskRect, mOtherInsetRect);
973         } else if (taskPosition != TASK_POSITION_SAME) {
974             calculateBoundsForPosition(position, DockedDividerUtils.invertDockSide(mDockSide),
975                     mOtherRect);
976             int dockSideInverted = DockedDividerUtils.invertDockSide(mDockSide);
977             int taskPositionDocked =
978                     restrictDismissingTaskPosition(taskPosition, mDockSide, taskSnapTarget);
979             int taskPositionOther =
980                     restrictDismissingTaskPosition(taskPosition, dockSideInverted, taskSnapTarget);
981             calculateBoundsForPosition(taskPositionDocked, mDockSide, mDockedTaskRect);
982             calculateBoundsForPosition(taskPositionOther, dockSideInverted, mOtherTaskRect);
983             mDisplayRect.set(0, 0, mDisplayWidth, mDisplayHeight);
984             alignTopLeft(mDockedRect, mDockedTaskRect);
985             alignTopLeft(mOtherRect, mOtherTaskRect);
986             mDockedInsetRect.set(mDockedTaskRect);
987             mOtherInsetRect.set(mOtherTaskRect);
988             if (dockSideTopLeft(mDockSide)) {
989                 alignTopLeft(mDisplayRect, mDockedInsetRect);
990                 alignBottomRight(mDisplayRect, mOtherInsetRect);
991             } else {
992                 alignBottomRight(mDisplayRect, mDockedInsetRect);
993                 alignTopLeft(mDisplayRect, mOtherInsetRect);
994             }
995             applyDismissingParallax(mDockedTaskRect, mDockSide, taskSnapTarget, position,
996                     taskPositionDocked);
997             applyDismissingParallax(mOtherTaskRect, dockSideInverted, taskSnapTarget, position,
998                     taskPositionOther);
999             mWindowManagerProxy.resizeDockedStack(mDockedRect, mDockedTaskRect, mDockedInsetRect,
1000                     mOtherTaskRect, mOtherInsetRect);
1001         } else {
1002             mWindowManagerProxy.resizeDockedStack(mDockedRect, null, null, null, null);
1003         }
1004         SnapTarget closestDismissTarget = getSnapAlgorithm().getClosestDismissTarget(position);
1005         float dimFraction = getDimFraction(position, closestDismissTarget);
1006         mWindowManagerProxy.setResizeDimLayer(dimFraction != 0f,
1007                 getStackIdForDismissTarget(closestDismissTarget),
1008                 dimFraction);
1009     }
1010 
applyExitAnimationParallax(Rect taskRect, int position)1011     private void applyExitAnimationParallax(Rect taskRect, int position) {
1012         if (mDockSide == WindowManager.DOCKED_TOP) {
1013             taskRect.offset(0, (int) ((position - mExitStartPosition) * 0.25f));
1014         } else if (mDockSide == WindowManager.DOCKED_LEFT) {
1015             taskRect.offset((int) ((position - mExitStartPosition) * 0.25f), 0);
1016         } else if (mDockSide == WindowManager.DOCKED_RIGHT) {
1017             taskRect.offset((int) ((mExitStartPosition - position) * 0.25f), 0);
1018         }
1019     }
1020 
getDimFraction(int position, SnapTarget dismissTarget)1021     private float getDimFraction(int position, SnapTarget dismissTarget) {
1022         if (mEntranceAnimationRunning) {
1023             return 0f;
1024         }
1025         float fraction = getSnapAlgorithm().calculateDismissingFraction(position);
1026         fraction = Math.max(0, Math.min(fraction, 1f));
1027         fraction = DIM_INTERPOLATOR.getInterpolation(fraction);
1028         if (hasInsetsAtDismissTarget(dismissTarget)) {
1029 
1030             // Less darkening with system insets.
1031             fraction *= 0.8f;
1032         }
1033         return fraction;
1034     }
1035 
1036     /**
1037      * @return true if and only if there are system insets at the location of the dismiss target
1038      */
hasInsetsAtDismissTarget(SnapTarget dismissTarget)1039     private boolean hasInsetsAtDismissTarget(SnapTarget dismissTarget) {
1040         if (isHorizontalDivision()) {
1041             if (dismissTarget == getSnapAlgorithm().getDismissStartTarget()) {
1042                 return mStableInsets.top != 0;
1043             } else {
1044                 return mStableInsets.bottom != 0;
1045             }
1046         } else {
1047             if (dismissTarget == getSnapAlgorithm().getDismissStartTarget()) {
1048                 return mStableInsets.left != 0;
1049             } else {
1050                 return mStableInsets.right != 0;
1051             }
1052         }
1053     }
1054 
1055     /**
1056      * When the snap target is dismissing one side, make sure that the dismissing side doesn't get
1057      * 0 size.
1058      */
restrictDismissingTaskPosition(int taskPosition, int dockSide, SnapTarget snapTarget)1059     private int restrictDismissingTaskPosition(int taskPosition, int dockSide,
1060             SnapTarget snapTarget) {
1061         if (snapTarget.flag == SnapTarget.FLAG_DISMISS_START && dockSideTopLeft(dockSide)) {
1062             return Math.max(mSnapAlgorithm.getFirstSplitTarget().position, mStartPosition);
1063         } else if (snapTarget.flag == SnapTarget.FLAG_DISMISS_END
1064                 && dockSideBottomRight(dockSide)) {
1065             return Math.min(mSnapAlgorithm.getLastSplitTarget().position, mStartPosition);
1066         } else {
1067             return taskPosition;
1068         }
1069     }
1070 
1071     /**
1072      * Applies a parallax to the task when dismissing.
1073      */
applyDismissingParallax(Rect taskRect, int dockSide, SnapTarget snapTarget, int position, int taskPosition)1074     private void applyDismissingParallax(Rect taskRect, int dockSide, SnapTarget snapTarget,
1075             int position, int taskPosition) {
1076         float fraction = Math.min(1, Math.max(0,
1077                 mSnapAlgorithm.calculateDismissingFraction(position)));
1078         SnapTarget dismissTarget = null;
1079         SnapTarget splitTarget = null;
1080         int start = 0;
1081         if (position <= mSnapAlgorithm.getLastSplitTarget().position
1082                 && dockSideTopLeft(dockSide)) {
1083             dismissTarget = mSnapAlgorithm.getDismissStartTarget();
1084             splitTarget = mSnapAlgorithm.getFirstSplitTarget();
1085             start = taskPosition;
1086         } else if (position >= mSnapAlgorithm.getLastSplitTarget().position
1087                 && dockSideBottomRight(dockSide)) {
1088             dismissTarget = mSnapAlgorithm.getDismissEndTarget();
1089             splitTarget = mSnapAlgorithm.getLastSplitTarget();
1090             start = splitTarget.position;
1091         }
1092         if (dismissTarget != null && fraction > 0f
1093                 && isDismissing(splitTarget, position, dockSide)) {
1094             fraction = calculateParallaxDismissingFraction(fraction, dockSide);
1095             int offsetPosition = (int) (start +
1096                     fraction * (dismissTarget.position - splitTarget.position));
1097             int width = taskRect.width();
1098             int height = taskRect.height();
1099             switch (dockSide) {
1100                 case WindowManager.DOCKED_LEFT:
1101                     taskRect.left = offsetPosition - width;
1102                     taskRect.right = offsetPosition;
1103                     break;
1104                 case WindowManager.DOCKED_RIGHT:
1105                     taskRect.left = offsetPosition + mDividerSize;
1106                     taskRect.right = offsetPosition + width + mDividerSize;
1107                     break;
1108                 case WindowManager.DOCKED_TOP:
1109                     taskRect.top = offsetPosition - height;
1110                     taskRect.bottom = offsetPosition;
1111                     break;
1112                 case WindowManager.DOCKED_BOTTOM:
1113                     taskRect.top = offsetPosition + mDividerSize;
1114                     taskRect.bottom = offsetPosition + height + mDividerSize;
1115                     break;
1116             }
1117         }
1118     }
1119 
1120     /**
1121      * @return for a specified {@code fraction}, this returns an adjusted value that simulates a
1122      *         slowing down parallax effect
1123      */
calculateParallaxDismissingFraction(float fraction, int dockSide)1124     private static float calculateParallaxDismissingFraction(float fraction, int dockSide) {
1125         float result = SLOWDOWN_INTERPOLATOR.getInterpolation(fraction) / 3.5f;
1126 
1127         // Less parallax at the top, just because.
1128         if (dockSide == WindowManager.DOCKED_TOP) {
1129             result /= 2f;
1130         }
1131         return result;
1132     }
1133 
isDismissing(SnapTarget snapTarget, int position, int dockSide)1134     private static boolean isDismissing(SnapTarget snapTarget, int position, int dockSide) {
1135         if (dockSide == WindowManager.DOCKED_TOP || dockSide == WindowManager.DOCKED_LEFT) {
1136             return position < snapTarget.position;
1137         } else {
1138             return position > snapTarget.position;
1139         }
1140     }
1141 
getStackIdForDismissTarget(SnapTarget dismissTarget)1142     private int getStackIdForDismissTarget(SnapTarget dismissTarget) {
1143         if ((dismissTarget.flag == SnapTarget.FLAG_DISMISS_START && dockSideTopLeft(mDockSide))
1144                 || (dismissTarget.flag == SnapTarget.FLAG_DISMISS_END
1145                         && dockSideBottomRight(mDockSide))) {
1146             return StackId.DOCKED_STACK_ID;
1147         } else {
1148             return StackId.RECENTS_STACK_ID;
1149         }
1150     }
1151 
1152     /**
1153      * @return true if and only if {@code dockSide} is top or left
1154      */
dockSideTopLeft(int dockSide)1155     private static boolean dockSideTopLeft(int dockSide) {
1156         return dockSide == WindowManager.DOCKED_TOP || dockSide == WindowManager.DOCKED_LEFT;
1157     }
1158 
1159     /**
1160      * @return true if and only if {@code dockSide} is bottom or right
1161      */
dockSideBottomRight(int dockSide)1162     private static boolean dockSideBottomRight(int dockSide) {
1163         return dockSide == WindowManager.DOCKED_BOTTOM || dockSide == WindowManager.DOCKED_RIGHT;
1164     }
1165 
1166     @Override
onComputeInternalInsets(InternalInsetsInfo inoutInfo)1167     public void onComputeInternalInsets(InternalInsetsInfo inoutInfo) {
1168         inoutInfo.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
1169         inoutInfo.touchableRegion.set(mHandle.getLeft(), mHandle.getTop(), mHandle.getRight(),
1170                 mHandle.getBottom());
1171         inoutInfo.touchableRegion.op(mBackground.getLeft(), mBackground.getTop(),
1172                 mBackground.getRight(), mBackground.getBottom(), Op.UNION);
1173     }
1174 
1175     /**
1176      * Checks whether recents will grow when invoked. This happens in multi-window when recents is
1177      * very small. When invoking recents, we shrink the docked stack so recents has more space.
1178      *
1179      * @return the position of the divider when recents grows, or
1180      *         {@link #INVALID_RECENTS_GROW_TARGET} if recents won't grow
1181      */
growsRecents()1182     public int growsRecents() {
1183         boolean result = mGrowRecents
1184                 && mDockSide == WindowManager.DOCKED_TOP
1185                 && getCurrentPosition() == getSnapAlgorithm().getLastSplitTarget().position;
1186         if (result) {
1187             return getSnapAlgorithm().getMiddleTarget().position;
1188         } else {
1189             return INVALID_RECENTS_GROW_TARGET;
1190         }
1191     }
1192 
onBusEvent(RecentsActivityStartingEvent recentsActivityStartingEvent)1193     public final void onBusEvent(RecentsActivityStartingEvent recentsActivityStartingEvent) {
1194         if (mGrowRecents && mDockSide == WindowManager.DOCKED_TOP
1195                 && getSnapAlgorithm().getMiddleTarget() != getSnapAlgorithm().getLastSplitTarget()
1196                 && getCurrentPosition() == getSnapAlgorithm().getLastSplitTarget().position) {
1197             mState.growAfterRecentsDrawn = true;
1198             startDragging(false /* animate */, false /* touching */);
1199         }
1200     }
1201 
onBusEvent(DockedTopTaskEvent event)1202     public final void onBusEvent(DockedTopTaskEvent event) {
1203         if (event.dragMode == NavigationBarGestureHelper.DRAG_MODE_NONE) {
1204             mState.growAfterRecentsDrawn = false;
1205             mState.animateAfterRecentsDrawn = true;
1206             startDragging(false /* animate */, false /* touching */);
1207         }
1208         updateDockSide();
1209         int position = DockedDividerUtils.calculatePositionForBounds(event.initialRect,
1210                 mDockSide, mDividerSize);
1211         mEntranceAnimationRunning = true;
1212 
1213         // Insets might not have been fetched yet, so fetch manually if needed.
1214         if (mStableInsets.isEmpty()) {
1215             SystemServicesProxy.getInstance(mContext).getStableInsets(mStableInsets);
1216             mSnapAlgorithm = null;
1217             mMinimizedSnapAlgorithm = null;
1218             initializeSnapAlgorithm();
1219         }
1220 
1221         resizeStack(position, mSnapAlgorithm.getMiddleTarget().position,
1222                 mSnapAlgorithm.getMiddleTarget());
1223     }
1224 
onRecentsDrawn()1225     public void onRecentsDrawn() {
1226         if (mState.animateAfterRecentsDrawn) {
1227             mState.animateAfterRecentsDrawn = false;
1228             updateDockSide();
1229 
1230             mHandler.post(() -> {
1231                 // Delay switching resizing mode because this might cause jank in recents animation
1232                 // that's longer than this animation.
1233                 stopDragging(getCurrentPosition(), mSnapAlgorithm.getMiddleTarget(),
1234                         mLongPressEntraceAnimDuration, Interpolators.FAST_OUT_SLOW_IN,
1235                         200 /* endDelay */);
1236             });
1237         }
1238         if (mState.growAfterRecentsDrawn) {
1239             mState.growAfterRecentsDrawn = false;
1240             updateDockSide();
1241             EventBus.getDefault().send(new RecentsGrowingEvent());
1242             stopDragging(getCurrentPosition(), mSnapAlgorithm.getMiddleTarget(), 336,
1243                     Interpolators.FAST_OUT_SLOW_IN);
1244         }
1245     }
1246 
onBusEvent(UndockingTaskEvent undockingTaskEvent)1247     public final void onBusEvent(UndockingTaskEvent undockingTaskEvent) {
1248         int dockSide = mWindowManagerProxy.getDockSide();
1249         if (dockSide != WindowManager.DOCKED_INVALID && (mHomeStackResizable
1250                 || !mDockedStackMinimized)) {
1251             startDragging(false /* animate */, false /* touching */);
1252             SnapTarget target = dockSideTopLeft(dockSide)
1253                     ? mSnapAlgorithm.getDismissEndTarget()
1254                     : mSnapAlgorithm.getDismissStartTarget();
1255 
1256             // Don't start immediately - give a little bit time to settle the drag resize change.
1257             mExitAnimationRunning = true;
1258             mExitStartPosition = getCurrentPosition();
1259             stopDragging(mExitStartPosition, target, 336 /* duration */, 100 /* startDelay */,
1260                     0 /* endDelay */, Interpolators.FAST_OUT_SLOW_IN);
1261         }
1262     }
1263 }
1264