2 * Copyright (C) 2015 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.systemui.stackdivider;
19 import static android.view.PointerIcon.TYPE_HORIZONTAL_DOUBLE_ARROW;
20 import static android.view.PointerIcon.TYPE_VERTICAL_DOUBLE_ARROW;
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.util.AttributeSet;
35 import android.view.Display;
36 import android.view.DisplayInfo;
37 import android.view.GestureDetector;
38 import android.view.GestureDetector.SimpleOnGestureListener;
39 import android.view.MotionEvent;
40 import android.view.PointerIcon;
41 import android.view.VelocityTracker;
42 import android.view.View;
43 import android.view.View.OnTouchListener;
44 import android.view.ViewConfiguration;
45 import android.view.ViewTreeObserver.InternalInsetsInfo;
46 import android.view.ViewTreeObserver.OnComputeInternalInsetsListener;
47 import android.view.WindowInsets;
48 import android.view.WindowManager;
49 import android.view.accessibility.AccessibilityNodeInfo;
50 import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
51 import android.view.animation.Interpolator;
52 import android.view.animation.PathInterpolator;
53 import android.widget.FrameLayout;
55 import com.android.internal.logging.MetricsLogger;
56 import com.android.internal.logging.MetricsProto.MetricsEvent;
57 import com.android.internal.policy.DividerSnapAlgorithm;
58 import com.android.internal.policy.DividerSnapAlgorithm.SnapTarget;
59 import com.android.internal.policy.DockedDividerUtils;
60 import com.android.systemui.Interpolators;
61 import com.android.systemui.R;
62 import com.android.systemui.recents.Recents;
63 import com.android.systemui.recents.events.EventBus;
64 import com.android.systemui.recents.events.activity.DockedTopTaskEvent;
65 import com.android.systemui.recents.events.activity.RecentsActivityStartingEvent;
66 import com.android.systemui.recents.events.activity.UndockingTaskEvent;
67 import com.android.systemui.recents.events.ui.RecentsDrawnEvent;
68 import com.android.systemui.recents.events.ui.RecentsGrowingEvent;
69 import com.android.systemui.recents.misc.SystemServicesProxy;
70 import com.android.systemui.stackdivider.events.StartedDragingEvent;
71 import com.android.systemui.stackdivider.events.StoppedDragingEvent;
72 import com.android.systemui.statusbar.FlingAnimationUtils;
73 import com.android.systemui.statusbar.phone.NavigationBarGestureHelper;
76 * Docked stack divider.
78 public class DividerView extends FrameLayout implements OnTouchListener,
79 OnComputeInternalInsetsListener {
81 static final long TOUCH_ANIMATION_DURATION = 150;
82 static final long TOUCH_RELEASE_ANIMATION_DURATION = 200;
84 public static final int INVALID_RECENTS_GROW_TARGET = -1;
86 private static final int LOG_VALUE_RESIZE_50_50 = 0;
87 private static final int LOG_VALUE_RESIZE_DOCKED_SMALLER = 1;
88 private static final int LOG_VALUE_RESIZE_DOCKED_LARGER = 2;
90 private static final int LOG_VALUE_UNDOCK_MAX_DOCKED = 0;
91 private static final int LOG_VALUE_UNDOCK_MAX_OTHER = 1;
93 private static final int TASK_POSITION_SAME = Integer.MAX_VALUE;
94 private static final boolean SWAPPING_ENABLED = false;
97 * How much the background gets scaled when we are in the minimized dock state.
99 private static final float MINIMIZE_DOCK_SCALE = 0f;
100 private static final float ADJUSTED_FOR_IME_SCALE = 0.5f;
102 private static final PathInterpolator SLOWDOWN_INTERPOLATOR =
103 new PathInterpolator(0.5f, 1f, 0.5f, 1f);
104 private static final PathInterpolator DIM_INTERPOLATOR =
105 new PathInterpolator(.23f, .87f, .52f, -0.11f);
106 private static final Interpolator IME_ADJUST_INTERPOLATOR =
107 new PathInterpolator(0.2f, 0f, 0.1f, 1f);
109 private DividerHandleView mHandle;
110 private View mBackground;
111 private MinimizedDockShadow mMinimizedShadow;
114 private int mStartPosition;
115 private int mDockSide;
116 private final int[] mTempInt2 = new int[2];
117 private boolean mMoving;
118 private int mTouchSlop;
119 private boolean mBackgroundLifted;
121 private int mDividerInsets;
122 private int mDisplayWidth;
123 private int mDisplayHeight;
124 private int mDividerWindowWidth;
125 private int mDividerSize;
126 private int mTouchElevation;
127 private int mLongPressEntraceAnimDuration;
129 private final Rect mDockedRect = new Rect();
130 private final Rect mDockedTaskRect = new Rect();
131 private final Rect mOtherTaskRect = new Rect();
132 private final Rect mOtherRect = new Rect();
133 private final Rect mDockedInsetRect = new Rect();
134 private final Rect mOtherInsetRect = new Rect();
135 private final Rect mLastResizeRect = new Rect();
136 private final Rect mDisplayRect = new Rect();
137 private final WindowManagerProxy mWindowManagerProxy = WindowManagerProxy.getInstance();
138 private DividerWindowManager mWindowManager;
139 private VelocityTracker mVelocityTracker;
140 private FlingAnimationUtils mFlingAnimationUtils;
141 private DividerSnapAlgorithm mSnapAlgorithm;
142 private final Rect mStableInsets = new Rect();
144 private boolean mGrowRecents;
145 private ValueAnimator mCurrentAnimator;
146 private boolean mEntranceAnimationRunning;
147 private boolean mExitAnimationRunning;
148 private int mExitStartPosition;
149 private GestureDetector mGestureDetector;
150 private boolean mDockedStackMinimized;
151 private boolean mAdjustedForIme;
152 private DividerState mState;
153 private final Handler mHandler = new Handler();
155 private final AccessibilityDelegate mHandleDelegate = new AccessibilityDelegate() {
157 public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
158 super.onInitializeAccessibilityNodeInfo(host, info);
159 if (isHorizontalDivision()) {
160 info.addAction(new AccessibilityAction(R.id.action_move_tl_full,
161 mContext.getString(R.string.accessibility_action_divider_top_full)));
162 if (mSnapAlgorithm.isFirstSplitTargetAvailable()) {
163 info.addAction(new AccessibilityAction(R.id.action_move_tl_70,
164 mContext.getString(R.string.accessibility_action_divider_top_70)));
166 info.addAction(new AccessibilityAction(R.id.action_move_tl_50,
167 mContext.getString(R.string.accessibility_action_divider_top_50)));
168 if (mSnapAlgorithm.isLastSplitTargetAvailable()) {
169 info.addAction(new AccessibilityAction(R.id.action_move_tl_30,
170 mContext.getString(R.string.accessibility_action_divider_top_30)));
172 info.addAction(new AccessibilityAction(R.id.action_move_rb_full,
173 mContext.getString(R.string.accessibility_action_divider_bottom_full)));
175 info.addAction(new AccessibilityAction(R.id.action_move_tl_full,
176 mContext.getString(R.string.accessibility_action_divider_left_full)));
177 if (mSnapAlgorithm.isFirstSplitTargetAvailable()) {
178 info.addAction(new AccessibilityAction(R.id.action_move_tl_70,
179 mContext.getString(R.string.accessibility_action_divider_left_70)));
181 info.addAction(new AccessibilityAction(R.id.action_move_tl_50,
182 mContext.getString(R.string.accessibility_action_divider_left_50)));
183 if (mSnapAlgorithm.isLastSplitTargetAvailable()) {
184 info.addAction(new AccessibilityAction(R.id.action_move_tl_30,
185 mContext.getString(R.string.accessibility_action_divider_left_30)));
187 info.addAction(new AccessibilityAction(R.id.action_move_rb_full,
188 mContext.getString(R.string.accessibility_action_divider_right_full)));
193 public boolean performAccessibilityAction(View host, int action, Bundle args) {
194 int currentPosition = getCurrentPosition();
195 SnapTarget nextTarget = null;
197 case R.id.action_move_tl_full:
198 nextTarget = mSnapAlgorithm.getDismissEndTarget();
200 case R.id.action_move_tl_70:
201 nextTarget = mSnapAlgorithm.getLastSplitTarget();
203 case R.id.action_move_tl_50:
204 nextTarget = mSnapAlgorithm.getMiddleTarget();
206 case R.id.action_move_tl_30:
207 nextTarget = mSnapAlgorithm.getFirstSplitTarget();
209 case R.id.action_move_rb_full:
210 nextTarget = mSnapAlgorithm.getDismissStartTarget();
213 if (nextTarget != null) {
214 startDragging(true /* animate */, false /* touching */);
215 stopDragging(currentPosition, nextTarget, 250, Interpolators.FAST_OUT_SLOW_IN);
218 return super.performAccessibilityAction(host, action, args);
222 private final Runnable mResetBackgroundRunnable = new Runnable() {
229 public DividerView(Context context) {
233 public DividerView(Context context, @Nullable AttributeSet attrs) {
234 super(context, attrs);
237 public DividerView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
238 super(context, attrs, defStyleAttr);
241 public DividerView(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
243 super(context, attrs, defStyleAttr, defStyleRes);
247 protected void onFinishInflate() {
248 super.onFinishInflate();
249 mHandle = (DividerHandleView) findViewById(R.id.docked_divider_handle);
250 mBackground = findViewById(R.id.docked_divider_background);
251 mMinimizedShadow = (MinimizedDockShadow) findViewById(R.id.minimized_dock_shadow);
252 mHandle.setOnTouchListener(this);
253 mDividerWindowWidth = getResources().getDimensionPixelSize(
254 com.android.internal.R.dimen.docked_stack_divider_thickness);
255 mDividerInsets = getResources().getDimensionPixelSize(
256 com.android.internal.R.dimen.docked_stack_divider_insets);
257 mDividerSize = mDividerWindowWidth - 2 * mDividerInsets;
258 mTouchElevation = getResources().getDimensionPixelSize(
259 R.dimen.docked_stack_divider_lift_elevation);
260 mLongPressEntraceAnimDuration = getResources().getInteger(
261 R.integer.long_press_dock_anim_duration);
262 mGrowRecents = getResources().getBoolean(R.bool.recents_grow_in_multiwindow);
263 mTouchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop();
264 mFlingAnimationUtils = new FlingAnimationUtils(getContext(), 0.3f);
266 boolean landscape = getResources().getConfiguration().orientation
267 == Configuration.ORIENTATION_LANDSCAPE;
268 mHandle.setPointerIcon(PointerIcon.getSystemIcon(getContext(),
269 landscape ? TYPE_HORIZONTAL_DOUBLE_ARROW : TYPE_VERTICAL_DOUBLE_ARROW));
270 getViewTreeObserver().addOnComputeInternalInsetsListener(this);
271 mHandle.setAccessibilityDelegate(mHandleDelegate);
272 mGestureDetector = new GestureDetector(mContext, new SimpleOnGestureListener() {
274 public boolean onSingleTapUp(MotionEvent e) {
275 if (SWAPPING_ENABLED) {
277 SystemServicesProxy ssp = Recents.getSystemServices();
278 if (mDockSide != WindowManager.DOCKED_INVALID
279 && !ssp.isRecentsActivityVisible()) {
280 mWindowManagerProxy.swapTasks();
290 protected void onAttachedToWindow() {
291 super.onAttachedToWindow();
292 EventBus.getDefault().register(this);
296 protected void onDetachedFromWindow() {
297 super.onDetachedFromWindow();
298 EventBus.getDefault().unregister(this);
302 public WindowInsets onApplyWindowInsets(WindowInsets insets) {
303 if (mStableInsets.left != insets.getStableInsetLeft()
304 || mStableInsets.top != insets.getStableInsetTop()
305 || mStableInsets.right != insets.getStableInsetRight()
306 || mStableInsets.bottom != insets.getStableInsetBottom()) {
307 mStableInsets.set(insets.getStableInsetLeft(), insets.getStableInsetTop(),
308 insets.getStableInsetRight(), insets.getStableInsetBottom());
309 if (mSnapAlgorithm != null) {
310 mSnapAlgorithm = null;
311 initializeSnapAlgorithm();
314 return super.onApplyWindowInsets(insets);
318 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
319 super.onLayout(changed, left, top, right, bottom);
320 int minimizeLeft = 0;
322 if (mDockSide == WindowManager.DOCKED_TOP) {
323 minimizeTop = mBackground.getTop();
324 } else if (mDockSide == WindowManager.DOCKED_LEFT) {
325 minimizeLeft = mBackground.getLeft();
326 } else if (mDockSide == WindowManager.DOCKED_RIGHT) {
327 minimizeLeft = mBackground.getRight() - mMinimizedShadow.getWidth();
329 mMinimizedShadow.layout(minimizeLeft, minimizeTop,
330 minimizeLeft + mMinimizedShadow.getMeasuredWidth(),
331 minimizeTop + mMinimizedShadow.getMeasuredHeight());
333 mWindowManagerProxy.setTouchRegion(new Rect(mHandle.getLeft(), mHandle.getTop(),
334 mHandle.getRight(), mHandle.getBottom()));
338 public void injectDependencies(DividerWindowManager windowManager, DividerState dividerState) {
339 mWindowManager = windowManager;
340 mState = dividerState;
343 public WindowManagerProxy getWindowManagerProxy() {
344 return mWindowManagerProxy;
347 public boolean startDragging(boolean animate, boolean touching) {
348 cancelFlingAnimation();
350 mHandle.setTouching(true, animate);
352 mDockSide = mWindowManagerProxy.getDockSide();
353 initializeSnapAlgorithm();
354 mWindowManagerProxy.setResizing(true);
356 mWindowManager.setSlippery(false);
359 EventBus.getDefault().send(new StartedDragingEvent());
360 return mDockSide != WindowManager.DOCKED_INVALID;
363 public void stopDragging(int position, float velocity, boolean avoidDismissStart,
364 boolean logMetrics) {
365 mHandle.setTouching(false, true /* animate */);
366 fling(position, velocity, avoidDismissStart, logMetrics);
367 mWindowManager.setSlippery(true);
371 public void stopDragging(int position, SnapTarget target, long duration,
372 Interpolator interpolator) {
373 stopDragging(position, target, duration, 0 /* startDelay*/, 0 /* endDelay */, interpolator);
376 public void stopDragging(int position, SnapTarget target, long duration,
377 Interpolator interpolator, long endDelay) {
378 stopDragging(position, target, duration, 0 /* startDelay*/, endDelay, interpolator);
381 public void stopDragging(int position, SnapTarget target, long duration, long startDelay,
382 long endDelay, Interpolator interpolator) {
383 mHandle.setTouching(false, true /* animate */);
384 flingTo(position, target, duration, startDelay, endDelay, interpolator);
385 mWindowManager.setSlippery(true);
389 private void stopDragging() {
390 mHandle.setTouching(false, true /* animate */);
391 mWindowManager.setSlippery(true);
395 private void updateDockSide() {
396 mDockSide = mWindowManagerProxy.getDockSide();
397 mMinimizedShadow.setDockSide(mDockSide);
400 private void initializeSnapAlgorithm() {
401 if (mSnapAlgorithm == null) {
402 mSnapAlgorithm = new DividerSnapAlgorithm(getContext().getResources(), mDisplayWidth,
403 mDisplayHeight, mDividerSize, isHorizontalDivision(), mStableInsets);
407 public DividerSnapAlgorithm getSnapAlgorithm() {
408 initializeSnapAlgorithm();
409 return mSnapAlgorithm;
412 public int getCurrentPosition() {
413 getLocationOnScreen(mTempInt2);
414 if (isHorizontalDivision()) {
415 return mTempInt2[1] + mDividerInsets;
417 return mTempInt2[0] + mDividerInsets;
422 public boolean onTouch(View v, MotionEvent event) {
423 convertToScreenCoordinates(event);
424 mGestureDetector.onTouchEvent(event);
425 final int action = event.getAction() & MotionEvent.ACTION_MASK;
427 case MotionEvent.ACTION_DOWN:
428 mVelocityTracker = VelocityTracker.obtain();
429 mVelocityTracker.addMovement(event);
430 mStartX = (int) event.getX();
431 mStartY = (int) event.getY();
432 boolean result = startDragging(true /* animate */, true /* touching */);
435 // Weren't able to start dragging successfully, so cancel it again.
438 mStartPosition = getCurrentPosition();
441 case MotionEvent.ACTION_MOVE:
442 mVelocityTracker.addMovement(event);
443 int x = (int) event.getX();
444 int y = (int) event.getY();
445 boolean exceededTouchSlop =
446 isHorizontalDivision() && Math.abs(y - mStartY) > mTouchSlop
447 || (!isHorizontalDivision() && Math.abs(x - mStartX) > mTouchSlop);
448 if (!mMoving && exceededTouchSlop) {
453 if (mMoving && mDockSide != WindowManager.DOCKED_INVALID) {
454 SnapTarget snapTarget = mSnapAlgorithm.calculateSnapTarget(
455 mStartPosition, 0 /* velocity */, false /* hardDismiss */);
456 resizeStack(calculatePosition(x, y), mStartPosition, snapTarget);
459 case MotionEvent.ACTION_UP:
460 case MotionEvent.ACTION_CANCEL:
461 mVelocityTracker.addMovement(event);
463 x = (int) event.getRawX();
464 y = (int) event.getRawY();
466 mVelocityTracker.computeCurrentVelocity(1000);
467 int position = calculatePosition(x, y);
468 stopDragging(position, isHorizontalDivision() ? mVelocityTracker.getYVelocity()
469 : mVelocityTracker.getXVelocity(), false /* avoidDismissStart */,
477 private void logResizeEvent(SnapTarget snapTarget) {
478 if (snapTarget == mSnapAlgorithm.getDismissStartTarget()) {
479 MetricsLogger.action(
480 mContext, MetricsEvent.ACTION_WINDOW_UNDOCK_MAX, dockSideTopLeft(mDockSide)
481 ? LOG_VALUE_UNDOCK_MAX_OTHER
482 : LOG_VALUE_UNDOCK_MAX_DOCKED);
483 } else if (snapTarget == mSnapAlgorithm.getDismissEndTarget()) {
484 MetricsLogger.action(
485 mContext, MetricsEvent.ACTION_WINDOW_UNDOCK_MAX, dockSideBottomRight(mDockSide)
486 ? LOG_VALUE_UNDOCK_MAX_OTHER
487 : LOG_VALUE_UNDOCK_MAX_DOCKED);
488 } else if (snapTarget == mSnapAlgorithm.getMiddleTarget()) {
489 MetricsLogger.action(mContext, MetricsEvent.ACTION_WINDOW_DOCK_RESIZE,
490 LOG_VALUE_RESIZE_50_50);
491 } else if (snapTarget == mSnapAlgorithm.getFirstSplitTarget()) {
492 MetricsLogger.action(mContext, MetricsEvent.ACTION_WINDOW_DOCK_RESIZE,
493 dockSideTopLeft(mDockSide)
494 ? LOG_VALUE_RESIZE_DOCKED_SMALLER
495 : LOG_VALUE_RESIZE_DOCKED_LARGER);
496 } else if (snapTarget == mSnapAlgorithm.getLastSplitTarget()) {
497 MetricsLogger.action(mContext, MetricsEvent.ACTION_WINDOW_DOCK_RESIZE,
498 dockSideTopLeft(mDockSide)
499 ? LOG_VALUE_RESIZE_DOCKED_LARGER
500 : LOG_VALUE_RESIZE_DOCKED_SMALLER);
504 private void convertToScreenCoordinates(MotionEvent event) {
505 event.setLocation(event.getRawX(), event.getRawY());
508 private void fling(int position, float velocity, boolean avoidDismissStart,
509 boolean logMetrics) {
510 SnapTarget snapTarget = mSnapAlgorithm.calculateSnapTarget(position, velocity);
511 if (avoidDismissStart && snapTarget == mSnapAlgorithm.getDismissStartTarget()) {
512 snapTarget = mSnapAlgorithm.getFirstSplitTarget();
515 logResizeEvent(snapTarget);
517 ValueAnimator anim = getFlingAnimator(position, snapTarget, 0 /* endDelay */);
518 mFlingAnimationUtils.apply(anim, position, snapTarget.position, velocity);
522 private void flingTo(int position, SnapTarget target, long duration, long startDelay,
523 long endDelay, Interpolator interpolator) {
524 ValueAnimator anim = getFlingAnimator(position, target, endDelay);
525 anim.setDuration(duration);
526 anim.setStartDelay(startDelay);
527 anim.setInterpolator(interpolator);
531 private ValueAnimator getFlingAnimator(int position, final SnapTarget snapTarget,
532 final long endDelay) {
533 final boolean taskPositionSameAtEnd = snapTarget.flag == SnapTarget.FLAG_NONE;
534 ValueAnimator anim = ValueAnimator.ofInt(position, snapTarget.position);
535 anim.addUpdateListener(animation -> resizeStack((Integer) animation.getAnimatedValue(),
536 taskPositionSameAtEnd && animation.getAnimatedFraction() == 1f
538 : snapTarget.taskPosition, snapTarget));
539 Runnable endAction = () -> {
540 commitSnapFlags(snapTarget);
541 mWindowManagerProxy.setResizing(false);
542 mDockSide = WindowManager.DOCKED_INVALID;
543 mCurrentAnimator = null;
544 mEntranceAnimationRunning = false;
545 mExitAnimationRunning = false;
546 EventBus.getDefault().send(new StoppedDragingEvent());
548 anim.addListener(new AnimatorListenerAdapter() {
550 private boolean mCancelled;
553 public void onAnimationCancel(Animator animation) {
558 public void onAnimationEnd(Animator animation) {
559 if (endDelay == 0 || mCancelled) {
562 mHandler.postDelayed(endAction, endDelay);
566 mCurrentAnimator = anim;
570 private void cancelFlingAnimation() {
571 if (mCurrentAnimator != null) {
572 mCurrentAnimator.cancel();
576 private void commitSnapFlags(SnapTarget target) {
577 if (target.flag == SnapTarget.FLAG_NONE) {
580 boolean dismissOrMaximize;
581 if (target.flag == SnapTarget.FLAG_DISMISS_START) {
582 dismissOrMaximize = mDockSide == WindowManager.DOCKED_LEFT
583 || mDockSide == WindowManager.DOCKED_TOP;
585 dismissOrMaximize = mDockSide == WindowManager.DOCKED_RIGHT
586 || mDockSide == WindowManager.DOCKED_BOTTOM;
588 if (dismissOrMaximize) {
589 mWindowManagerProxy.dismissDockedStack();
591 mWindowManagerProxy.maximizeDockedStack();
593 mWindowManagerProxy.setResizeDimLayer(false, -1, 0f);
596 private void liftBackground() {
597 if (mBackgroundLifted) {
600 if (isHorizontalDivision()) {
601 mBackground.animate().scaleY(1.4f);
603 mBackground.animate().scaleX(1.4f);
605 mBackground.animate()
606 .setInterpolator(Interpolators.TOUCH_RESPONSE)
607 .setDuration(TOUCH_ANIMATION_DURATION)
608 .translationZ(mTouchElevation)
611 // Lift handle as well so it doesn't get behind the background, even though it doesn't
614 .setInterpolator(Interpolators.TOUCH_RESPONSE)
615 .setDuration(TOUCH_ANIMATION_DURATION)
616 .translationZ(mTouchElevation)
618 mBackgroundLifted = true;
621 private void releaseBackground() {
622 if (!mBackgroundLifted) {
625 mBackground.animate()
626 .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
627 .setDuration(TOUCH_RELEASE_ANIMATION_DURATION)
633 .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
634 .setDuration(TOUCH_RELEASE_ANIMATION_DURATION)
637 mBackgroundLifted = false;
641 public void setMinimizedDockStack(boolean minimized) {
643 mHandle.setAlpha(minimized ? 0f : 1f);
646 } else if (mDockSide == WindowManager.DOCKED_TOP) {
647 mBackground.setPivotY(0);
648 mBackground.setScaleY(MINIMIZE_DOCK_SCALE);
649 } else if (mDockSide == WindowManager.DOCKED_LEFT
650 || mDockSide == WindowManager.DOCKED_RIGHT) {
651 mBackground.setPivotX(mDockSide == WindowManager.DOCKED_LEFT
653 : mBackground.getWidth());
654 mBackground.setScaleX(MINIMIZE_DOCK_SCALE);
656 mMinimizedShadow.setAlpha(minimized ? 1f : 0f);
657 mDockedStackMinimized = minimized;
660 public void setMinimizedDockStack(boolean minimized, long animDuration) {
663 .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
664 .setDuration(animDuration)
665 .alpha(minimized ? 0f : 1f)
667 if (mDockSide == WindowManager.DOCKED_TOP) {
668 mBackground.setPivotY(0);
669 mBackground.animate()
670 .scaleY(minimized ? MINIMIZE_DOCK_SCALE : 1f);
671 } else if (mDockSide == WindowManager.DOCKED_LEFT
672 || mDockSide == WindowManager.DOCKED_RIGHT) {
673 mBackground.setPivotX(mDockSide == WindowManager.DOCKED_LEFT
675 : mBackground.getWidth());
676 mBackground.animate()
677 .scaleX(minimized ? MINIMIZE_DOCK_SCALE : 1f);
680 mBackground.animate().withEndAction(mResetBackgroundRunnable);
682 mMinimizedShadow.animate()
683 .alpha(minimized ? 1f : 0f)
684 .setInterpolator(Interpolators.ALPHA_IN)
685 .setDuration(animDuration)
687 mBackground.animate()
688 .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
689 .setDuration(animDuration)
691 mDockedStackMinimized = minimized;
694 public void setAdjustedForIme(boolean adjustedForIme) {
696 mHandle.setAlpha(adjustedForIme ? 0f : 1f);
697 if (!adjustedForIme) {
699 } else if (mDockSide == WindowManager.DOCKED_TOP) {
700 mBackground.setPivotY(0);
701 mBackground.setScaleY(ADJUSTED_FOR_IME_SCALE);
703 mAdjustedForIme = adjustedForIme;
706 public void setAdjustedForIme(boolean adjustedForIme, long animDuration) {
709 .setInterpolator(IME_ADJUST_INTERPOLATOR)
710 .setDuration(animDuration)
711 .alpha(adjustedForIme ? 0f : 1f)
713 if (mDockSide == WindowManager.DOCKED_TOP) {
714 mBackground.setPivotY(0);
715 mBackground.animate()
716 .scaleY(adjustedForIme ? ADJUSTED_FOR_IME_SCALE : 1f);
718 if (!adjustedForIme) {
719 mBackground.animate().withEndAction(mResetBackgroundRunnable);
721 mBackground.animate()
722 .setInterpolator(IME_ADJUST_INTERPOLATOR)
723 .setDuration(animDuration)
725 mAdjustedForIme = adjustedForIme;
728 private void resetBackground() {
729 mBackground.setPivotX(mBackground.getWidth() / 2);
730 mBackground.setPivotY(mBackground.getHeight() / 2);
731 mBackground.setScaleX(1f);
732 mBackground.setScaleY(1f);
733 mMinimizedShadow.setAlpha(0f);
737 protected void onConfigurationChanged(Configuration newConfig) {
738 super.onConfigurationChanged(newConfig);
743 public void notifyDockSideChanged(int newDockSide) {
744 mDockSide = newDockSide;
745 mMinimizedShadow.setDockSide(mDockSide);
749 private void updateDisplayInfo() {
750 final DisplayManager displayManager =
751 (DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE);
752 Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
753 final DisplayInfo info = new DisplayInfo();
754 display.getDisplayInfo(info);
755 mDisplayWidth = info.logicalWidth;
756 mDisplayHeight = info.logicalHeight;
757 mSnapAlgorithm = null;
758 initializeSnapAlgorithm();
761 private int calculatePosition(int touchX, int touchY) {
762 return isHorizontalDivision() ? calculateYPosition(touchY) : calculateXPosition(touchX);
765 public boolean isHorizontalDivision() {
766 return getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
769 private int calculateXPosition(int touchX) {
770 return mStartPosition + touchX - mStartX;
773 private int calculateYPosition(int touchY) {
774 return mStartPosition + touchY - mStartY;
777 private void alignTopLeft(Rect containingRect, Rect rect) {
778 int width = rect.width();
779 int height = rect.height();
780 rect.set(containingRect.left, containingRect.top,
781 containingRect.left + width, containingRect.top + height);
784 private void alignBottomRight(Rect containingRect, Rect rect) {
785 int width = rect.width();
786 int height = rect.height();
787 rect.set(containingRect.right - width, containingRect.bottom - height,
788 containingRect.right, containingRect.bottom);
791 public void calculateBoundsForPosition(int position, int dockSide, Rect outRect) {
792 DockedDividerUtils.calculateBoundsForPosition(position, dockSide, outRect, mDisplayWidth,
793 mDisplayHeight, mDividerSize);
796 public void resizeStack(int position, int taskPosition, SnapTarget taskSnapTarget) {
797 calculateBoundsForPosition(position, mDockSide, mDockedRect);
799 if (mDockedRect.equals(mLastResizeRect) && !mEntranceAnimationRunning) {
803 // Make sure shadows are updated
804 if (mBackground.getZ() > 0f) {
805 mBackground.invalidate();
808 mLastResizeRect.set(mDockedRect);
809 if (mEntranceAnimationRunning && taskPosition != TASK_POSITION_SAME) {
810 if (mCurrentAnimator != null) {
811 calculateBoundsForPosition(taskPosition, mDockSide, mDockedTaskRect);
813 calculateBoundsForPosition(isHorizontalDivision() ? mDisplayHeight : mDisplayWidth,
814 mDockSide, mDockedTaskRect);
816 calculateBoundsForPosition(taskPosition, DockedDividerUtils.invertDockSide(mDockSide),
818 mWindowManagerProxy.resizeDockedStack(mDockedRect, mDockedTaskRect, null,
819 mOtherTaskRect, null);
820 } else if (mExitAnimationRunning && taskPosition != TASK_POSITION_SAME) {
821 calculateBoundsForPosition(taskPosition,
822 mDockSide, mDockedTaskRect);
823 calculateBoundsForPosition(mExitStartPosition,
824 DockedDividerUtils.invertDockSide(mDockSide), mOtherTaskRect);
825 mOtherInsetRect.set(mOtherTaskRect);
826 applyExitAnimationParallax(mOtherTaskRect, position);
827 mWindowManagerProxy.resizeDockedStack(mDockedRect, mDockedTaskRect, null,
828 mOtherTaskRect, mOtherInsetRect);
829 } else if (taskPosition != TASK_POSITION_SAME) {
830 calculateBoundsForPosition(position, DockedDividerUtils.invertDockSide(mDockSide),
832 int dockSideInverted = DockedDividerUtils.invertDockSide(mDockSide);
833 int taskPositionDocked =
834 restrictDismissingTaskPosition(taskPosition, mDockSide, taskSnapTarget);
835 int taskPositionOther =
836 restrictDismissingTaskPosition(taskPosition, dockSideInverted, taskSnapTarget);
837 calculateBoundsForPosition(taskPositionDocked, mDockSide, mDockedTaskRect);
838 calculateBoundsForPosition(taskPositionOther, dockSideInverted, mOtherTaskRect);
839 mDisplayRect.set(0, 0, mDisplayWidth, mDisplayHeight);
840 alignTopLeft(mDockedRect, mDockedTaskRect);
841 alignTopLeft(mOtherRect, mOtherTaskRect);
842 mDockedInsetRect.set(mDockedTaskRect);
843 mOtherInsetRect.set(mOtherTaskRect);
844 if (dockSideTopLeft(mDockSide)) {
845 alignTopLeft(mDisplayRect, mDockedInsetRect);
846 alignBottomRight(mDisplayRect, mOtherInsetRect);
848 alignBottomRight(mDisplayRect, mDockedInsetRect);
849 alignTopLeft(mDisplayRect, mOtherInsetRect);
851 applyDismissingParallax(mDockedTaskRect, mDockSide, taskSnapTarget, position,
853 applyDismissingParallax(mOtherTaskRect, dockSideInverted, taskSnapTarget, position,
855 mWindowManagerProxy.resizeDockedStack(mDockedRect, mDockedTaskRect, mDockedInsetRect,
856 mOtherTaskRect, mOtherInsetRect);
858 mWindowManagerProxy.resizeDockedStack(mDockedRect, null, null, null, null);
860 SnapTarget closestDismissTarget = mSnapAlgorithm.getClosestDismissTarget(position);
861 float dimFraction = getDimFraction(position, closestDismissTarget);
862 mWindowManagerProxy.setResizeDimLayer(dimFraction != 0f,
863 getStackIdForDismissTarget(closestDismissTarget),
867 private void applyExitAnimationParallax(Rect taskRect, int position) {
868 if (mDockSide == WindowManager.DOCKED_TOP) {
869 taskRect.offset(0, (int) ((position - mExitStartPosition) * 0.25f));
870 } else if (mDockSide == WindowManager.DOCKED_LEFT) {
871 taskRect.offset((int) ((position - mExitStartPosition) * 0.25f), 0);
872 } else if (mDockSide == WindowManager.DOCKED_RIGHT) {
873 taskRect.offset((int) ((mExitStartPosition - position) * 0.25f), 0);
877 private float getDimFraction(int position, SnapTarget dismissTarget) {
878 if (mEntranceAnimationRunning) {
881 float fraction = mSnapAlgorithm.calculateDismissingFraction(position);
882 fraction = Math.max(0, Math.min(fraction, 1f));
883 fraction = DIM_INTERPOLATOR.getInterpolation(fraction);
884 if (hasInsetsAtDismissTarget(dismissTarget)) {
886 // Less darkening with system insets.
893 * @return true if and only if there are system insets at the location of the dismiss target
895 private boolean hasInsetsAtDismissTarget(SnapTarget dismissTarget) {
896 if (isHorizontalDivision()) {
897 if (dismissTarget == mSnapAlgorithm.getDismissStartTarget()) {
898 return mStableInsets.top != 0;
900 return mStableInsets.bottom != 0;
903 if (dismissTarget == mSnapAlgorithm.getDismissStartTarget()) {
904 return mStableInsets.left != 0;
906 return mStableInsets.right != 0;
912 * When the snap target is dismissing one side, make sure that the dismissing side doesn't get
915 private int restrictDismissingTaskPosition(int taskPosition, int dockSide,
916 SnapTarget snapTarget) {
917 if (snapTarget.flag == SnapTarget.FLAG_DISMISS_START && dockSideTopLeft(dockSide)) {
918 return Math.max(mSnapAlgorithm.getFirstSplitTarget().position, mStartPosition);
919 } else if (snapTarget.flag == SnapTarget.FLAG_DISMISS_END
920 && dockSideBottomRight(dockSide)) {
921 return Math.min(mSnapAlgorithm.getLastSplitTarget().position, mStartPosition);
928 * Applies a parallax to the task when dismissing.
930 private void applyDismissingParallax(Rect taskRect, int dockSide, SnapTarget snapTarget,
931 int position, int taskPosition) {
932 float fraction = Math.min(1, Math.max(0,
933 mSnapAlgorithm.calculateDismissingFraction(position)));
934 SnapTarget dismissTarget = null;
935 SnapTarget splitTarget = null;
937 if (position <= mSnapAlgorithm.getLastSplitTarget().position
938 && dockSideTopLeft(dockSide)) {
939 dismissTarget = mSnapAlgorithm.getDismissStartTarget();
940 splitTarget = mSnapAlgorithm.getFirstSplitTarget();
941 start = taskPosition;
942 } else if (position >= mSnapAlgorithm.getLastSplitTarget().position
943 && dockSideBottomRight(dockSide)) {
944 dismissTarget = mSnapAlgorithm.getDismissEndTarget();
945 splitTarget = mSnapAlgorithm.getLastSplitTarget();
946 start = splitTarget.position;
948 if (dismissTarget != null && fraction > 0f
949 && isDismissing(splitTarget, position, dockSide)) {
950 fraction = calculateParallaxDismissingFraction(fraction, dockSide);
951 int offsetPosition = (int) (start +
952 fraction * (dismissTarget.position - splitTarget.position));
953 int width = taskRect.width();
954 int height = taskRect.height();
956 case WindowManager.DOCKED_LEFT:
957 taskRect.left = offsetPosition - width;
958 taskRect.right = offsetPosition;
960 case WindowManager.DOCKED_RIGHT:
961 taskRect.left = offsetPosition + mDividerSize;
962 taskRect.right = offsetPosition + width + mDividerSize;
964 case WindowManager.DOCKED_TOP:
965 taskRect.top = offsetPosition - height;
966 taskRect.bottom = offsetPosition;
968 case WindowManager.DOCKED_BOTTOM:
969 taskRect.top = offsetPosition + mDividerSize;
970 taskRect.bottom = offsetPosition + height + mDividerSize;
977 * @return for a specified {@code fraction}, this returns an adjusted value that simulates a
978 * slowing down parallax effect
980 private static float calculateParallaxDismissingFraction(float fraction, int dockSide) {
981 float result = SLOWDOWN_INTERPOLATOR.getInterpolation(fraction) / 3.5f;
983 // Less parallax at the top, just because.
984 if (dockSide == WindowManager.DOCKED_TOP) {
990 private static boolean isDismissing(SnapTarget snapTarget, int position, int dockSide) {
991 if (dockSide == WindowManager.DOCKED_TOP || dockSide == WindowManager.DOCKED_LEFT) {
992 return position < snapTarget.position;
994 return position > snapTarget.position;
998 private int getStackIdForDismissTarget(SnapTarget dismissTarget) {
999 if ((dismissTarget.flag == SnapTarget.FLAG_DISMISS_START && dockSideTopLeft(mDockSide))
1000 || (dismissTarget.flag == SnapTarget.FLAG_DISMISS_END
1001 && dockSideBottomRight(mDockSide))) {
1002 return StackId.DOCKED_STACK_ID;
1004 return StackId.HOME_STACK_ID;
1009 * @return true if and only if {@code dockSide} is top or left
1011 private static boolean dockSideTopLeft(int dockSide) {
1012 return dockSide == WindowManager.DOCKED_TOP || dockSide == WindowManager.DOCKED_LEFT;
1016 * @return true if and only if {@code dockSide} is bottom or right
1018 private static boolean dockSideBottomRight(int dockSide) {
1019 return dockSide == WindowManager.DOCKED_BOTTOM || dockSide == WindowManager.DOCKED_RIGHT;
1023 public void onComputeInternalInsets(InternalInsetsInfo inoutInfo) {
1024 inoutInfo.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
1025 inoutInfo.touchableRegion.set(mHandle.getLeft(), mHandle.getTop(), mHandle.getRight(),
1026 mHandle.getBottom());
1027 inoutInfo.touchableRegion.op(mBackground.getLeft(), mBackground.getTop(),
1028 mBackground.getRight(), mBackground.getBottom(), Op.UNION);
1032 * Checks whether recents will grow when invoked. This happens in multi-window when recents is
1033 * very small. When invoking recents, we shrink the docked stack so recents has more space.
1035 * @return the position of the divider when recents grows, or
1036 * {@link #INVALID_RECENTS_GROW_TARGET} if recents won't grow
1038 public int growsRecents() {
1039 boolean result = mGrowRecents
1040 && mWindowManagerProxy.getDockSide() == WindowManager.DOCKED_TOP
1041 && getCurrentPosition() == getSnapAlgorithm().getLastSplitTarget().position;
1043 return getSnapAlgorithm().getMiddleTarget().position;
1045 return INVALID_RECENTS_GROW_TARGET;
1049 public final void onBusEvent(RecentsActivityStartingEvent recentsActivityStartingEvent) {
1050 if (mGrowRecents && getWindowManagerProxy().getDockSide() == WindowManager.DOCKED_TOP
1051 && getCurrentPosition() == getSnapAlgorithm().getLastSplitTarget().position) {
1052 mState.growAfterRecentsDrawn = true;
1053 startDragging(false /* animate */, false /* touching */);
1057 public final void onBusEvent(DockedTopTaskEvent event) {
1058 if (event.dragMode == NavigationBarGestureHelper.DRAG_MODE_NONE) {
1059 mState.growAfterRecentsDrawn = false;
1060 mState.animateAfterRecentsDrawn = true;
1061 startDragging(false /* animate */, false /* touching */);
1064 int position = DockedDividerUtils.calculatePositionForBounds(event.initialRect,
1065 mDockSide, mDividerSize);
1066 mEntranceAnimationRunning = true;
1068 // Insets might not have been fetched yet, so fetch manually if needed.
1069 if (mStableInsets.isEmpty()) {
1070 SystemServicesProxy.getInstance(mContext).getStableInsets(mStableInsets);
1071 mSnapAlgorithm = null;
1072 initializeSnapAlgorithm();
1075 resizeStack(position, mSnapAlgorithm.getMiddleTarget().position,
1076 mSnapAlgorithm.getMiddleTarget());
1079 public final void onBusEvent(RecentsDrawnEvent drawnEvent) {
1080 if (mState.animateAfterRecentsDrawn) {
1081 mState.animateAfterRecentsDrawn = false;
1084 mHandler.post(() -> {
1085 // Delay switching resizing mode because this might cause jank in recents animation
1086 // that's longer than this animation.
1087 stopDragging(getCurrentPosition(), mSnapAlgorithm.getMiddleTarget(),
1088 mLongPressEntraceAnimDuration, Interpolators.FAST_OUT_SLOW_IN,
1089 200 /* endDelay */);
1092 if (mState.growAfterRecentsDrawn) {
1093 mState.growAfterRecentsDrawn = false;
1095 EventBus.getDefault().send(new RecentsGrowingEvent());
1096 stopDragging(getCurrentPosition(), mSnapAlgorithm.getMiddleTarget(), 336,
1097 Interpolators.FAST_OUT_SLOW_IN);
1101 public final void onBusEvent(UndockingTaskEvent undockingTaskEvent) {
1102 int dockSide = mWindowManagerProxy.getDockSide();
1103 if (dockSide != WindowManager.DOCKED_INVALID && !mDockedStackMinimized) {
1104 startDragging(false /* animate */, false /* touching */);
1105 SnapTarget target = dockSideTopLeft(dockSide)
1106 ? mSnapAlgorithm.getDismissEndTarget()
1107 : mSnapAlgorithm.getDismissStartTarget();
1109 // Don't start immediately - give a little bit time to settle the drag resize change.
1110 mExitAnimationRunning = true;
1111 mExitStartPosition = getCurrentPosition();
1112 stopDragging(mExitStartPosition, target, 336 /* duration */, 100 /* startDelay */,
1113 0 /* endDelay */, Interpolators.FAST_OUT_SLOW_IN);