Adding horizontal swipe support for 3p launcher

Bug: 137197916
Change-Id: I0fb5db791b3471d651db43f0e8c30b8d5baf9f27
This commit is contained in:
Sunny Goyal
2019-07-11 11:55:22 -07:00
parent d4b839145c
commit 2e4b665933
7 changed files with 460 additions and 198 deletions
@@ -18,26 +18,41 @@ package com.android.quickstep;
import static android.os.VibrationEffect.EFFECT_CLICK;
import static android.os.VibrationEffect.createPredefined;
import static com.android.launcher3.Utilities.postAsyncCallback;
import static com.android.launcher3.anim.Interpolators.DEACCEL;
import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
import static com.android.quickstep.TouchInteractionService.BACKGROUND_EXECUTOR;
import static com.android.quickstep.TouchInteractionService.MAIN_THREAD_EXECUTOR;
import static com.android.quickstep.TouchInteractionService.TOUCH_INTERACTION_LOG;
import android.annotation.TargetApi;
import android.app.ActivityManager.RunningTaskInfo;
import android.content.Context;
import android.content.Intent;
import android.graphics.PointF;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.provider.Settings;
import android.view.MotionEvent;
import android.view.animation.Interpolator;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.graphics.RotationMode;
import com.android.quickstep.ActivityControlHelper.ActivityInitListener;
import com.android.quickstep.inputconsumers.InputConsumer;
import com.android.quickstep.util.ClipAnimationHelper;
import com.android.quickstep.util.ClipAnimationHelper.TransformParams;
import com.android.quickstep.util.SwipeAnimationTargetSet.SwipeAnimationListener;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.system.InputConsumerController;
import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat;
import java.util.function.Consumer;
@@ -47,7 +62,10 @@ import androidx.annotation.UiThread;
* Base class for swipe up handler with some utility methods
*/
@TargetApi(Build.VERSION_CODES.Q)
public abstract class BaseSwipeUpHandler implements SwipeAnimationListener {
public abstract class BaseSwipeUpHandler<T extends BaseDraggingActivity>
implements SwipeAnimationListener {
private static final String TAG = "BaseSwipeUpHandler";
// Start resisting when swiping past this factor of mTransitionDragLength.
private static final float DRAG_LENGTH_FACTOR_START_PULLBACK = 1.4f;
@@ -61,6 +79,9 @@ public abstract class BaseSwipeUpHandler implements SwipeAnimationListener {
protected float mDragLengthFactor = 1;
protected final Context mContext;
protected final OverviewComponentObserver mOverviewComponentObserver;
protected final ActivityControlHelper<T> mActivityControlHelper;
protected final RecentsModel mRecentsModel;
protected final ClipAnimationHelper mClipAnimationHelper;
protected final TransformParams mTransformParams = new TransformParams();
@@ -73,18 +94,48 @@ public abstract class BaseSwipeUpHandler implements SwipeAnimationListener {
// visible.
protected final AnimatedFloat mCurrentShift = new AnimatedFloat(this::updateFinalShift);
protected final ActivityInitListener mActivityInitListener;
protected final RecentsAnimationWrapper mRecentsAnimationWrapper;
protected T mActivity;
protected RecentsView mRecentsView;
protected DeviceProfile mDp;
private final int mPageSpacing;
protected Runnable mGestureEndCallback;
protected BaseSwipeUpHandler(Context context) {
mContext = context;
mClipAnimationHelper = new ClipAnimationHelper(context);
protected final Handler mMainThreadHandler = MAIN_THREAD_EXECUTOR.getHandler();
protected MultiStateCallback mStateCallback;
protected boolean mCanceled;
protected int mFinishingRecentsAnimationForNewTaskId = -1;
protected BaseSwipeUpHandler(Context context,
OverviewComponentObserver overviewComponentObserver,
RecentsModel recentsModel, InputConsumerController inputConsumer) {
mContext = context;
mOverviewComponentObserver = overviewComponentObserver;
mActivityControlHelper = overviewComponentObserver.getActivityControlHelper();
mRecentsModel = recentsModel;
mActivityInitListener =
mActivityControlHelper.createActivityInitListener(this::onActivityInit);
mRecentsAnimationWrapper = new RecentsAnimationWrapper(inputConsumer,
this::createNewInputProxyHandler);
mClipAnimationHelper = new ClipAnimationHelper(context);
mPageSpacing = context.getResources().getDimensionPixelSize(R.dimen.recents_page_spacing);
mVibrator = context.getSystemService(Vibrator.class);
}
public void performHapticFeedback() {
protected void setStateOnUiThread(int stateFlag) {
if (Looper.myLooper() == mMainThreadHandler.getLooper()) {
mStateCallback.setState(stateFlag);
} else {
postAsyncCallback(mMainThreadHandler, () -> mStateCallback.setState(stateFlag));
}
}
protected void performHapticFeedback() {
if (!mVibrator.hasVibrator()) {
return;
}
@@ -130,12 +181,76 @@ public abstract class BaseSwipeUpHandler implements SwipeAnimationListener {
mGestureEndCallback = gestureEndCallback;
}
public abstract Intent getLaunchIntent();
protected void linkRecentsViewScroll() {
SyncRtSurfaceTransactionApplierCompat.create(mRecentsView, applier -> {
mTransformParams.setSyncTransactionApplier(applier);
mRecentsAnimationWrapper.runOnInit(() ->
mRecentsAnimationWrapper.targetSet.addDependentTransactionApplier(applier));
});
mRecentsView.setOnScrollChangeListener((v, scrollX, scrollY, oldScrollX, oldScrollY) -> {
if (moveWindowWithRecentsScroll()) {
updateFinalShift();
}
});
mRecentsView.setRecentsAnimationWrapper(mRecentsAnimationWrapper);
mRecentsView.setClipAnimationHelper(mClipAnimationHelper);
}
protected void startNewTask(int successStateFlag, Consumer<Boolean> resultCallback) {
// Launch the task user scrolled to (mRecentsView.getNextPage()).
if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
// We finish recents animation inside launchTask() when live tile is enabled.
mRecentsView.getTaskViewAt(mRecentsView.getNextPage()).launchTask(false /* animate */,
true /* freezeTaskList */);
} else {
int taskId = mRecentsView.getTaskViewAt(mRecentsView.getNextPage()).getTask().key.id;
mFinishingRecentsAnimationForNewTaskId = taskId;
mRecentsAnimationWrapper.finish(true /* toRecents */, () -> {
if (!mCanceled) {
TaskView nextTask = mRecentsView.getTaskView(taskId);
if (nextTask != null) {
nextTask.launchTask(false /* animate */, true /* freezeTaskList */,
success -> {
resultCallback.accept(success);
if (!success) {
mActivityControlHelper.onLaunchTaskFailed(mActivity);
nextTask.notifyTaskLaunchFailed(TAG);
} else {
mActivityControlHelper.onLaunchTaskSuccess(mActivity);
}
}, mMainThreadHandler);
}
setStateOnUiThread(successStateFlag);
}
mCanceled = false;
mFinishingRecentsAnimationForNewTaskId = -1;
});
}
TOUCH_INTERACTION_LOG.addLog("finishRecentsAnimation", true);
}
/**
* Return true if the window should be translated horizontally if the recents view scrolls
*/
protected abstract boolean moveWindowWithRecentsScroll();
protected abstract boolean onActivityInit(final T activity, Boolean alreadyOnHome);
/**
* Called to create a input proxy for the running task
*/
@UiThread
protected abstract InputConsumer createNewInputProxyHandler();
/**
* Called when the value of {@link #mCurrentShift} changes
*/
@UiThread
public abstract void updateFinalShift();
/**
* Called when motion pause is detected
*/
@@ -150,15 +265,39 @@ public abstract class BaseSwipeUpHandler implements SwipeAnimationListener {
@UiThread
public abstract void onGestureEnded(float endVelocity, PointF velocity, PointF downPos);
public void onConsumerAboutToBeSwitched(SwipeSharedState sharedState) { }
public abstract void onConsumerAboutToBeSwitched(SwipeSharedState sharedState);
public void setIsLikelyToStartNewTask(boolean isLikelyToStartNewTask) { }
public void initWhenReady() { }
public void initWhenReady() {
// Preload the plan
mRecentsModel.getTasks(null);
mActivityInitListener.register();
}
/**
* Applies the transform on the recents animation without any additional null checks
*/
protected void applyTransformUnchecked() {
float shift = mCurrentShift.value;
float offsetX = mRecentsView == null ? 0 : mRecentsView.getScrollOffset();
float offsetScale = getTaskCurveScaleForOffsetX(offsetX,
mClipAnimationHelper.getTargetRect().width());
mTransformParams.setProgress(shift).setOffsetX(offsetX).setOffsetScale(offsetScale);
mClipAnimationHelper.applyTransform(mRecentsAnimationWrapper.targetSet,
mTransformParams);
}
private float getTaskCurveScaleForOffsetX(float offsetX, float taskWidth) {
float distanceToReachEdge = mDp.widthPx / 2 + taskWidth / 2 + mPageSpacing;
float interpolation = Math.min(1, offsetX / distanceToReachEdge);
return TaskView.getCurveScaleForInterpolation(interpolation);
}
public interface Factory {
BaseSwipeUpHandler newHandler(RunningTaskInfo runningTask,
long touchTimeMs, boolean continuingLastGesture);
long touchTimeMs, boolean continuingLastGesture, boolean isLikelyToStartNewTask);
}
}
@@ -649,20 +649,17 @@ public class TouchInteractionService extends Service implements
final boolean shouldDefer;
final BaseSwipeUpHandler.Factory factory;
final Intent homeIntent;
if (mMode == Mode.NO_BUTTON && !mOverviewComponentObserver.isHomeAndOverviewSame()) {
shouldDefer = true;
shouldDefer = !sSwipeSharedState.recentsAnimationFinishInterrupted;
factory = mFallbackNoButtonFactory;
homeIntent = mOverviewComponentObserver.getHomeIntent();
} else {
shouldDefer = mOverviewComponentObserver.getActivityControlHelper()
.deferStartingActivity(mActiveNavBarRegion, event);
factory = mWindowTreansformFactory;
homeIntent = mOverviewComponentObserver.getOverviewIntent();
}
return new OtherActivityInputConsumer(this, runningTaskInfo, homeIntent,
return new OtherActivityInputConsumer(this, runningTaskInfo,
shouldDefer, mOverviewCallbacks, this::onConsumerInactive,
sSwipeSharedState, mInputMonitorCompat, mSwipeTouchRegion,
disableHorizontalSwipe(event), factory);
@@ -809,16 +806,15 @@ public class TouchInteractionService extends Service implements
}
private BaseSwipeUpHandler createWindowTransformSwipeHandler(RunningTaskInfo runningTask,
long touchTimeMs, boolean continuingLastGesture) {
return new WindowTransformSwipeHandler(
runningTask, this, touchTimeMs,
mOverviewComponentObserver.getActivityControlHelper(),
continuingLastGesture, mInputConsumer, mRecentsModel);
long touchTimeMs, boolean continuingLastGesture, boolean isLikelyToStartNewTask) {
return new WindowTransformSwipeHandler(runningTask, this, touchTimeMs,
mOverviewComponentObserver, continuingLastGesture, mInputConsumer, mRecentsModel);
}
private BaseSwipeUpHandler createFallbackNoButtonSwipeHandler(RunningTaskInfo runningTask,
long touchTimeMs, boolean continuingLastGesture) {
return new FallbackNoButtonInputConsumer(this, mOverviewComponentObserver, runningTask);
long touchTimeMs, boolean continuingLastGesture, boolean isLikelyToStartNewTask) {
return new FallbackNoButtonInputConsumer(this, mOverviewComponentObserver, runningTask,
mRecentsModel, mInputConsumer, isLikelyToStartNewTask, continuingLastGesture);
}
public static void startRecentsActivityAsync(Intent intent, RecentsAnimationListener listener) {
@@ -18,7 +18,6 @@ package com.android.quickstep;
import static com.android.launcher3.BaseActivity.INVISIBLE_BY_STATE_HANDLER;
import static com.android.launcher3.BaseActivity.STATE_HANDLER_INVISIBILITY_FLAGS;
import static com.android.launcher3.Utilities.SINGLE_FRAME_MS;
import static com.android.launcher3.Utilities.postAsyncCallback;
import static com.android.launcher3.anim.Interpolators.ACCEL_1_5;
import static com.android.launcher3.anim.Interpolators.DEACCEL;
import static com.android.launcher3.anim.Interpolators.LINEAR;
@@ -32,7 +31,6 @@ import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATI
import static com.android.quickstep.ActivityControlHelper.AnimationFactory.ShelfAnimState.HIDE;
import static com.android.quickstep.ActivityControlHelper.AnimationFactory.ShelfAnimState.PEEK;
import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
import static com.android.quickstep.TouchInteractionService.MAIN_THREAD_EXECUTOR;
import static com.android.quickstep.TouchInteractionService.TOUCH_INTERACTION_LOG;
import static com.android.quickstep.WindowTransformSwipeHandler.GestureEndTarget.HOME;
import static com.android.quickstep.WindowTransformSwipeHandler.GestureEndTarget.LAST_TASK;
@@ -48,14 +46,13 @@ import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.app.ActivityManager.RunningTaskInfo;
import android.content.Context;
import android.content.Intent;
import android.graphics.Canvas;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.os.SystemClock;
import android.util.Log;
import android.view.View;
@@ -82,7 +79,6 @@ import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.launcher3.util.RaceConditionTracker;
import com.android.launcher3.util.TraceHelper;
import com.android.launcher3.views.FloatingIconView;
import com.android.quickstep.ActivityControlHelper.ActivityInitListener;
import com.android.quickstep.ActivityControlHelper.AnimationFactory;
import com.android.quickstep.ActivityControlHelper.AnimationFactory.ShelfAnimState;
import com.android.quickstep.ActivityControlHelper.HomeAnimationFactory;
@@ -99,14 +95,14 @@ import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.InputConsumerController;
import com.android.systemui.shared.system.LatencyTrackerCompat;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat;
import com.android.systemui.shared.system.WindowCallbacksCompat;
import androidx.annotation.NonNull;
import androidx.annotation.UiThread;
@TargetApi(Build.VERSION_CODES.O)
public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> extends BaseSwipeUpHandler
public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
extends BaseSwipeUpHandler<T>
implements OnApplyWindowInsetsListener {
private static final String TAG = WindowTransformSwipeHandler.class.getSimpleName();
@@ -219,35 +215,24 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> extends
// Either RectFSpringAnim (if animating home) or ObjectAnimator (from mCurrentShift) otherwise
private RunningWindowAnim mRunningWindowAnim;
private boolean mIsShelfPeeking;
private DeviceProfile mDp;
private boolean mContinuingLastGesture;
// To avoid UI jump when gesture is started, we offset the animation by the threshold.
private float mShiftAtGestureStart = 0;
private final Handler mMainThreadHandler = MAIN_THREAD_EXECUTOR.getHandler();
private final ActivityControlHelper<T> mActivityControlHelper;
private final ActivityInitListener mActivityInitListener;
private final RecentsModel mRecentsModel;
private final SysUINavigationMode.Mode mMode;
private final Mode mMode;
private final int mRunningTaskId;
private ThumbnailData mTaskSnapshot;
private MultiStateCallback mStateCallback;
// Used to control launcher components throughout the swipe gesture.
private AnimatorPlaybackController mLauncherTransitionController;
private boolean mHasLauncherTransitionControllerStarted;
private T mActivity;
private AnimationFactory mAnimationFactory = (t) -> { };
private LiveTileOverlay mLiveTileOverlay = new LiveTileOverlay();
private boolean mCanceled;
private boolean mWasLauncherAlreadyVisible;
private int mFinishingRecentsAnimationForNewTaskId = -1;
private boolean mPassedOverviewThreshold;
private boolean mGestureStarted;
@@ -256,24 +241,17 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> extends
private PointF mDownPos;
private boolean mIsLikelyToStartNewTask;
private final RecentsAnimationWrapper mRecentsAnimationWrapper;
private final long mTouchTimeMs;
private long mLauncherFrameDrawnTime;
public WindowTransformSwipeHandler(RunningTaskInfo runningTaskInfo, Context context,
long touchTimeMs, ActivityControlHelper<T> controller, boolean continuingLastGesture,
long touchTimeMs, OverviewComponentObserver overviewComponentObserver,
boolean continuingLastGesture,
InputConsumerController inputConsumer, RecentsModel recentsModel) {
super(context);
super(context, overviewComponentObserver, recentsModel, inputConsumer);
mRunningTaskId = runningTaskInfo.id;
mTouchTimeMs = touchTimeMs;
mActivityControlHelper = controller;
mRecentsModel = recentsModel;
mActivityInitListener = mActivityControlHelper
.createActivityInitListener(this::onActivityInit);
mContinuingLastGesture = continuingLastGesture;
mRecentsAnimationWrapper = new RecentsAnimationWrapper(inputConsumer,
this::createNewInputProxyHandler);
mMode = SysUINavigationMode.getMode(context);
initStateCallbacks();
@@ -342,14 +320,6 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> extends
}
}
private void setStateOnUiThread(int stateFlag) {
if (Looper.myLooper() == mMainThreadHandler.getLooper()) {
mStateCallback.setState(stateFlag);
} else {
postAsyncCallback(mMainThreadHandler, () -> mStateCallback.setState(stateFlag));
}
}
private Rect getStackBounds(DeviceProfile dp) {
if (mActivity != null) {
int loc[] = new int[2];
@@ -383,14 +353,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> extends
}
@Override
public void initWhenReady() {
// Preload the plan
mRecentsModel.getTasks(null);
mActivityInitListener.register();
}
private boolean onActivityInit(final T activity, Boolean alreadyOnHome) {
protected boolean onActivityInit(final T activity, Boolean alreadyOnHome) {
if (mActivity == activity) {
return true;
}
@@ -411,19 +374,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> extends
}
mRecentsView = activity.getOverviewPanel();
SyncRtSurfaceTransactionApplierCompat.create(mRecentsView, applier -> {
mTransformParams.setSyncTransactionApplier(applier);
mRecentsAnimationWrapper.runOnInit(() ->
mRecentsAnimationWrapper.targetSet.addDependentTransactionApplier(applier));
});
mRecentsView.setOnScrollChangeListener((v, scrollX, scrollY, oldScrollX, oldScrollY) -> {
if (mGestureEndTarget != HOME) {
updateFinalShift();
}
});
mRecentsView.setRecentsAnimationWrapper(mRecentsAnimationWrapper);
mRecentsView.setClipAnimationHelper(mClipAnimationHelper);
linkRecentsViewScroll();
mRecentsView.setLiveTileOverlay(mLiveTileOverlay);
mActivity.getRootView().getOverlay().add(mLiveTileOverlay);
@@ -438,6 +389,11 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> extends
return true;
}
@Override
protected boolean moveWindowWithRecentsScroll() {
return mGestureEndTarget != HOME;
}
private void onLauncherStart(final T activity) {
if (TestProtocol.sDebugTracing) {
Log.d(TestProtocol.NO_OVERVIEW_EVENT_TAG, "onLauncherStart");
@@ -654,20 +610,18 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> extends
updateLauncherTransitionProgress();
}
@UiThread
@Override
public Intent getLaunchIntent() {
return mOverviewComponentObserver.getOverviewIntent();
}
@Override
public void updateFinalShift() {
float shift = mCurrentShift.value;
SwipeAnimationTargetSet controller = mRecentsAnimationWrapper.getController();
if (controller != null) {
float offsetX = mRecentsView == null ? 0 : mRecentsView.getScrollOffset();
float offsetScale = getTaskCurveScaleForOffsetX(offsetX,
mClipAnimationHelper.getTargetRect().width());
mTransformParams.setProgress(shift).setOffsetX(offsetX).setOffsetScale(offsetScale);
mClipAnimationHelper.applyTransform(mRecentsAnimationWrapper.targetSet,
mTransformParams);
updateSysUiFlags(shift);
applyTransformUnchecked();
updateSysUiFlags(mCurrentShift.value);
}
if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
@@ -817,8 +771,8 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> extends
handleNormalGestureEnd(endVelocity, isFling, velocity, false /* isCancel */);
}
@UiThread
private InputConsumer createNewInputProxyHandler() {
@Override
protected InputConsumer createNewInputProxyHandler() {
endRunningWindowAnim(true /* cancel */);
endLauncherTransitionController();
if (!ENABLE_QUICKSTEP_LIVE_TILE.get()) {
@@ -1208,40 +1162,15 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> extends
@UiThread
private void startNewTask() {
// Launch the task user scrolled to (mRecentsView.getNextPage()).
if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
// We finish recents animation inside launchTask() when live tile is enabled.
mRecentsView.getTaskViewAt(mRecentsView.getNextPage()).launchTask(false /* animate */,
true /* freezeTaskList */);
} else {
int taskId = mRecentsView.getTaskViewAt(mRecentsView.getNextPage()).getTask().key.id;
mFinishingRecentsAnimationForNewTaskId = taskId;
mRecentsAnimationWrapper.finish(true /* toRecents */, () -> {
if (!mCanceled) {
TaskView nextTask = mRecentsView.getTaskView(taskId);
if (nextTask != null) {
nextTask.launchTask(false /* animate */, true /* freezeTaskList */,
success -> {
if (!success) {
// We couldn't launch the task, so take user to overview so they can
// decide what to do instead of staying in this broken state.
endLauncherTransitionController();
mActivityControlHelper.onLaunchTaskFailed(mActivity);
nextTask.notifyTaskLaunchFailed(TAG);
updateSysUiFlags(1 /* windowProgress == overview */);
} else {
mActivityControlHelper.onLaunchTaskSuccess(mActivity);
}
}, mMainThreadHandler);
doLogGesture(NEW_TASK);
}
reset();
}
mCanceled = false;
mFinishingRecentsAnimationForNewTaskId = -1;
});
}
TOUCH_INTERACTION_LOG.addLog("finishRecentsAnimation", true);
startNewTask(STATE_HANDLER_INVALIDATED, success -> {
if (!success) {
// We couldn't launch the task, so take user to overview so they can
// decide what to do instead of staying in this broken state.
endLauncherTransitionController();
updateSysUiFlags(1 /* windowProgress == overview */);
}
doLogGesture(NEW_TASK);
});
}
private void reset() {
@@ -87,6 +87,12 @@ public class FallbackRecentsView extends RecentsView<RecentsActivity> {
super.draw(canvas);
}
@Override
public void reset() {
super.reset();
resetViewUI();
}
@Override
protected void getTaskSize(DeviceProfile dp, Rect outRect) {
LayoutUtils.calculateFallbackTaskSize(getContext(), dp, outRect);
@@ -114,6 +120,12 @@ public class FallbackRecentsView extends RecentsView<RecentsActivity> {
}
}
@Override
public void resetTaskVisuals() {
super.resetTaskVisuals();
setFullscreenProgress(mFullscreenProgress);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
@@ -15,15 +15,16 @@
*/
package com.android.quickstep.inputconsumers;
import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
import static com.android.quickstep.RecentsActivity.EXTRA_TASK_ID;
import static com.android.quickstep.RecentsActivity.EXTRA_THUMBNAIL;
import static com.android.quickstep.inputconsumers.FallbackNoButtonInputConsumer.GestureEndTarget.NEW_TASK;
import static com.android.quickstep.WindowTransformSwipeHandler.MIN_PROGRESS_FOR_OVERVIEW;
import static com.android.quickstep.inputconsumers.FallbackNoButtonInputConsumer.GestureEndTarget.HOME;
import static com.android.quickstep.inputconsumers.FallbackNoButtonInputConsumer.GestureEndTarget.LAST_TASK;
import static com.android.quickstep.inputconsumers.FallbackNoButtonInputConsumer.GestureEndTarget.RECENTS;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.ActivityOptions;
@@ -34,24 +35,53 @@ import android.graphics.Rect;
import android.os.Bundle;
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.launcher3.anim.AnimationSuccessListener;
import com.android.quickstep.AnimatedFloat;
import com.android.quickstep.BaseSwipeUpHandler;
import com.android.quickstep.MultiStateCallback;
import com.android.quickstep.OverviewComponentObserver;
import com.android.quickstep.RecentsActivity;
import com.android.quickstep.RecentsModel;
import com.android.quickstep.SwipeSharedState;
import com.android.quickstep.fallback.FallbackRecentsView;
import com.android.quickstep.util.ObjectWrapper;
import com.android.quickstep.util.SwipeAnimationTargetSet;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.ActivityOptionsCompat;
import com.android.systemui.shared.system.InputConsumerController;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
public class FallbackNoButtonInputConsumer extends BaseSwipeUpHandler {
public class FallbackNoButtonInputConsumer extends BaseSwipeUpHandler<RecentsActivity> {
private static final String[] STATE_NAMES = DEBUG_STATES ? new String[5] : null;
private static int getFlagForIndex(int index, String name) {
if (DEBUG_STATES) {
STATE_NAMES[index] = name;
}
return 1 << index;
}
private static final int STATE_RECENTS_PRESENT =
getFlagForIndex(0, "STATE_RECENTS_PRESENT");
private static final int STATE_HANDLER_INVALIDATED =
getFlagForIndex(1, "STATE_HANDLER_INVALIDATED");
private static final int STATE_GESTURE_CANCELLED =
getFlagForIndex(2, "STATE_GESTURE_CANCELLED");
private static final int STATE_GESTURE_COMPLETED =
getFlagForIndex(3, "STATE_GESTURE_COMPLETED");
private static final int STATE_APP_CONTROLLER_RECEIVED =
getFlagForIndex(4, "STATE_APP_CONTROLLER_RECEIVED");
public enum GestureEndTarget {
HOME(3, 100, 1),
RECENTS(1, 300, 0),
LAST_TASK(0, 150, 1);
LAST_TASK(0, 150, 1),
NEW_TASK(0, 150, 1);
private final float mEndProgress;
private final long mDurationMultiplier;
@@ -64,53 +94,131 @@ public class FallbackNoButtonInputConsumer extends BaseSwipeUpHandler {
}
}
private final ActivityControlHelper mActivityControlHelper;
private final OverviewComponentObserver mOverviewComponentObserver;
private final int mRunningTaskId;
private final DeviceProfile mDP;
private final Rect mTargetRect = new Rect();
private final AnimatedFloat mLauncherAlpha = new AnimatedFloat(this::onLauncherAlphaChanged);
private SwipeAnimationTargetSet mSwipeAnimationTargetSet;
private boolean mIsMotionPaused = false;
private GestureEndTarget mEndTarget;
private final boolean mInQuickSwitchMode;
private final boolean mContinuingLastGesture;
private Animator mFinishAnimation;
public FallbackNoButtonInputConsumer(Context context,
OverviewComponentObserver overviewComponentObserver,
RunningTaskInfo runningTaskInfo) {
super(context);
mOverviewComponentObserver = overviewComponentObserver;
mActivityControlHelper = overviewComponentObserver.getActivityControlHelper();
RunningTaskInfo runningTaskInfo, RecentsModel recentsModel,
InputConsumerController inputConsumer,
boolean isLikelyToStartNewTask, boolean continuingLastGesture) {
super(context, overviewComponentObserver, recentsModel, inputConsumer);
mRunningTaskId = runningTaskInfo.id;
mDP = InvariantDeviceProfile.INSTANCE.get(context).getDeviceProfile(context).copy(context);
mDp = InvariantDeviceProfile.INSTANCE.get(context).getDeviceProfile(context).copy(context);
mLauncherAlpha.value = 1;
mInQuickSwitchMode = isLikelyToStartNewTask || continuingLastGesture;
mContinuingLastGesture = continuingLastGesture;
mClipAnimationHelper.setBaseAlphaCallback((t, a) -> mLauncherAlpha.value);
initStateCallbacks();
initTransitionTarget();
}
private void initStateCallbacks() {
mStateCallback = new MultiStateCallback(STATE_NAMES);
mStateCallback.addCallback(STATE_HANDLER_INVALIDATED,
this::onHandlerInvalidated);
mStateCallback.addCallback(STATE_RECENTS_PRESENT | STATE_HANDLER_INVALIDATED,
this::onHandlerInvalidatedWithRecents);
mStateCallback.addCallback(STATE_GESTURE_CANCELLED | STATE_APP_CONTROLLER_RECEIVED,
this::finishAnimationTargetSetAnimationComplete);
if (mInQuickSwitchMode) {
mStateCallback.addCallback(STATE_GESTURE_COMPLETED | STATE_APP_CONTROLLER_RECEIVED
| STATE_RECENTS_PRESENT,
this::finishAnimationTargetSet);
} else {
mStateCallback.addCallback(STATE_GESTURE_COMPLETED | STATE_APP_CONTROLLER_RECEIVED,
this::finishAnimationTargetSet);
}
}
private void onLauncherAlphaChanged() {
if (mSwipeAnimationTargetSet != null && mEndTarget == null) {
mClipAnimationHelper.applyTransform(mSwipeAnimationTargetSet, mTransformParams);
if (mRecentsAnimationWrapper.targetSet != null && mEndTarget == null) {
applyTransformUnchecked();
}
}
@Override
protected boolean onActivityInit(final RecentsActivity activity, Boolean alreadyOnHome) {
mActivity = activity;
mRecentsView = activity.getOverviewPanel();
linkRecentsViewScroll();
mRecentsView.setDisallowScrollToClearAll(true);
mRecentsView.getClearAllButton().setVisibilityAlpha(0);
((FallbackRecentsView) mRecentsView).setZoomProgress(1);
if (!mContinuingLastGesture) {
mRecentsView.onGestureAnimationStart(mRunningTaskId);
}
setStateOnUiThread(STATE_RECENTS_PRESENT);
return true;
}
@Override
protected boolean moveWindowWithRecentsScroll() {
return mInQuickSwitchMode;
}
@Override
public void initWhenReady() {
if (mInQuickSwitchMode) {
// Only init if we are in quickswitch mode
super.initWhenReady();
}
}
@Override
public void updateDisplacement(float displacement) {
if (!mInQuickSwitchMode) {
super.updateDisplacement(displacement);
}
}
@Override
protected InputConsumer createNewInputProxyHandler() {
// Just consume all input on the active task
return InputConsumer.NO_OP;
}
@Override
public void onMotionPauseChanged(boolean isPaused) {
mIsMotionPaused = isPaused;
mLauncherAlpha.animateToValue(mLauncherAlpha.value, isPaused ? 0 : 1)
.setDuration(150).start();
performHapticFeedback();
if (!mInQuickSwitchMode) {
mIsMotionPaused = isPaused;
mLauncherAlpha.animateToValue(mLauncherAlpha.value, isPaused ? 0 : 1)
.setDuration(150).start();
performHapticFeedback();
}
}
@Override
public Intent getLaunchIntent() {
if (mInQuickSwitchMode) {
return mOverviewComponentObserver.getOverviewIntent();
} else {
return mOverviewComponentObserver.getHomeIntent();
}
}
@Override
public void updateFinalShift() {
mTransformParams.setProgress(mCurrentShift.value);
if (mSwipeAnimationTargetSet != null) {
mClipAnimationHelper.applyTransform(mSwipeAnimationTargetSet, mTransformParams);
if (mRecentsAnimationWrapper.targetSet != null) {
applyTransformUnchecked();
}
}
@@ -118,41 +226,90 @@ public class FallbackNoButtonInputConsumer extends BaseSwipeUpHandler {
public void onGestureCancelled() {
updateDisplacement(0);
mEndTarget = LAST_TASK;
finishAnimationTargetSetAnimationComplete();
setStateOnUiThread(STATE_GESTURE_CANCELLED);
}
@Override
public void onGestureEnded(float endVelocity, PointF velocity, PointF downPos) {
float flingThreshold = mContext.getResources()
.getDimension(R.dimen.quickstep_fling_threshold_velocity);
boolean isFling = Math.abs(endVelocity) > flingThreshold;
if (isFling) {
mEndTarget = endVelocity < 0 ? HOME : LAST_TASK;
} else if (mIsMotionPaused) {
mEndTarget = RECENTS;
if (mInQuickSwitchMode) {
// For now set it to non-null, it will be reset before starting the animation
mEndTarget = LAST_TASK;
} else {
mEndTarget = mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW ? HOME : LAST_TASK;
float flingThreshold = mContext.getResources()
.getDimension(R.dimen.quickstep_fling_threshold_velocity);
boolean isFling = Math.abs(endVelocity) > flingThreshold;
if (isFling) {
mEndTarget = endVelocity < 0 ? HOME : LAST_TASK;
} else if (mIsMotionPaused) {
mEndTarget = RECENTS;
} else {
mEndTarget = mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW ? HOME : LAST_TASK;
}
}
if (mSwipeAnimationTargetSet != null) {
finishAnimationTargetSet();
setStateOnUiThread(STATE_GESTURE_COMPLETED);
}
@Override
public void onConsumerAboutToBeSwitched(SwipeSharedState sharedState) {
if (mInQuickSwitchMode && mEndTarget != null) {
sharedState.canGestureBeContinued = true;
sharedState.goingToLauncher = false;
mCanceled = true;
mCurrentShift.cancelAnimation();
if (mFinishAnimation != null) {
mFinishAnimation.cancel();
}
if (mRecentsView != null) {
if (mFinishingRecentsAnimationForNewTaskId != -1) {
TaskView newRunningTaskView = mRecentsView.getTaskView(
mFinishingRecentsAnimationForNewTaskId);
int newRunningTaskId = newRunningTaskView != null
? newRunningTaskView.getTask().key.id
: -1;
mRecentsView.setCurrentTask(newRunningTaskId);
sharedState.setRecentsAnimationFinishInterrupted(newRunningTaskId);
}
mRecentsView.setOnScrollChangeListener(null);
}
} else {
setStateOnUiThread(STATE_HANDLER_INVALIDATED);
}
}
private void onHandlerInvalidated() {
mActivityInitListener.unregister();
if (mGestureEndCallback != null) {
mGestureEndCallback.run();
}
}
private void onHandlerInvalidatedWithRecents() {
mRecentsView.onGestureAnimationEnd();
mRecentsView.setDisallowScrollToClearAll(false);
mRecentsView.getClearAllButton().setVisibilityAlpha(1);
}
private void finishAnimationTargetSetAnimationComplete() {
switch (mEndTarget) {
case HOME:
mSwipeAnimationTargetSet.finishController(true, null, true);
mRecentsAnimationWrapper.finish(true, null, true);
break;
case LAST_TASK:
mSwipeAnimationTargetSet.finishController(false, null, false);
mRecentsAnimationWrapper.finish(false, null, false);
break;
case RECENTS: {
ThumbnailData thumbnail =
mSwipeAnimationTargetSet.controller.screenshotTask(mRunningTaskId);
mSwipeAnimationTargetSet.controller.setCancelWithDeferredScreenshot(true);
mRecentsAnimationWrapper.targetSet.controller.screenshotTask(mRunningTaskId);
mRecentsAnimationWrapper.setCancelWithDeferredScreenshot(true);
ActivityOptions options = ActivityOptions.makeCustomAnimation(mContext, 0, 0);
ActivityOptionsCompat.setFreezeRecentTasksList(options);
Bundle extras = new Bundle();
extras.putBinder(EXTRA_THUMBNAIL, new ObjectWrapper<>(thumbnail));
extras.putInt(EXTRA_TASK_ID, mRunningTaskId);
@@ -160,33 +317,58 @@ public class FallbackNoButtonInputConsumer extends BaseSwipeUpHandler {
Intent intent = new Intent(mOverviewComponentObserver.getOverviewIntent())
.putExtras(extras);
mContext.startActivity(intent, options.toBundle());
mSwipeAnimationTargetSet.controller.cleanupScreenshot();
mRecentsAnimationWrapper.targetSet.controller.cleanupScreenshot();
break;
}
case NEW_TASK: {
startNewTask(STATE_HANDLER_INVALIDATED, b -> {});
break;
}
}
if (mGestureEndCallback != null) {
mGestureEndCallback.run();
}
setStateOnUiThread(STATE_HANDLER_INVALIDATED);
}
private void finishAnimationTargetSet() {
if (mInQuickSwitchMode) {
// Recalculate the end target, some views might have been initialized after
// gesture has ended.
if (mRecentsView == null || !mRecentsAnimationWrapper.hasTargets()) {
mEndTarget = LAST_TASK;
} else {
final int runningTaskIndex = mRecentsView.getRunningTaskIndex();
final int taskToLaunch = mRecentsView.getNextPage();
mEndTarget = (runningTaskIndex >= 0 && taskToLaunch != runningTaskIndex)
? NEW_TASK : LAST_TASK;
}
}
float endProgress = mEndTarget.mEndProgress;
if (mCurrentShift.value != endProgress) {
if (mCurrentShift.value != endProgress || mInQuickSwitchMode) {
AnimatorSet anim = new AnimatorSet();
anim.play(mLauncherAlpha.animateToValue(
mLauncherAlpha.value, mEndTarget.mLauncherAlpha));
anim.play(mCurrentShift.animateToValue(mCurrentShift.value, endProgress));
anim.setDuration((long) (mEndTarget.mDurationMultiplier *
Math.abs(endProgress - mCurrentShift.value)));
anim.addListener(new AnimatorListenerAdapter() {
long duration = (long) (mEndTarget.mDurationMultiplier *
Math.abs(endProgress - mCurrentShift.value));
if (mRecentsView != null) {
duration = Math.max(duration, mRecentsView.getScroller().getDuration());
}
anim.setDuration(duration);
anim.addListener(new AnimationSuccessListener() {
@Override
public void onAnimationEnd(Animator animation) {
public void onAnimationSuccess(Animator animator) {
finishAnimationTargetSetAnimationComplete();
mFinishAnimation = null;
}
});
anim.start();
mFinishAnimation = anim;
} else {
finishAnimationTargetSetAnimationComplete();
}
@@ -194,34 +376,36 @@ public class FallbackNoButtonInputConsumer extends BaseSwipeUpHandler {
@Override
public void onRecentsAnimationStart(SwipeAnimationTargetSet targetSet) {
mSwipeAnimationTargetSet = targetSet;
Rect overviewStackBounds = new Rect(0, 0, mDP.widthPx, mDP.heightPx);
mRecentsAnimationWrapper.setController(targetSet);
mRecentsAnimationWrapper.enableInputConsumer();
Rect overviewStackBounds = new Rect(0, 0, mDp.widthPx, mDp.heightPx);
RemoteAnimationTargetCompat runningTaskTarget = targetSet.findTask(mRunningTaskId);
mDP.updateIsSeascape(mContext.getSystemService(WindowManager.class));
mDp.updateIsSeascape(mContext.getSystemService(WindowManager.class));
if (targetSet.homeContentInsets != null) {
mDP.updateInsets(targetSet.homeContentInsets);
mDp.updateInsets(targetSet.homeContentInsets);
}
if (runningTaskTarget != null) {
mClipAnimationHelper.updateSource(overviewStackBounds, runningTaskTarget);
}
mClipAnimationHelper.prepareAnimation(mDP, false /* isOpening */);
mClipAnimationHelper.prepareAnimation(mDp, false /* isOpening */);
initTransitionTarget();
mClipAnimationHelper.applyTransform(mSwipeAnimationTargetSet, mTransformParams);
applyTransformUnchecked();
if (mEndTarget != null) {
finishAnimationTargetSet();
}
setStateOnUiThread(STATE_APP_CONTROLLER_RECEIVED);
}
private void initTransitionTarget() {
mTransitionDragLength = mActivityControlHelper.getSwipeUpDestinationAndLength(
mDP, mContext, mTargetRect);
mDragLengthFactor = (float) mDP.heightPx / mTransitionDragLength;
mDp, mContext, mTargetRect);
mDragLengthFactor = (float) mDp.heightPx / mTransitionDragLength;
mClipAnimationHelper.updateTargetRect(mTargetRect);
}
@Override
public void onRecentsAnimationCanceled() { }
public void onRecentsAnimationCanceled() {
mRecentsAnimationWrapper.setController(null);
setStateOnUiThread(STATE_HANDLER_INVALIDATED);
}
}
@@ -35,7 +35,6 @@ import android.annotation.TargetApi;
import android.app.ActivityManager.RunningTaskInfo;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
import android.graphics.PointF;
import android.graphics.RectF;
import android.os.Build;
@@ -79,7 +78,6 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC
private final CachedEventDispatcher mRecentsViewDispatcher = new CachedEventDispatcher();
private final RunningTaskInfo mRunningTask;
private final Intent mHomeIntent;
private final OverviewCallbacks mOverviewCallbacks;
private final SwipeSharedState mSwipeSharedState;
private final InputMonitorCompat mInputMonitorCompat;
@@ -117,12 +115,13 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC
private float mStartDisplacement;
private Handler mMainThreadHandler;
private Runnable mCancelRecentsAnimationRunnable = () ->
private Runnable mCancelRecentsAnimationRunnable = () -> {
ActivityManagerWrapper.getInstance().cancelRecentsAnimation(
true /* restoreHomeStackPosition */);
};
public OtherActivityInputConsumer(Context base, RunningTaskInfo runningTaskInfo,
Intent homeIntent, boolean isDeferredDownTarget, OverviewCallbacks overviewCallbacks,
boolean isDeferredDownTarget, OverviewCallbacks overviewCallbacks,
Consumer<OtherActivityInputConsumer> onCompleteCallback,
SwipeSharedState swipeSharedState, InputMonitorCompat inputMonitorCompat,
RectF swipeTouchRegion, boolean disableHorizontalSwipe,
@@ -131,7 +130,6 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC
mMainThreadHandler = new Handler(Looper.getMainLooper());
mRunningTask = runningTaskInfo;
mHomeIntent = homeIntent;
mMode = SysUINavigationMode.getMode(base);
mSwipeTouchRegion = swipeTouchRegion;
mHandlerFactory = handlerFactory;
@@ -204,7 +202,7 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC
// Start the window animation on down to give more time for launcher to draw if the
// user didn't start the gesture over the back button
if (!mIsDeferredDownTarget) {
startTouchTrackingForWindowAnimation(ev.getEventTime());
startTouchTrackingForWindowAnimation(ev.getEventTime(), false);
}
RaceConditionTracker.onEvent(DOWN_EVT, EXIT);
@@ -253,6 +251,10 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC
}
}
float horizontalDist = Math.abs(displacementX);
float upDist = -displacement;
boolean isLikelyToStartNewTask = horizontalDist > upDist;
if (!mPassedPilferInputSlop) {
float displacementY = mLastPos.y - mDownPos.y;
if (squaredHypot(displacementX, displacementY) >= mSquaredTouchSlop) {
@@ -268,7 +270,8 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC
if (mIsDeferredDownTarget) {
// Deferred gesture, start the animation and gesture tracking once
// we pass the actual touch slop
startTouchTrackingForWindowAnimation(ev.getEventTime());
startTouchTrackingForWindowAnimation(
ev.getEventTime(), isLikelyToStartNewTask);
}
if (!mPassedWindowMoveSlop) {
mPassedWindowMoveSlop = true;
@@ -286,9 +289,6 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC
}
if (mMode == Mode.NO_BUTTON) {
float horizontalDist = Math.abs(displacementX);
float upDist = -displacement;
boolean isLikelyToStartNewTask = horizontalDist > upDist;
mMotionPauseDetector.setDisallowPause(upDist < mMotionPauseMinDisplacement
|| isLikelyToStartNewTask);
mMotionPauseDetector.addPosition(displacement, ev.getEventTime());
@@ -320,12 +320,13 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC
mInteractionHandler.onGestureStarted();
}
private void startTouchTrackingForWindowAnimation(long touchTimeMs) {
private void startTouchTrackingForWindowAnimation(
long touchTimeMs, boolean isLikelyToStartNewTask) {
TOUCH_INTERACTION_LOG.addLog("startRecentsAnimation");
RecentsAnimationListenerSet listenerSet = mSwipeSharedState.getActiveListener();
final BaseSwipeUpHandler handler = mHandlerFactory.newHandler(mRunningTask, touchTimeMs,
listenerSet != null);
listenerSet != null, isLikelyToStartNewTask);
mInteractionHandler = handler;
handler.setGestureEndCallback(this::onInteractionGestureFinished);
@@ -340,7 +341,7 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC
RecentsAnimationListenerSet newListenerSet =
mSwipeSharedState.newRecentsAnimationListenerSet();
newListenerSet.addListener(handler);
startRecentsActivityAsync(mHomeIntent, newListenerSet);
startRecentsActivityAsync(handler.getLaunchIntent(), newListenerSet);
}
}
@@ -289,7 +289,7 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
@ViewDebug.ExportedProperty(category = "launcher")
private float mContentAlpha = 1;
@ViewDebug.ExportedProperty(category = "launcher")
private float mFullscreenProgress = 0;
protected float mFullscreenProgress = 0;
// Keeps track of task id whose visual state should not be reset
private int mIgnoreResetTaskId = -1;
@@ -604,6 +604,7 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
TaskView taskView = (TaskView) getChildAt(i);
if (mIgnoreResetTaskId != taskView.getTask().key.id) {
taskView.resetVisualProperties();
taskView.setStableAlpha(mContentAlpha);
}
}
if (mRunningTaskTileHidden) {