blob: d17bba49b545a069ccee62703ced832f40788369 [file] [log] [blame]
/*
* Copyright (C) 2019 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.quickstep.inputconsumers;
import static android.view.MotionEvent.ACTION_CANCEL;
import static android.view.MotionEvent.ACTION_DOWN;
import static android.view.MotionEvent.ACTION_MOVE;
import static android.view.MotionEvent.ACTION_POINTER_DOWN;
import static android.view.MotionEvent.ACTION_POINTER_UP;
import static android.view.MotionEvent.ACTION_UP;
import static android.view.MotionEvent.INVALID_POINTER_ID;
import static com.android.quickstep.WindowTransformSwipeHandler.MAX_SWIPE_DURATION;
import static com.android.quickstep.WindowTransformSwipeHandler.MIN_PROGRESS_FOR_OVERVIEW;
import static com.android.quickstep.WindowTransformSwipeHandler.MIN_SWIPE_DURATION;
import static com.android.quickstep.inputconsumers.OtherActivityInputConsumer.QUICKSTEP_TOUCH_SLOP_RATIO;
import static com.android.quickstep.TouchInteractionService.startRecentsActivityAsync;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.app.ActivityManager.RunningTaskInfo;
import android.content.Context;
import android.content.Intent;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.ViewConfiguration;
import android.view.WindowManager;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.R;
import com.android.quickstep.ActivityControlHelper;
import com.android.quickstep.OverviewComponentObserver;
import com.android.quickstep.SwipeSharedState;
import com.android.quickstep.util.ClipAnimationHelper;
import com.android.quickstep.util.ClipAnimationHelper.TransformParams;
import com.android.quickstep.util.NavBarPosition;
import com.android.quickstep.util.RecentsAnimationListenerSet;
import com.android.quickstep.util.SwipeAnimationTargetSet;
import com.android.quickstep.util.SwipeAnimationTargetSet.SwipeAnimationListener;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.InputMonitorCompat;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
public class FallbackNoButtonInputConsumer implements InputConsumer, SwipeAnimationListener {
private static final int STATE_NOT_FINISHED = 0;
private static final int STATE_FINISHED_TO_HOME = 1;
private static final int STATE_FINISHED_TO_APP = 2;
private static final float PROGRESS_TO_END_GESTURE = -2;
private final ActivityControlHelper mActivityControlHelper;
private final InputMonitorCompat mInputMonitor;
private final Context mContext;
private final NavBarPosition mNavBarPosition;
private final SwipeSharedState mSwipeSharedState;
private final OverviewComponentObserver mOverviewComponentObserver;
private final int mRunningTaskId;
private final ClipAnimationHelper mClipAnimationHelper;
private final TransformParams mTransformParams = new TransformParams();
private final float mTransitionDragLength;
private final DeviceProfile mDP;
private final RectF mSwipeTouchRegion;
private final boolean mDisableHorizontalSwipe;
private final PointF mDownPos = new PointF();
private final PointF mLastPos = new PointF();
private int mActivePointerId = -1;
// Slop used to determine when we say that the gesture has started.
private boolean mPassedPilferInputSlop;
private VelocityTracker mVelocityTracker;
// Distance after which we start dragging the window.
private final float mTouchSlop;
// Might be displacement in X or Y, depending on the direction we are swiping from the nav bar.
private float mStartDisplacement;
private SwipeAnimationTargetSet mSwipeAnimationTargetSet;
private float mProgress;
private int mState = STATE_NOT_FINISHED;
public FallbackNoButtonInputConsumer(Context context,
ActivityControlHelper activityControlHelper, InputMonitorCompat inputMonitor,
SwipeSharedState swipeSharedState, RectF swipeTouchRegion,
OverviewComponentObserver overviewComponentObserver,
boolean disableHorizontalSwipe, RunningTaskInfo runningTaskInfo) {
mContext = context;
mActivityControlHelper = activityControlHelper;
mInputMonitor = inputMonitor;
mOverviewComponentObserver = overviewComponentObserver;
mRunningTaskId = runningTaskInfo.id;
mSwipeSharedState = swipeSharedState;
mSwipeTouchRegion = swipeTouchRegion;
mDisableHorizontalSwipe = disableHorizontalSwipe;
mNavBarPosition = new NavBarPosition(context);
mVelocityTracker = VelocityTracker.obtain();
mTouchSlop = QUICKSTEP_TOUCH_SLOP_RATIO
* ViewConfiguration.get(context).getScaledTouchSlop();
mClipAnimationHelper = new ClipAnimationHelper(context);
mDP = InvariantDeviceProfile.INSTANCE.get(context).getDeviceProfile(context).copy(context);
Rect tempRect = new Rect();
mTransitionDragLength = mActivityControlHelper.getSwipeUpDestinationAndLength(
mDP, context, tempRect);
mClipAnimationHelper.updateTargetRect(tempRect);
}
@Override
public int getType() {
return TYPE_FALLBACK_NO_BUTTON;
}
@Override
public void onMotionEvent(MotionEvent ev) {
if (mVelocityTracker == null) {
return;
}
mVelocityTracker.addMovement(ev);
if (ev.getActionMasked() == ACTION_POINTER_UP) {
mVelocityTracker.clear();
}
switch (ev.getActionMasked()) {
case ACTION_DOWN: {
mActivePointerId = ev.getPointerId(0);
mDownPos.set(ev.getX(), ev.getY());
mLastPos.set(mDownPos);
break;
}
case ACTION_POINTER_DOWN: {
if (!mPassedPilferInputSlop) {
// Cancel interaction in case of multi-touch interaction
int ptrIdx = ev.getActionIndex();
if (!mSwipeTouchRegion.contains(ev.getX(ptrIdx), ev.getY(ptrIdx))) {
forceCancelGesture(ev);
}
}
break;
}
case ACTION_POINTER_UP: {
int ptrIdx = ev.getActionIndex();
int ptrId = ev.getPointerId(ptrIdx);
if (ptrId == mActivePointerId) {
final int newPointerIdx = ptrIdx == 0 ? 1 : 0;
mDownPos.set(
ev.getX(newPointerIdx) - (mLastPos.x - mDownPos.x),
ev.getY(newPointerIdx) - (mLastPos.y - mDownPos.y));
mLastPos.set(ev.getX(newPointerIdx), ev.getY(newPointerIdx));
mActivePointerId = ev.getPointerId(newPointerIdx);
}
break;
}
case ACTION_MOVE: {
int pointerIndex = ev.findPointerIndex(mActivePointerId);
if (pointerIndex == INVALID_POINTER_ID) {
break;
}
mLastPos.set(ev.getX(pointerIndex), ev.getY(pointerIndex));
float displacement = getDisplacement(ev);
if (!mPassedPilferInputSlop) {
if (mDisableHorizontalSwipe && Math.abs(mLastPos.x - mDownPos.x)
> Math.abs(mLastPos.y - mDownPos.y)) {
// Horizontal gesture is not allowed in this region
forceCancelGesture(ev);
break;
}
if (Math.abs(displacement) >= mTouchSlop) {
mPassedPilferInputSlop = true;
// Deferred gesture, start the animation and gesture tracking once
// we pass the actual touch slop
startTouchTrackingForWindowAnimation(displacement);
}
} else {
updateDisplacement(displacement - mStartDisplacement);
}
break;
}
case ACTION_CANCEL:
case ACTION_UP: {
finishTouchTracking(ev);
break;
}
}
}
private void startTouchTrackingForWindowAnimation(float displacement) {
mStartDisplacement = Math.min(displacement, -mTouchSlop);
RecentsAnimationListenerSet listenerSet =
mSwipeSharedState.newRecentsAnimationListenerSet();
listenerSet.addListener(this);
Intent homeIntent = mOverviewComponentObserver.getHomeIntent();
startRecentsActivityAsync(homeIntent, listenerSet);
ActivityManagerWrapper.getInstance().closeSystemWindows(
CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
mInputMonitor.pilferPointers();
}
private void updateDisplacement(float displacement) {
mProgress = displacement / mTransitionDragLength;
mTransformParams.setProgress(mProgress);
if (mSwipeAnimationTargetSet != null) {
mClipAnimationHelper.applyTransform(mSwipeAnimationTargetSet, mTransformParams);
}
}
private void forceCancelGesture(MotionEvent ev) {
int action = ev.getAction();
ev.setAction(ACTION_CANCEL);
finishTouchTracking(ev);
ev.setAction(action);
}
/**
* Called when the gesture has ended. Does not correlate to the completion of the interaction as
* the animation can still be running.
*/
private void finishTouchTracking(MotionEvent ev) {
if (ev.getAction() == ACTION_CANCEL) {
mState = STATE_FINISHED_TO_APP;
} else {
mVelocityTracker.computeCurrentVelocity(1000,
ViewConfiguration.get(mContext).getScaledMaximumFlingVelocity());
float velocityX = mVelocityTracker.getXVelocity(mActivePointerId);
float velocityY = mVelocityTracker.getYVelocity(mActivePointerId);
float velocity = mNavBarPosition.isRightEdge() ? velocityX
: mNavBarPosition.isLeftEdge() ? -velocityX
: velocityY;
float flingThreshold = mContext.getResources()
.getDimension(R.dimen.quickstep_fling_threshold_velocity);
boolean isFling = Math.abs(velocity) > flingThreshold;
boolean goingHome;
if (!isFling) {
goingHome = -mProgress >= MIN_PROGRESS_FOR_OVERVIEW;
} else {
goingHome = velocity < 0;
}
if (goingHome) {
mState = STATE_FINISHED_TO_HOME;
} else {
mState = STATE_FINISHED_TO_APP;
}
}
if (mSwipeAnimationTargetSet != null) {
finishAnimationTargetSet();
}
}
private void finishAnimationTargetSet() {
if (mState == STATE_FINISHED_TO_APP) {
mSwipeAnimationTargetSet.finishController(false, null, false);
} else {
if (mProgress < PROGRESS_TO_END_GESTURE) {
mSwipeAnimationTargetSet.finishController(true, null, true);
} else {
long duration = (long) (Math.min(mProgress - PROGRESS_TO_END_GESTURE, 1)
* MAX_SWIPE_DURATION / Math.abs(PROGRESS_TO_END_GESTURE));
if (duration < 0) {
duration = MIN_SWIPE_DURATION;
}
ValueAnimator anim = ValueAnimator.ofFloat(mProgress, PROGRESS_TO_END_GESTURE);
anim.addUpdateListener(a -> {
float p = (Float) anim.getAnimatedValue();
mTransformParams.setProgress(p);
mClipAnimationHelper.applyTransform(mSwipeAnimationTargetSet, mTransformParams);
});
anim.setDuration(duration);
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mSwipeAnimationTargetSet.finishController(true, null, true);
}
});
anim.start();
}
}
}
@Override
public void onRecentsAnimationStart(SwipeAnimationTargetSet targetSet) {
mSwipeAnimationTargetSet = targetSet;
Rect overviewStackBounds = new Rect(0, 0, mDP.widthPx, mDP.heightPx);
RemoteAnimationTargetCompat runningTaskTarget = targetSet.findTask(mRunningTaskId);
mDP.updateIsSeascape(mContext.getSystemService(WindowManager.class));
if (runningTaskTarget != null) {
mClipAnimationHelper.updateSource(overviewStackBounds, runningTaskTarget);
}
mClipAnimationHelper.prepareAnimation(mDP, false /* isOpening */);
overviewStackBounds
.inset(-overviewStackBounds.width() / 5, -overviewStackBounds.height() / 5);
mClipAnimationHelper.updateTargetRect(overviewStackBounds);
mClipAnimationHelper.applyTransform(mSwipeAnimationTargetSet, mTransformParams);
if (mState != STATE_NOT_FINISHED) {
finishAnimationTargetSet();
}
}
@Override
public void onRecentsAnimationCanceled() { }
private float getDisplacement(MotionEvent ev) {
if (mNavBarPosition.isRightEdge()) {
return ev.getX() - mDownPos.x;
} else if (mNavBarPosition.isLeftEdge()) {
return mDownPos.x - ev.getX();
} else {
return ev.getY() - mDownPos.y;
}
}
@Override
public boolean allowInterceptByParent() {
return !mPassedPilferInputSlop;
}
}