blob: bb34a878a0d8f6f7e42ec82dffc386fd7a66ee45 [file] [log] [blame]
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.stackdivider;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.view.PointerIcon.TYPE_HORIZONTAL_DOUBLE_ARROW;
import static android.view.PointerIcon.TYPE_VERTICAL_DOUBLE_ARROW;
import static android.view.WindowManager.DOCKED_LEFT;
import static android.view.WindowManager.DOCKED_RIGHT;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.graphics.Region.Op;
import android.hardware.display.DisplayManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.view.Choreographer;
import android.view.Display;
import android.view.DisplayInfo;
import android.view.MotionEvent;
import android.view.PointerIcon;
import android.view.VelocityTracker;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.ViewConfiguration;
import android.view.ViewTreeObserver.InternalInsetsInfo;
import android.view.ViewTreeObserver.OnComputeInternalInsetsListener;
import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
import android.widget.FrameLayout;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.policy.DividerSnapAlgorithm;
import com.android.internal.policy.DividerSnapAlgorithm.SnapTarget;
import com.android.internal.policy.DockedDividerUtils;
import com.android.internal.view.SurfaceFlingerVsyncChoreographer;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.shared.system.WindowManagerWrapper;
import com.android.systemui.statusbar.FlingAnimationUtils;
/**
* Docked stack divider.
*/
public class DividerView extends FrameLayout implements OnTouchListener,
OnComputeInternalInsetsListener {
public interface DividerCallbacks {
void onDraggingStart();
void onDraggingEnd();
void growRecents();
}
static final long TOUCH_ANIMATION_DURATION = 150;
static final long TOUCH_RELEASE_ANIMATION_DURATION = 200;
public static final int INVALID_RECENTS_GROW_TARGET = -1;
private static final int LOG_VALUE_RESIZE_50_50 = 0;
private static final int LOG_VALUE_RESIZE_DOCKED_SMALLER = 1;
private static final int LOG_VALUE_RESIZE_DOCKED_LARGER = 2;
private static final int LOG_VALUE_UNDOCK_MAX_DOCKED = 0;
private static final int LOG_VALUE_UNDOCK_MAX_OTHER = 1;
private static final int TASK_POSITION_SAME = Integer.MAX_VALUE;
/**
* How much the background gets scaled when we are in the minimized dock state.
*/
private static final float MINIMIZE_DOCK_SCALE = 0f;
private static final float ADJUSTED_FOR_IME_SCALE = 0.5f;
private static final PathInterpolator SLOWDOWN_INTERPOLATOR =
new PathInterpolator(0.5f, 1f, 0.5f, 1f);
private static final PathInterpolator DIM_INTERPOLATOR =
new PathInterpolator(.23f, .87f, .52f, -0.11f);
private static final Interpolator IME_ADJUST_INTERPOLATOR =
new PathInterpolator(0.2f, 0f, 0.1f, 1f);
private static final int MSG_RESIZE_STACK = 0;
private DividerHandleView mHandle;
private View mBackground;
private MinimizedDockShadow mMinimizedShadow;
private int mStartX;
private int mStartY;
private int mStartPosition;
private int mDockSide;
private final int[] mTempInt2 = new int[2];
private boolean mMoving;
private int mTouchSlop;
private boolean mBackgroundLifted;
private boolean mIsInMinimizeInteraction;
private SnapTarget mSnapTargetBeforeMinimized;
private int mDividerInsets;
private final Display mDefaultDisplay;
private int mDisplayWidth;
private int mDisplayHeight;
private int mDisplayRotation;
private int mDividerWindowWidth;
private int mDividerSize;
private int mTouchElevation;
private int mLongPressEntraceAnimDuration;
private final Rect mDockedRect = new Rect();
private final Rect mDockedTaskRect = new Rect();
private final Rect mOtherTaskRect = new Rect();
private final Rect mOtherRect = new Rect();
private final Rect mDockedInsetRect = new Rect();
private final Rect mOtherInsetRect = new Rect();
private final Rect mLastResizeRect = new Rect();
private final Rect mTmpRect = new Rect();
private final WindowManagerProxy mWindowManagerProxy = WindowManagerProxy.getInstance();
private DividerWindowManager mWindowManager;
private VelocityTracker mVelocityTracker;
private FlingAnimationUtils mFlingAnimationUtils;
private DividerSnapAlgorithm mSnapAlgorithm;
private DividerSnapAlgorithm mMinimizedSnapAlgorithm;
private DividerCallbacks mCallback;
private final Rect mStableInsets = new Rect();
private boolean mGrowRecents;
private ValueAnimator mCurrentAnimator;
private boolean mEntranceAnimationRunning;
private boolean mExitAnimationRunning;
private int mExitStartPosition;
private boolean mDockedStackMinimized;
private boolean mHomeStackResizable;
private boolean mAdjustedForIme;
private DividerState mState;
private final SurfaceFlingerVsyncChoreographer mSfChoreographer;
// The view is removed or in the process of been removed from the system.
private boolean mRemoved;
private final Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_RESIZE_STACK:
resizeStack(msg.arg1, msg.arg2, (SnapTarget) msg.obj);
break;
default:
super.handleMessage(msg);
}
}
};
private final AccessibilityDelegate mHandleDelegate = new AccessibilityDelegate() {
@Override
public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(host, info);
final DividerSnapAlgorithm snapAlgorithm = getSnapAlgorithm();
if (isHorizontalDivision()) {
info.addAction(new AccessibilityAction(R.id.action_move_tl_full,
mContext.getString(R.string.accessibility_action_divider_top_full)));
if (snapAlgorithm.isFirstSplitTargetAvailable()) {
info.addAction(new AccessibilityAction(R.id.action_move_tl_70,
mContext.getString(R.string.accessibility_action_divider_top_70)));
}
if (snapAlgorithm.showMiddleSplitTargetForAccessibility()) {
// Only show the middle target if there are more than 1 split target
info.addAction(new AccessibilityAction(R.id.action_move_tl_50,
mContext.getString(R.string.accessibility_action_divider_top_50)));
}
if (snapAlgorithm.isLastSplitTargetAvailable()) {
info.addAction(new AccessibilityAction(R.id.action_move_tl_30,
mContext.getString(R.string.accessibility_action_divider_top_30)));
}
info.addAction(new AccessibilityAction(R.id.action_move_rb_full,
mContext.getString(R.string.accessibility_action_divider_bottom_full)));
} else {
info.addAction(new AccessibilityAction(R.id.action_move_tl_full,
mContext.getString(R.string.accessibility_action_divider_left_full)));
if (snapAlgorithm.isFirstSplitTargetAvailable()) {
info.addAction(new AccessibilityAction(R.id.action_move_tl_70,
mContext.getString(R.string.accessibility_action_divider_left_70)));
}
if (snapAlgorithm.showMiddleSplitTargetForAccessibility()) {
// Only show the middle target if there are more than 1 split target
info.addAction(new AccessibilityAction(R.id.action_move_tl_50,
mContext.getString(R.string.accessibility_action_divider_left_50)));
}
if (snapAlgorithm.isLastSplitTargetAvailable()) {
info.addAction(new AccessibilityAction(R.id.action_move_tl_30,
mContext.getString(R.string.accessibility_action_divider_left_30)));
}
info.addAction(new AccessibilityAction(R.id.action_move_rb_full,
mContext.getString(R.string.accessibility_action_divider_right_full)));
}
}
@Override
public boolean performAccessibilityAction(View host, int action, Bundle args) {
int currentPosition = getCurrentPosition();
SnapTarget nextTarget = null;
if (action == R.id.action_move_tl_full) {
nextTarget = mSnapAlgorithm.getDismissEndTarget();
} else if (action == R.id.action_move_tl_70) {
nextTarget = mSnapAlgorithm.getLastSplitTarget();
} else if (action == R.id.action_move_tl_50) {
nextTarget = mSnapAlgorithm.getMiddleTarget();
} else if (action == R.id.action_move_tl_30) {
nextTarget = mSnapAlgorithm.getFirstSplitTarget();
} else if (action == R.id.action_move_rb_full) {
nextTarget = mSnapAlgorithm.getDismissStartTarget();
}
if (nextTarget != null) {
startDragging(true /* animate */, false /* touching */);
stopDragging(currentPosition, nextTarget, 250, Interpolators.FAST_OUT_SLOW_IN);
return true;
}
return super.performAccessibilityAction(host, action, args);
}
};
private final Runnable mResetBackgroundRunnable = new Runnable() {
@Override
public void run() {
resetBackground();
}
};
public DividerView(Context context) {
this(context, null);
}
public DividerView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public DividerView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public DividerView(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
mSfChoreographer = new SurfaceFlingerVsyncChoreographer(mHandler, context.getDisplay(),
Choreographer.getInstance());
final DisplayManager displayManager =
(DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE);
mDefaultDisplay = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mHandle = findViewById(R.id.docked_divider_handle);
mBackground = findViewById(R.id.docked_divider_background);
mMinimizedShadow = findViewById(R.id.minimized_dock_shadow);
mHandle.setOnTouchListener(this);
mDividerWindowWidth = getResources().getDimensionPixelSize(
com.android.internal.R.dimen.docked_stack_divider_thickness);
mDividerInsets = getResources().getDimensionPixelSize(
com.android.internal.R.dimen.docked_stack_divider_insets);
mDividerSize = mDividerWindowWidth - 2 * mDividerInsets;
mTouchElevation = getResources().getDimensionPixelSize(
R.dimen.docked_stack_divider_lift_elevation);
mLongPressEntraceAnimDuration = getResources().getInteger(
R.integer.long_press_dock_anim_duration);
mGrowRecents = getResources().getBoolean(R.bool.recents_grow_in_multiwindow);
mTouchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop();
mFlingAnimationUtils = new FlingAnimationUtils(getContext(), 0.3f);
updateDisplayInfo();
boolean landscape = getResources().getConfiguration().orientation
== Configuration.ORIENTATION_LANDSCAPE;
mHandle.setPointerIcon(PointerIcon.getSystemIcon(getContext(),
landscape ? TYPE_HORIZONTAL_DOUBLE_ARROW : TYPE_VERTICAL_DOUBLE_ARROW));
getViewTreeObserver().addOnComputeInternalInsetsListener(this);
mHandle.setAccessibilityDelegate(mHandleDelegate);
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
// Save the current target if not minimized once attached to window
if (mHomeStackResizable && mDockSide != WindowManager.DOCKED_INVALID
&& !mIsInMinimizeInteraction) {
saveSnapTargetBeforeMinimized(mSnapTargetBeforeMinimized);
}
}
void onDividerRemoved() {
mRemoved = true;
mCallback = null;
mHandler.removeMessages(MSG_RESIZE_STACK);
}
@Override
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
if (mStableInsets.left != insets.getStableInsetLeft()
|| mStableInsets.top != insets.getStableInsetTop()
|| mStableInsets.right != insets.getStableInsetRight()
|| mStableInsets.bottom != insets.getStableInsetBottom()) {
mStableInsets.set(insets.getStableInsetLeft(), insets.getStableInsetTop(),
insets.getStableInsetRight(), insets.getStableInsetBottom());
if (mSnapAlgorithm != null || mMinimizedSnapAlgorithm != null) {
mSnapAlgorithm = null;
mMinimizedSnapAlgorithm = null;
initializeSnapAlgorithm();
}
}
return super.onApplyWindowInsets(insets);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
int minimizeLeft = 0;
int minimizeTop = 0;
if (mDockSide == WindowManager.DOCKED_TOP) {
minimizeTop = mBackground.getTop();
} else if (mDockSide == WindowManager.DOCKED_LEFT) {
minimizeLeft = mBackground.getLeft();
} else if (mDockSide == WindowManager.DOCKED_RIGHT) {
minimizeLeft = mBackground.getRight() - mMinimizedShadow.getWidth();
}
mMinimizedShadow.layout(minimizeLeft, minimizeTop,
minimizeLeft + mMinimizedShadow.getMeasuredWidth(),
minimizeTop + mMinimizedShadow.getMeasuredHeight());
if (changed) {
mWindowManagerProxy.setTouchRegion(new Rect(mHandle.getLeft(), mHandle.getTop(),
mHandle.getRight(), mHandle.getBottom()));
}
}
public void injectDependencies(DividerWindowManager windowManager, DividerState dividerState,
DividerCallbacks callback) {
mWindowManager = windowManager;
mState = dividerState;
mCallback = callback;
// Set the previous position ratio before minimized state after attaching this divider
if (mStableInsets.isEmpty()) {
WindowManagerWrapper.getInstance().getStableInsets(mStableInsets);
}
if (mState.mRatioPositionBeforeMinimized == 0) {
// Set the middle target as the initial state
mSnapTargetBeforeMinimized = mSnapAlgorithm.getMiddleTarget();
} else {
repositionSnapTargetBeforeMinimized();
}
}
public WindowManagerProxy getWindowManagerProxy() {
return mWindowManagerProxy;
}
public Rect getNonMinimizedSplitScreenSecondaryBounds() {
calculateBoundsForPosition(mSnapTargetBeforeMinimized.position,
DockedDividerUtils.invertDockSide(mDockSide), mOtherTaskRect);
mOtherTaskRect.bottom -= mStableInsets.bottom;
switch (mDockSide) {
case WindowManager.DOCKED_LEFT:
mOtherTaskRect.top += mStableInsets.top;
mOtherTaskRect.right -= mStableInsets.right;
break;
case WindowManager.DOCKED_RIGHT:
mOtherTaskRect.top += mStableInsets.top;
mOtherTaskRect.left += mStableInsets.left;
break;
}
return mOtherTaskRect;
}
public boolean startDragging(boolean animate, boolean touching) {
cancelFlingAnimation();
if (touching) {
mHandle.setTouching(true, animate);
}
mDockSide = mWindowManagerProxy.getDockSide();
// Update snap algorithm if rotation has occurred
if (mDisplayRotation != mDefaultDisplay.getRotation()) {
updateDisplayInfo();
}
initializeSnapAlgorithm();
mWindowManagerProxy.setResizing(true);
if (touching) {
mWindowManager.setSlippery(false);
liftBackground();
}
if (mCallback != null) {
mCallback.onDraggingStart();
}
return mDockSide != WindowManager.DOCKED_INVALID;
}
public void stopDragging(int position, float velocity, boolean avoidDismissStart,
boolean logMetrics) {
mHandle.setTouching(false, true /* animate */);
fling(position, velocity, avoidDismissStart, logMetrics);
mWindowManager.setSlippery(true);
releaseBackground();
}
public void stopDragging(int position, SnapTarget target, long duration,
Interpolator interpolator) {
stopDragging(position, target, duration, 0 /* startDelay*/, 0 /* endDelay */, interpolator);
}
public void stopDragging(int position, SnapTarget target, long duration,
Interpolator interpolator, long endDelay) {
stopDragging(position, target, duration, 0 /* startDelay*/, endDelay, interpolator);
}
public void stopDragging(int position, SnapTarget target, long duration, long startDelay,
long endDelay, Interpolator interpolator) {
mHandle.setTouching(false, true /* animate */);
flingTo(position, target, duration, startDelay, endDelay, interpolator);
mWindowManager.setSlippery(true);
releaseBackground();
}
private void stopDragging() {
mHandle.setTouching(false, true /* animate */);
mWindowManager.setSlippery(true);
releaseBackground();
}
private void updateDockSide() {
mDockSide = mWindowManagerProxy.getDockSide();
mMinimizedShadow.setDockSide(mDockSide);
}
private void initializeSnapAlgorithm() {
if (mSnapAlgorithm == null) {
mSnapAlgorithm = new DividerSnapAlgorithm(getContext().getResources(), mDisplayWidth,
mDisplayHeight, mDividerSize, isHorizontalDivision(), mStableInsets, mDockSide);
if (mSnapTargetBeforeMinimized != null && mSnapTargetBeforeMinimized.isMiddleTarget) {
mSnapTargetBeforeMinimized = mSnapAlgorithm.getMiddleTarget();
}
}
if (mMinimizedSnapAlgorithm == null) {
mMinimizedSnapAlgorithm = new DividerSnapAlgorithm(getContext().getResources(),
mDisplayWidth, mDisplayHeight, mDividerSize, isHorizontalDivision(),
mStableInsets, mDockSide, mDockedStackMinimized && mHomeStackResizable);
}
}
public DividerSnapAlgorithm getSnapAlgorithm() {
initializeSnapAlgorithm();
return mDockedStackMinimized && mHomeStackResizable ? mMinimizedSnapAlgorithm :
mSnapAlgorithm;
}
public int getCurrentPosition() {
getLocationOnScreen(mTempInt2);
if (isHorizontalDivision()) {
return mTempInt2[1] + mDividerInsets;
} else {
return mTempInt2[0] + mDividerInsets;
}
}
@Override
public boolean onTouch(View v, MotionEvent event) {
convertToScreenCoordinates(event);
final int action = event.getAction() & MotionEvent.ACTION_MASK;
switch (action) {
case MotionEvent.ACTION_DOWN:
mVelocityTracker = VelocityTracker.obtain();
mVelocityTracker.addMovement(event);
mStartX = (int) event.getX();
mStartY = (int) event.getY();
boolean result = startDragging(true /* animate */, true /* touching */);
if (!result) {
// Weren't able to start dragging successfully, so cancel it again.
stopDragging();
}
mStartPosition = getCurrentPosition();
mMoving = false;
return result;
case MotionEvent.ACTION_MOVE:
mVelocityTracker.addMovement(event);
int x = (int) event.getX();
int y = (int) event.getY();
boolean exceededTouchSlop =
isHorizontalDivision() && Math.abs(y - mStartY) > mTouchSlop
|| (!isHorizontalDivision() && Math.abs(x - mStartX) > mTouchSlop);
if (!mMoving && exceededTouchSlop) {
mStartX = x;
mStartY = y;
mMoving = true;
}
if (mMoving && mDockSide != WindowManager.DOCKED_INVALID) {
SnapTarget snapTarget = getSnapAlgorithm().calculateSnapTarget(
mStartPosition, 0 /* velocity */, false /* hardDismiss */);
resizeStackDelayed(calculatePosition(x, y), mStartPosition, snapTarget);
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
mVelocityTracker.addMovement(event);
x = (int) event.getRawX();
y = (int) event.getRawY();
mVelocityTracker.computeCurrentVelocity(1000);
int position = calculatePosition(x, y);
stopDragging(position, isHorizontalDivision() ? mVelocityTracker.getYVelocity()
: mVelocityTracker.getXVelocity(), false /* avoidDismissStart */,
true /* log */);
mMoving = false;
break;
}
return true;
}
private void logResizeEvent(SnapTarget snapTarget) {
if (snapTarget == mSnapAlgorithm.getDismissStartTarget()) {
MetricsLogger.action(
mContext, MetricsEvent.ACTION_WINDOW_UNDOCK_MAX, dockSideTopLeft(mDockSide)
? LOG_VALUE_UNDOCK_MAX_OTHER
: LOG_VALUE_UNDOCK_MAX_DOCKED);
} else if (snapTarget == mSnapAlgorithm.getDismissEndTarget()) {
MetricsLogger.action(
mContext, MetricsEvent.ACTION_WINDOW_UNDOCK_MAX, dockSideBottomRight(mDockSide)
? LOG_VALUE_UNDOCK_MAX_OTHER
: LOG_VALUE_UNDOCK_MAX_DOCKED);
} else if (snapTarget == mSnapAlgorithm.getMiddleTarget()) {
MetricsLogger.action(mContext, MetricsEvent.ACTION_WINDOW_DOCK_RESIZE,
LOG_VALUE_RESIZE_50_50);
} else if (snapTarget == mSnapAlgorithm.getFirstSplitTarget()) {
MetricsLogger.action(mContext, MetricsEvent.ACTION_WINDOW_DOCK_RESIZE,
dockSideTopLeft(mDockSide)
? LOG_VALUE_RESIZE_DOCKED_SMALLER
: LOG_VALUE_RESIZE_DOCKED_LARGER);
} else if (snapTarget == mSnapAlgorithm.getLastSplitTarget()) {
MetricsLogger.action(mContext, MetricsEvent.ACTION_WINDOW_DOCK_RESIZE,
dockSideTopLeft(mDockSide)
? LOG_VALUE_RESIZE_DOCKED_LARGER
: LOG_VALUE_RESIZE_DOCKED_SMALLER);
}
}
private void convertToScreenCoordinates(MotionEvent event) {
event.setLocation(event.getRawX(), event.getRawY());
}
private void fling(int position, float velocity, boolean avoidDismissStart,
boolean logMetrics) {
DividerSnapAlgorithm currentSnapAlgorithm = getSnapAlgorithm();
SnapTarget snapTarget = currentSnapAlgorithm.calculateSnapTarget(position, velocity);
if (avoidDismissStart && snapTarget == currentSnapAlgorithm.getDismissStartTarget()) {
snapTarget = currentSnapAlgorithm.getFirstSplitTarget();
}
if (logMetrics) {
logResizeEvent(snapTarget);
}
ValueAnimator anim = getFlingAnimator(position, snapTarget, 0 /* endDelay */);
mFlingAnimationUtils.apply(anim, position, snapTarget.position, velocity);
anim.start();
}
private void flingTo(int position, SnapTarget target, long duration, long startDelay,
long endDelay, Interpolator interpolator) {
ValueAnimator anim = getFlingAnimator(position, target, endDelay);
anim.setDuration(duration);
anim.setStartDelay(startDelay);
anim.setInterpolator(interpolator);
anim.start();
}
private ValueAnimator getFlingAnimator(int position, final SnapTarget snapTarget,
final long endDelay) {
if (mCurrentAnimator != null) {
cancelFlingAnimation();
updateDockSide();
}
final boolean taskPositionSameAtEnd = snapTarget.flag == SnapTarget.FLAG_NONE;
ValueAnimator anim = ValueAnimator.ofInt(position, snapTarget.position);
anim.addUpdateListener(animation -> resizeStackDelayed((int) animation.getAnimatedValue(),
taskPositionSameAtEnd && animation.getAnimatedFraction() == 1f
? TASK_POSITION_SAME
: snapTarget.taskPosition,
snapTarget));
Runnable endAction = () -> {
commitSnapFlags(snapTarget);
mWindowManagerProxy.setResizing(false);
updateDockSide();
mCurrentAnimator = null;
mEntranceAnimationRunning = false;
mExitAnimationRunning = false;
if (mCallback != null) {
mCallback.onDraggingEnd();
}
// Record last snap target the divider moved to
if (mHomeStackResizable && !mIsInMinimizeInteraction) {
// The last snapTarget position can be negative when the last divider position was
// offscreen. In that case, save the middle (default) SnapTarget so calculating next
// position isn't negative.
final SnapTarget saveTarget;
if (snapTarget.position < 0) {
saveTarget = mSnapAlgorithm.getMiddleTarget();
} else {
saveTarget = snapTarget;
}
if (saveTarget.position != mSnapAlgorithm.getDismissEndTarget().position
&& saveTarget.position != mSnapAlgorithm.getDismissStartTarget().position) {
saveSnapTargetBeforeMinimized(saveTarget);
}
}
};
Runnable notCancelledEndAction = () -> {
// Reset minimized divider position after unminimized state animation finishes
if (!mDockedStackMinimized && mIsInMinimizeInteraction) {
mIsInMinimizeInteraction = false;
}
};
anim.addListener(new AnimatorListenerAdapter() {
private boolean mCancelled;
@Override
public void onAnimationCancel(Animator animation) {
mHandler.removeMessages(MSG_RESIZE_STACK);
mCancelled = true;
}
@Override
public void onAnimationEnd(Animator animation) {
long delay = 0;
if (endDelay != 0) {
delay = endDelay;
} else if (mCancelled) {
delay = 0;
} else if (mSfChoreographer.getSurfaceFlingerOffsetMs() > 0) {
delay = mSfChoreographer.getSurfaceFlingerOffsetMs();
}
if (delay == 0) {
if (!mCancelled) {
notCancelledEndAction.run();
}
endAction.run();
} else {
if (!mCancelled) {
mHandler.postDelayed(notCancelledEndAction, delay);
}
mHandler.postDelayed(endAction, delay);
}
}
});
mCurrentAnimator = anim;
return anim;
}
private void cancelFlingAnimation() {
if (mCurrentAnimator != null) {
mCurrentAnimator.cancel();
}
}
private void commitSnapFlags(SnapTarget target) {
if (target.flag == SnapTarget.FLAG_NONE) {
return;
}
boolean dismissOrMaximize;
if (target.flag == SnapTarget.FLAG_DISMISS_START) {
dismissOrMaximize = mDockSide == WindowManager.DOCKED_LEFT
|| mDockSide == WindowManager.DOCKED_TOP;
} else {
dismissOrMaximize = mDockSide == WindowManager.DOCKED_RIGHT
|| mDockSide == WindowManager.DOCKED_BOTTOM;
}
if (dismissOrMaximize) {
mWindowManagerProxy.dismissDockedStack();
} else {
mWindowManagerProxy.maximizeDockedStack();
}
mWindowManagerProxy.setResizeDimLayer(false, WINDOWING_MODE_UNDEFINED, 0f);
}
private void liftBackground() {
if (mBackgroundLifted) {
return;
}
if (isHorizontalDivision()) {
mBackground.animate().scaleY(1.4f);
} else {
mBackground.animate().scaleX(1.4f);
}
mBackground.animate()
.setInterpolator(Interpolators.TOUCH_RESPONSE)
.setDuration(TOUCH_ANIMATION_DURATION)
.translationZ(mTouchElevation)
.start();
// Lift handle as well so it doesn't get behind the background, even though it doesn't
// cast shadow.
mHandle.animate()
.setInterpolator(Interpolators.TOUCH_RESPONSE)
.setDuration(TOUCH_ANIMATION_DURATION)
.translationZ(mTouchElevation)
.start();
mBackgroundLifted = true;
}
private void releaseBackground() {
if (!mBackgroundLifted) {
return;
}
mBackground.animate()
.setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
.setDuration(TOUCH_RELEASE_ANIMATION_DURATION)
.translationZ(0)
.scaleX(1f)
.scaleY(1f)
.start();
mHandle.animate()
.setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
.setDuration(TOUCH_RELEASE_ANIMATION_DURATION)
.translationZ(0)
.start();
mBackgroundLifted = false;
}
public void setMinimizedDockStack(boolean minimized, boolean isHomeStackResizable) {
mHomeStackResizable = isHomeStackResizable;
updateDockSide();
if (!minimized) {
resetBackground();
} else if (!isHomeStackResizable) {
if (mDockSide == WindowManager.DOCKED_TOP) {
mBackground.setPivotY(0);
mBackground.setScaleY(MINIMIZE_DOCK_SCALE);
} else if (mDockSide == WindowManager.DOCKED_LEFT
|| mDockSide == WindowManager.DOCKED_RIGHT) {
mBackground.setPivotX(mDockSide == WindowManager.DOCKED_LEFT
? 0
: mBackground.getWidth());
mBackground.setScaleX(MINIMIZE_DOCK_SCALE);
}
}
mMinimizedShadow.setAlpha(minimized ? 1f : 0f);
if (!isHomeStackResizable) {
mHandle.setAlpha(minimized ? 0f : 1f);
mDockedStackMinimized = minimized;
} else if (mDockedStackMinimized != minimized) {
mDockedStackMinimized = minimized;
if (mDisplayRotation != mDefaultDisplay.getRotation()) {
// Splitscreen to minimize is about to starts after rotating landscape to seascape,
// update insets, display info and snap algorithm targets
WindowManagerWrapper.getInstance().getStableInsets(mStableInsets);
repositionSnapTargetBeforeMinimized();
updateDisplayInfo();
} else {
mMinimizedSnapAlgorithm = null;
initializeSnapAlgorithm();
}
if (mIsInMinimizeInteraction != minimized || mCurrentAnimator != null) {
cancelFlingAnimation();
if (minimized) {
// Relayout to recalculate the divider shadow when minimizing
requestLayout();
mIsInMinimizeInteraction = true;
resizeStack(mMinimizedSnapAlgorithm.getMiddleTarget());
} else {
resizeStack(mSnapTargetBeforeMinimized);
mIsInMinimizeInteraction = false;
}
}
}
}
public void setMinimizedDockStack(boolean minimized, long animDuration,
boolean isHomeStackResizable) {
mHomeStackResizable = isHomeStackResizable;
updateDockSide();
if (!isHomeStackResizable) {
mMinimizedShadow.animate()
.alpha(minimized ? 1f : 0f)
.setInterpolator(Interpolators.ALPHA_IN)
.setDuration(animDuration)
.start();
mHandle.animate()
.setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
.setDuration(animDuration)
.alpha(minimized ? 0f : 1f)
.start();
if (mDockSide == WindowManager.DOCKED_TOP) {
mBackground.setPivotY(0);
mBackground.animate()
.scaleY(minimized ? MINIMIZE_DOCK_SCALE : 1f);
} else if (mDockSide == WindowManager.DOCKED_LEFT
|| mDockSide == WindowManager.DOCKED_RIGHT) {
mBackground.setPivotX(mDockSide == WindowManager.DOCKED_LEFT
? 0
: mBackground.getWidth());
mBackground.animate()
.scaleX(minimized ? MINIMIZE_DOCK_SCALE : 1f);
}
mDockedStackMinimized = minimized;
} else if (mDockedStackMinimized != minimized) {
mIsInMinimizeInteraction = true;
mMinimizedSnapAlgorithm = null;
mDockedStackMinimized = minimized;
initializeSnapAlgorithm();
stopDragging(minimized
? mSnapTargetBeforeMinimized.position
: getCurrentPosition(),
minimized
? mMinimizedSnapAlgorithm.getMiddleTarget()
: mSnapTargetBeforeMinimized,
animDuration, Interpolators.FAST_OUT_SLOW_IN, 0);
setAdjustedForIme(false, animDuration);
}
if (!minimized) {
mBackground.animate().withEndAction(mResetBackgroundRunnable);
}
mBackground.animate()
.setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
.setDuration(animDuration)
.start();
}
public void setAdjustedForIme(boolean adjustedForIme) {
updateDockSide();
mHandle.setAlpha(adjustedForIme ? 0f : 1f);
if (!adjustedForIme) {
resetBackground();
} else if (mDockSide == WindowManager.DOCKED_TOP) {
mBackground.setPivotY(0);
mBackground.setScaleY(ADJUSTED_FOR_IME_SCALE);
}
mAdjustedForIme = adjustedForIme;
}
public void setAdjustedForIme(boolean adjustedForIme, long animDuration) {
updateDockSide();
mHandle.animate()
.setInterpolator(IME_ADJUST_INTERPOLATOR)
.setDuration(animDuration)
.alpha(adjustedForIme ? 0f : 1f)
.start();
if (mDockSide == WindowManager.DOCKED_TOP) {
mBackground.setPivotY(0);
mBackground.animate()
.scaleY(adjustedForIme ? ADJUSTED_FOR_IME_SCALE : 1f);
}
if (!adjustedForIme) {
mBackground.animate().withEndAction(mResetBackgroundRunnable);
}
mBackground.animate()
.setInterpolator(IME_ADJUST_INTERPOLATOR)
.setDuration(animDuration)
.start();
mAdjustedForIme = adjustedForIme;
}
private void saveSnapTargetBeforeMinimized(SnapTarget target) {
mSnapTargetBeforeMinimized = target;
mState.mRatioPositionBeforeMinimized = (float) target.position /
(isHorizontalDivision() ? mDisplayHeight : mDisplayWidth);
}
private void resetBackground() {
mBackground.setPivotX(mBackground.getWidth() / 2);
mBackground.setPivotY(mBackground.getHeight() / 2);
mBackground.setScaleX(1f);
mBackground.setScaleY(1f);
mMinimizedShadow.setAlpha(0f);
}
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
updateDisplayInfo();
}
public void notifyDockSideChanged(int newDockSide) {
int oldDockSide = mDockSide;
mDockSide = newDockSide;
mMinimizedShadow.setDockSide(mDockSide);
requestLayout();
// Update the snap position to the new docked side with correct insets
WindowManagerWrapper.getInstance().getStableInsets(mStableInsets);
mMinimizedSnapAlgorithm = null;
initializeSnapAlgorithm();
if (oldDockSide == DOCKED_LEFT && mDockSide == DOCKED_RIGHT
|| oldDockSide == DOCKED_RIGHT && mDockSide == DOCKED_LEFT) {
repositionSnapTargetBeforeMinimized();
}
// Landscape to seascape rotation requires minimized to resize docked app correctly
if (mHomeStackResizable && mDockedStackMinimized) {
resizeStack(mMinimizedSnapAlgorithm.getMiddleTarget());
}
}
private void repositionSnapTargetBeforeMinimized() {
int position = (int) (mState.mRatioPositionBeforeMinimized *
(isHorizontalDivision() ? mDisplayHeight : mDisplayWidth));
mSnapAlgorithm = null;
initializeSnapAlgorithm();
// Set the snap target before minimized but do not save until divider is attached and not
// minimized because it does not know its minimized state yet.
mSnapTargetBeforeMinimized = mSnapAlgorithm.calculateNonDismissingSnapTarget(position);
}
private void updateDisplayInfo() {
mDisplayRotation = mDefaultDisplay.getRotation();
final DisplayInfo info = new DisplayInfo();
mDefaultDisplay.getDisplayInfo(info);
mDisplayWidth = info.logicalWidth;
mDisplayHeight = info.logicalHeight;
mSnapAlgorithm = null;
mMinimizedSnapAlgorithm = null;
initializeSnapAlgorithm();
}
private int calculatePosition(int touchX, int touchY) {
return isHorizontalDivision() ? calculateYPosition(touchY) : calculateXPosition(touchX);
}
public boolean isHorizontalDivision() {
return getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
}
private int calculateXPosition(int touchX) {
return mStartPosition + touchX - mStartX;
}
private int calculateYPosition(int touchY) {
return mStartPosition + touchY - mStartY;
}
private void alignTopLeft(Rect containingRect, Rect rect) {
int width = rect.width();
int height = rect.height();
rect.set(containingRect.left, containingRect.top,
containingRect.left + width, containingRect.top + height);
}
private void alignBottomRight(Rect containingRect, Rect rect) {
int width = rect.width();
int height = rect.height();
rect.set(containingRect.right - width, containingRect.bottom - height,
containingRect.right, containingRect.bottom);
}
public void calculateBoundsForPosition(int position, int dockSide, Rect outRect) {
DockedDividerUtils.calculateBoundsForPosition(position, dockSide, outRect, mDisplayWidth,
mDisplayHeight, mDividerSize);
}
public void resizeStackDelayed(int position, int taskPosition, SnapTarget taskSnapTarget) {
Message message = mHandler.obtainMessage(MSG_RESIZE_STACK, position, taskPosition,
taskSnapTarget);
message.setAsynchronous(true);
mSfChoreographer.scheduleAtSfVsync(mHandler, message);
}
private void resizeStack(SnapTarget taskSnapTarget) {
resizeStack(taskSnapTarget.position, taskSnapTarget.position, taskSnapTarget);
}
public void resizeStack(int position, int taskPosition, SnapTarget taskSnapTarget) {
if (mRemoved) {
// This divider view has been removed so shouldn't have any additional influence.
return;
}
calculateBoundsForPosition(position, mDockSide, mDockedRect);
if (mDockedRect.equals(mLastResizeRect) && !mEntranceAnimationRunning) {
return;
}
// Make sure shadows are updated
if (mBackground.getZ() > 0f) {
mBackground.invalidate();
}
mLastResizeRect.set(mDockedRect);
if (mHomeStackResizable && mIsInMinimizeInteraction) {
calculateBoundsForPosition(mSnapTargetBeforeMinimized.position, mDockSide,
mDockedTaskRect);
calculateBoundsForPosition(mSnapTargetBeforeMinimized.position,
DockedDividerUtils.invertDockSide(mDockSide), mOtherTaskRect);
// Move a right-docked-app to line up with the divider while dragging it
if (mDockSide == DOCKED_RIGHT) {
mDockedTaskRect.offset(Math.max(position, mStableInsets.left - mDividerSize)
- mDockedTaskRect.left + mDividerSize, 0);
}
mWindowManagerProxy.resizeDockedStack(mDockedRect, mDockedTaskRect, mDockedTaskRect,
mOtherTaskRect, null);
return;
}
if (mEntranceAnimationRunning && taskPosition != TASK_POSITION_SAME) {
calculateBoundsForPosition(taskPosition, mDockSide, mDockedTaskRect);
// Move a docked app if from the right in position with the divider up to insets
if (mDockSide == DOCKED_RIGHT) {
mDockedTaskRect.offset(Math.max(position, mStableInsets.left - mDividerSize)
- mDockedTaskRect.left + mDividerSize, 0);
}
calculateBoundsForPosition(taskPosition, DockedDividerUtils.invertDockSide(mDockSide),
mOtherTaskRect);
mWindowManagerProxy.resizeDockedStack(mDockedRect, mDockedTaskRect, null,
mOtherTaskRect, null);
} else if (mExitAnimationRunning && taskPosition != TASK_POSITION_SAME) {
calculateBoundsForPosition(taskPosition, mDockSide, mDockedTaskRect);
mDockedInsetRect.set(mDockedTaskRect);
calculateBoundsForPosition(mExitStartPosition,
DockedDividerUtils.invertDockSide(mDockSide), mOtherTaskRect);
mOtherInsetRect.set(mOtherTaskRect);
applyExitAnimationParallax(mOtherTaskRect, position);
// Move a right-docked-app to line up with the divider while dragging it
if (mDockSide == DOCKED_RIGHT) {
mDockedTaskRect.offset(position - mStableInsets.left + mDividerSize, 0);
}
mWindowManagerProxy.resizeDockedStack(mDockedRect, mDockedTaskRect, mDockedInsetRect,
mOtherTaskRect, mOtherInsetRect);
} else if (taskPosition != TASK_POSITION_SAME) {
calculateBoundsForPosition(position, DockedDividerUtils.invertDockSide(mDockSide),
mOtherRect);
int dockSideInverted = DockedDividerUtils.invertDockSide(mDockSide);
int taskPositionDocked =
restrictDismissingTaskPosition(taskPosition, mDockSide, taskSnapTarget);
int taskPositionOther =
restrictDismissingTaskPosition(taskPosition, dockSideInverted, taskSnapTarget);
calculateBoundsForPosition(taskPositionDocked, mDockSide, mDockedTaskRect);
calculateBoundsForPosition(taskPositionOther, dockSideInverted, mOtherTaskRect);
mTmpRect.set(0, 0, mDisplayWidth, mDisplayHeight);
alignTopLeft(mDockedRect, mDockedTaskRect);
alignTopLeft(mOtherRect, mOtherTaskRect);
mDockedInsetRect.set(mDockedTaskRect);
mOtherInsetRect.set(mOtherTaskRect);
if (dockSideTopLeft(mDockSide)) {
alignTopLeft(mTmpRect, mDockedInsetRect);
alignBottomRight(mTmpRect, mOtherInsetRect);
} else {
alignBottomRight(mTmpRect, mDockedInsetRect);
alignTopLeft(mTmpRect, mOtherInsetRect);
}
applyDismissingParallax(mDockedTaskRect, mDockSide, taskSnapTarget, position,
taskPositionDocked);
applyDismissingParallax(mOtherTaskRect, dockSideInverted, taskSnapTarget, position,
taskPositionOther);
mWindowManagerProxy.resizeDockedStack(mDockedRect, mDockedTaskRect, mDockedInsetRect,
mOtherTaskRect, mOtherInsetRect);
} else {
mWindowManagerProxy.resizeDockedStack(mDockedRect, null, null, null, null);
}
SnapTarget closestDismissTarget = getSnapAlgorithm().getClosestDismissTarget(position);
float dimFraction = getDimFraction(position, closestDismissTarget);
mWindowManagerProxy.setResizeDimLayer(dimFraction != 0f,
getWindowingModeForDismissTarget(closestDismissTarget), dimFraction);
}
private void applyExitAnimationParallax(Rect taskRect, int position) {
if (mDockSide == WindowManager.DOCKED_TOP) {
taskRect.offset(0, (int) ((position - mExitStartPosition) * 0.25f));
} else if (mDockSide == WindowManager.DOCKED_LEFT) {
taskRect.offset((int) ((position - mExitStartPosition) * 0.25f), 0);
} else if (mDockSide == WindowManager.DOCKED_RIGHT) {
taskRect.offset((int) ((mExitStartPosition - position) * 0.25f), 0);
}
}
private float getDimFraction(int position, SnapTarget dismissTarget) {
if (mEntranceAnimationRunning) {
return 0f;
}
float fraction = getSnapAlgorithm().calculateDismissingFraction(position);
fraction = Math.max(0, Math.min(fraction, 1f));
fraction = DIM_INTERPOLATOR.getInterpolation(fraction);
if (hasInsetsAtDismissTarget(dismissTarget)) {
// Less darkening with system insets.
fraction *= 0.8f;
}
return fraction;
}
/**
* @return true if and only if there are system insets at the location of the dismiss target
*/
private boolean hasInsetsAtDismissTarget(SnapTarget dismissTarget) {
if (isHorizontalDivision()) {
if (dismissTarget == getSnapAlgorithm().getDismissStartTarget()) {
return mStableInsets.top != 0;
} else {
return mStableInsets.bottom != 0;
}
} else {
if (dismissTarget == getSnapAlgorithm().getDismissStartTarget()) {
return mStableInsets.left != 0;
} else {
return mStableInsets.right != 0;
}
}
}
/**
* When the snap target is dismissing one side, make sure that the dismissing side doesn't get
* 0 size.
*/
private int restrictDismissingTaskPosition(int taskPosition, int dockSide,
SnapTarget snapTarget) {
if (snapTarget.flag == SnapTarget.FLAG_DISMISS_START && dockSideTopLeft(dockSide)) {
return Math.max(mSnapAlgorithm.getFirstSplitTarget().position, mStartPosition);
} else if (snapTarget.flag == SnapTarget.FLAG_DISMISS_END
&& dockSideBottomRight(dockSide)) {
return Math.min(mSnapAlgorithm.getLastSplitTarget().position, mStartPosition);
} else {
return taskPosition;
}
}
/**
* Applies a parallax to the task when dismissing.
*/
private void applyDismissingParallax(Rect taskRect, int dockSide, SnapTarget snapTarget,
int position, int taskPosition) {
float fraction = Math.min(1, Math.max(0,
mSnapAlgorithm.calculateDismissingFraction(position)));
SnapTarget dismissTarget = null;
SnapTarget splitTarget = null;
int start = 0;
if (position <= mSnapAlgorithm.getLastSplitTarget().position
&& dockSideTopLeft(dockSide)) {
dismissTarget = mSnapAlgorithm.getDismissStartTarget();
splitTarget = mSnapAlgorithm.getFirstSplitTarget();
start = taskPosition;
} else if (position >= mSnapAlgorithm.getLastSplitTarget().position
&& dockSideBottomRight(dockSide)) {
dismissTarget = mSnapAlgorithm.getDismissEndTarget();
splitTarget = mSnapAlgorithm.getLastSplitTarget();
start = splitTarget.position;
}
if (dismissTarget != null && fraction > 0f
&& isDismissing(splitTarget, position, dockSide)) {
fraction = calculateParallaxDismissingFraction(fraction, dockSide);
int offsetPosition = (int) (start +
fraction * (dismissTarget.position - splitTarget.position));
int width = taskRect.width();
int height = taskRect.height();
switch (dockSide) {
case WindowManager.DOCKED_LEFT:
taskRect.left = offsetPosition - width;
taskRect.right = offsetPosition;
break;
case WindowManager.DOCKED_RIGHT:
taskRect.left = offsetPosition + mDividerSize;
taskRect.right = offsetPosition + width + mDividerSize;
break;
case WindowManager.DOCKED_TOP:
taskRect.top = offsetPosition - height;
taskRect.bottom = offsetPosition;
break;
case WindowManager.DOCKED_BOTTOM:
taskRect.top = offsetPosition + mDividerSize;
taskRect.bottom = offsetPosition + height + mDividerSize;
break;
}
}
}
/**
* @return for a specified {@code fraction}, this returns an adjusted value that simulates a
* slowing down parallax effect
*/
private static float calculateParallaxDismissingFraction(float fraction, int dockSide) {
float result = SLOWDOWN_INTERPOLATOR.getInterpolation(fraction) / 3.5f;
// Less parallax at the top, just because.
if (dockSide == WindowManager.DOCKED_TOP) {
result /= 2f;
}
return result;
}
private static boolean isDismissing(SnapTarget snapTarget, int position, int dockSide) {
if (dockSide == WindowManager.DOCKED_TOP || dockSide == WindowManager.DOCKED_LEFT) {
return position < snapTarget.position;
} else {
return position > snapTarget.position;
}
}
private int getWindowingModeForDismissTarget(SnapTarget dismissTarget) {
if ((dismissTarget.flag == SnapTarget.FLAG_DISMISS_START && dockSideTopLeft(mDockSide))
|| (dismissTarget.flag == SnapTarget.FLAG_DISMISS_END
&& dockSideBottomRight(mDockSide))) {
return WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
} else {
return WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
}
}
/**
* @return true if and only if {@code dockSide} is top or left
*/
private static boolean dockSideTopLeft(int dockSide) {
return dockSide == WindowManager.DOCKED_TOP || dockSide == WindowManager.DOCKED_LEFT;
}
/**
* @return true if and only if {@code dockSide} is bottom or right
*/
private static boolean dockSideBottomRight(int dockSide) {
return dockSide == WindowManager.DOCKED_BOTTOM || dockSide == WindowManager.DOCKED_RIGHT;
}
@Override
public void onComputeInternalInsets(InternalInsetsInfo inoutInfo) {
inoutInfo.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
inoutInfo.touchableRegion.set(mHandle.getLeft(), mHandle.getTop(), mHandle.getRight(),
mHandle.getBottom());
inoutInfo.touchableRegion.op(mBackground.getLeft(), mBackground.getTop(),
mBackground.getRight(), mBackground.getBottom(), Op.UNION);
}
/**
* Checks whether recents will grow when invoked. This happens in multi-window when recents is
* very small. When invoking recents, we shrink the docked stack so recents has more space.
*
* @return the position of the divider when recents grows, or
* {@link #INVALID_RECENTS_GROW_TARGET} if recents won't grow
*/
public int growsRecents() {
boolean result = mGrowRecents
&& mDockSide == WindowManager.DOCKED_TOP
&& getCurrentPosition() == getSnapAlgorithm().getLastSplitTarget().position;
if (result) {
return getSnapAlgorithm().getMiddleTarget().position;
} else {
return INVALID_RECENTS_GROW_TARGET;
}
}
void onRecentsActivityStarting() {
if (mGrowRecents && mDockSide == WindowManager.DOCKED_TOP
&& getSnapAlgorithm().getMiddleTarget() != getSnapAlgorithm().getLastSplitTarget()
&& getCurrentPosition() == getSnapAlgorithm().getLastSplitTarget().position) {
mState.growAfterRecentsDrawn = true;
startDragging(false /* animate */, false /* touching */);
}
}
void onDockedFirstAnimationFrame() {
saveSnapTargetBeforeMinimized(mSnapAlgorithm.getMiddleTarget());
}
void onDockedTopTask() {
mState.growAfterRecentsDrawn = false;
mState.animateAfterRecentsDrawn = true;
startDragging(false /* animate */, false /* touching */);
updateDockSide();
mEntranceAnimationRunning = true;
resizeStack(calculatePositionForInsetBounds(), mSnapAlgorithm.getMiddleTarget().position,
mSnapAlgorithm.getMiddleTarget());
}
void onRecentsDrawn() {
updateDockSide();
final int position = calculatePositionForInsetBounds();
if (mState.animateAfterRecentsDrawn) {
mState.animateAfterRecentsDrawn = false;
mHandler.post(() -> {
// Delay switching resizing mode because this might cause jank in recents animation
// that's longer than this animation.
stopDragging(position, getSnapAlgorithm().getMiddleTarget(),
mLongPressEntraceAnimDuration, Interpolators.FAST_OUT_SLOW_IN,
200 /* endDelay */);
});
}
if (mState.growAfterRecentsDrawn) {
mState.growAfterRecentsDrawn = false;
updateDockSide();
if (mCallback != null) {
mCallback.growRecents();
}
stopDragging(position, getSnapAlgorithm().getMiddleTarget(), 336,
Interpolators.FAST_OUT_SLOW_IN);
}
}
void onUndockingTask() {
int dockSide = mWindowManagerProxy.getDockSide();
if (dockSide != WindowManager.DOCKED_INVALID && (mHomeStackResizable
|| !mDockedStackMinimized)) {
startDragging(false /* animate */, false /* touching */);
SnapTarget target = dockSideTopLeft(dockSide)
? mSnapAlgorithm.getDismissEndTarget()
: mSnapAlgorithm.getDismissStartTarget();
// Don't start immediately - give a little bit time to settle the drag resize change.
mExitAnimationRunning = true;
mExitStartPosition = getCurrentPosition();
stopDragging(mExitStartPosition, target, 336 /* duration */, 100 /* startDelay */,
0 /* endDelay */, Interpolators.FAST_OUT_SLOW_IN);
}
}
private int calculatePositionForInsetBounds() {
mTmpRect.set(0, 0, mDisplayWidth, mDisplayHeight);
mTmpRect.inset(mStableInsets);
return DockedDividerUtils.calculatePositionForBounds(mTmpRect, mDockSide, mDividerSize);
}
}