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