From 18759647f0e3af23b69c8bdfa60c97a53c97f878 Mon Sep 17 00:00:00 2001 From: Tony Wickham Date: Thu, 10 Jan 2019 18:21:44 -0800 Subject: Swipe up to go home, swipe and hold to go to overview - Pause (swipe and hold) detection based on velocity and acceleration, so it feels immediately responsive - Don't show shelf during swipe; peek when swipe pauses - Disallow long swipe - If swiping to go home (we didn't detect a pause), animate window and launcher together (not final animation, but mechanism is in place) - Guarded by SWIPE_HOME flag Bug: 111926330 Change-Id: Ie4af04517c6688e3d649c2971a1aad197837cb3b --- quickstep/res/values/dimens.xml | 7 + .../android/quickstep/ActivityControlHelper.java | 146 +++++++++- .../quickstep/OtherActivityTouchConsumer.java | 13 +- .../src/com/android/quickstep/TouchConsumer.java | 4 +- .../quickstep/WindowTransformSwipeHandler.java | 317 ++++++++++++++++----- .../quickstep/fallback/FallbackRecentsView.java | 2 +- .../quickstep/util/ClipAnimationHelper.java | 4 +- .../quickstep/util/MotionPauseDetector.java | 141 +++++++++ .../quickstep/views/LauncherRecentsView.java | 2 +- .../com/android/quickstep/views/RecentsView.java | 2 +- .../android/quickstep/views/ShelfScrimView.java | 7 + 11 files changed, 555 insertions(+), 90 deletions(-) create mode 100644 quickstep/src/com/android/quickstep/util/MotionPauseDetector.java (limited to 'quickstep') diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml index 8d62ab8e1..04fd59ce0 100644 --- a/quickstep/res/values/dimens.xml +++ b/quickstep/res/values/dimens.xml @@ -30,9 +30,16 @@ loading full resolution screenshots. --> 600dp + 500dp 250dp + + 0.0285dp + 0.285dp + 0.5dp + 48dp + 50dp 50dp diff --git a/quickstep/src/com/android/quickstep/ActivityControlHelper.java b/quickstep/src/com/android/quickstep/ActivityControlHelper.java index c3df9c757..d6a7f2175 100644 --- a/quickstep/src/com/android/quickstep/ActivityControlHelper.java +++ b/quickstep/src/com/android/quickstep/ActivityControlHelper.java @@ -16,10 +16,12 @@ package com.android.quickstep; import static android.view.View.TRANSLATION_Y; + import static com.android.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS; import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY; import static com.android.launcher3.LauncherState.BACKGROUND_APP; import static com.android.launcher3.LauncherState.FAST_OVERVIEW; +import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.LauncherState.OVERVIEW; import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS_SPRING; import static com.android.launcher3.anim.Interpolators.LINEAR; @@ -31,6 +33,7 @@ import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_ import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_ROTATION; import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.annotation.TargetApi; @@ -44,6 +47,9 @@ import android.os.Build; import android.os.Handler; import android.os.Looper; import android.view.View; +import android.view.animation.Interpolator; + +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.UiThread; @@ -56,6 +62,7 @@ import com.android.launcher3.LauncherState; import com.android.launcher3.R; import com.android.launcher3.TestProtocol; import com.android.launcher3.allapps.DiscoveryBounce; +import com.android.launcher3.anim.AnimationSuccessListener; import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.anim.SpringObjectAnimator; import com.android.launcher3.compat.AccessibilityManagerCompat; @@ -105,6 +112,8 @@ public interface ActivityControlHelper { void onSwipeUpComplete(T activity); + @NonNull HomeAnimationFactory prepareHomeUI(T activity); + AnimationFactory prepareRecentsUI(T activity, boolean activityVisible, boolean animateActivity, Consumer callback); @@ -234,6 +243,32 @@ public interface ActivityControlHelper { DiscoveryBounce.showForOverviewIfNeeded(activity); } + @NonNull + @Override + public HomeAnimationFactory prepareHomeUI(Launcher activity) { + DeviceProfile dp = activity.getDeviceProfile(); + + return new HomeAnimationFactory() { + @NonNull + @Override + public RectF getWindowTargetRect() { + int halfIconSize = dp.iconSizePx / 2; + float targetCenterX = dp.availableWidthPx / 2; + float targetCenterY = dp.availableHeightPx - dp.hotseatBarSizePx; + return new RectF(targetCenterX - halfIconSize, targetCenterY - halfIconSize, + targetCenterX + halfIconSize, targetCenterY + halfIconSize); + } + + @NonNull + @Override + public Animator createActivityAnimationToHome() { + long accuracy = 2 * Math.max(dp.widthPx, dp.heightPx); + return activity.getStateManager().createAnimationToNewWorkspace( + NORMAL, accuracy).getTarget(); + } + }; + } + @Override public AnimationFactory prepareRecentsUI(Launcher activity, boolean activityVisible, boolean animateActivity, Consumer callback) { @@ -263,6 +298,9 @@ public interface ActivityControlHelper { } return new AnimationFactory() { + private Animator mShelfAnim; + private ShelfAnimState mShelfState; + @Override public void createActivityController(long transitionLength, @InteractionType int interactionType) { @@ -274,6 +312,40 @@ public interface ActivityControlHelper { public void onTransitionCancelled() { activity.getStateManager().goToState(startState, false /* animate */); } + + @Override + public void setShelfState(ShelfAnimState shelfState, Interpolator interpolator, + long duration) { + if (mShelfState == shelfState) { + return; + } + mShelfState = shelfState; + if (mShelfAnim != null) { + mShelfAnim.cancel(); + } + if (mShelfState == ShelfAnimState.CANCEL) { + return; + } + float shelfHiddenProgress = BACKGROUND_APP.getVerticalProgress(activity); + float shelfOverviewProgress = OVERVIEW.getVerticalProgress(activity); + float shelfPeekingProgress = shelfHiddenProgress + - (shelfHiddenProgress - shelfOverviewProgress) * 0.25f; + float toProgress = mShelfState == ShelfAnimState.HIDE + ? shelfHiddenProgress + : mShelfState == ShelfAnimState.PEEK + ? shelfPeekingProgress + : shelfOverviewProgress; + mShelfAnim = createShelfAnim(activity, toProgress); + mShelfAnim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mShelfAnim = null; + } + }); + mShelfAnim.setInterpolator(interpolator); + mShelfAnim.setDuration(duration); + mShelfAnim.start(); + } }; } @@ -295,13 +367,12 @@ public interface ActivityControlHelper { } AnimatorSet anim = new AnimatorSet(); - if (!activity.getDeviceProfile().isVerticalBarLayout()) { - Animator shiftAnim = new SpringObjectAnimator<>(activity.getAllAppsController(), - ALL_APPS_PROGRESS_SPRING, "allAppsSpringFromACH", - activity.getAllAppsController().getShiftRange(), + if (!activity.getDeviceProfile().isVerticalBarLayout() + && !FeatureFlags.SWIPE_HOME.get()) { + // Don't animate the shelf when SWIPE_HOME is true, because we update it atomically. + Animator shiftAnim = createShelfAnim(activity, fromState.getVerticalProgress(activity), endState.getVerticalProgress(activity)); - shiftAnim.setInterpolator(LINEAR); anim.play(shiftAnim); } @@ -322,6 +393,14 @@ public interface ActivityControlHelper { callback.accept(controller); } + private Animator createShelfAnim(Launcher activity, float ... progressValues) { + Animator shiftAnim = new SpringObjectAnimator(activity.getAllAppsController(), + ALL_APPS_PROGRESS_SPRING, "allAppsSpringFromACH", + activity.getAllAppsController().getShiftRange(), progressValues); + shiftAnim.setInterpolator(LINEAR); + return shiftAnim; + } + /** * Scale down recents from the center task being full screen to being in overview. */ @@ -512,6 +591,35 @@ public interface ActivityControlHelper { // TODO: } + @NonNull + @Override + public HomeAnimationFactory prepareHomeUI(RecentsActivity activity) { + RecentsView recentsView = activity.getOverviewPanel(); + + return new HomeAnimationFactory() { + @NonNull + @Override + public RectF getWindowTargetRect() { + float centerX = recentsView.getPivotX(); + float centerY = recentsView.getPivotY(); + return new RectF(centerX, centerY, centerX, centerY); + } + + @NonNull + @Override + public Animator createActivityAnimationToHome() { + Animator anim = ObjectAnimator.ofFloat(recentsView, CONTENT_ALPHA, 0); + anim.addListener(new AnimationSuccessListener() { + @Override + public void onAnimationSuccess(Animator animator) { + recentsView.startHome(); + } + }); + return anim; + } + }; + } + @Override public AnimationFactory prepareRecentsUI(RecentsActivity activity, boolean activityVisible, boolean animateActivity, Consumer callback) { @@ -524,12 +632,12 @@ public interface ActivityControlHelper { return new AnimationFactory() { - boolean isAnimatingHome = false; + boolean isAnimatingToRecents = false; @Override public void onRemoteAnimationReceived(RemoteAnimationTargetSet targets) { - isAnimatingHome = targets != null && targets.isAnimatingHome(); - if (!isAnimatingHome) { + isAnimatingToRecents = targets != null && targets.isAnimatingHome(); + if (!isAnimatingToRecents) { rv.setContentAlpha(1); } createActivityController(getSwipeUpDestinationAndLength( @@ -539,7 +647,7 @@ public interface ActivityControlHelper { @Override public void createActivityController(long transitionLength, int interactionType) { - if (!isAnimatingHome) { + if (!isAnimatingToRecents) { return; } @@ -667,10 +775,30 @@ public interface ActivityControlHelper { interface AnimationFactory { + enum ShelfAnimState { + HIDE(true), PEEK(true), OVERVIEW(false), CANCEL(false); + + ShelfAnimState(boolean shouldPreformHaptic) { + this.shouldPreformHaptic = shouldPreformHaptic; + } + + public final boolean shouldPreformHaptic; + } + default void onRemoteAnimationReceived(RemoteAnimationTargetSet targets) { } void createActivityController(long transitionLength, @InteractionType int interactionType); default void onTransitionCancelled() { } + + default void setShelfState(ShelfAnimState animState, Interpolator interpolator, + long duration) { } + } + + interface HomeAnimationFactory { + + @NonNull RectF getWindowTargetRect(); + + @NonNull Animator createActivityAnimationToHome(); } } diff --git a/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java b/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java index cd71f3d26..34d04b5ad 100644 --- a/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java +++ b/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java @@ -21,6 +21,7 @@ import static android.view.MotionEvent.ACTION_MOVE; 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.launcher3.util.RaceConditionTracker.ENTER; import static com.android.launcher3.util.RaceConditionTracker.EXIT; import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS; @@ -47,8 +48,10 @@ import android.view.ViewConfiguration; import android.view.WindowManager; import com.android.launcher3.MainThreadExecutor; +import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.util.RaceConditionTracker; import com.android.launcher3.util.TraceHelper; +import com.android.quickstep.util.MotionPauseDetector; import com.android.quickstep.util.RemoteAnimationTargetSet; import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.AssistDataReceiver; @@ -99,6 +102,7 @@ public class OtherActivityTouchConsumer extends ContextWrapper implements TouchC private Rect mStableInsets = new Rect(); private VelocityTracker mVelocityTracker; + private MotionPauseDetector mMotionPauseDetector; private MotionEventQueue mEventQueue; private boolean mIsGoingToLauncher; @@ -114,6 +118,7 @@ public class OtherActivityTouchConsumer extends ContextWrapper implements TouchC mRecentsModel = recentsModel; mHomeIntent = homeIntent; mVelocityTracker = velocityTracker; + mMotionPauseDetector = new MotionPauseDetector(base); mActivityControlHelper = activityControl; mMainThreadExecutor = mainThreadExecutor; mBackgroundThreadChoreographer = backgroundThreadChoreographer; @@ -192,6 +197,10 @@ public class OtherActivityTouchConsumer extends ContextWrapper implements TouchC if (mPassedInitialSlop && mInteractionHandler != null) { // Move dispatchMotion(ev, displacement - mStartDisplacement); + + if (FeatureFlags.SWIPE_HOME.get()) { + mMotionPauseDetector.addPosition(displacement); + } } break; } @@ -250,6 +259,7 @@ public class OtherActivityTouchConsumer extends ContextWrapper implements TouchC mRecentsModel.getTasks(null); mInteractionHandler = handler; handler.setGestureEndCallback(mEventQueue::reset); + mMotionPauseDetector.setOnMotionPauseListener(handler::onMotionPauseChanged); CountDownLatch drawWaitLock = new CountDownLatch(1); handler.setLauncherOnDrawCallback(() -> { @@ -336,7 +346,7 @@ public class OtherActivityTouchConsumer extends ContextWrapper implements TouchC if (mInteractionHandler != null) { final WindowTransformSwipeHandler handler = mInteractionHandler; mInteractionHandler = null; - mIsGoingToLauncher = handler.mIsGoingToRecents; + mIsGoingToLauncher = handler.mIsGoingToRecents || handler.mIsGoingToHome; mMainThreadExecutor.execute(handler::reset); } } @@ -414,6 +424,7 @@ public class OtherActivityTouchConsumer extends ContextWrapper implements TouchC mVelocityTracker.addMovement(ev); if (ev.getActionMasked() == ACTION_POINTER_UP) { mVelocityTracker.clear(); + mMotionPauseDetector.clear(); } } } diff --git a/quickstep/src/com/android/quickstep/TouchConsumer.java b/quickstep/src/com/android/quickstep/TouchConsumer.java index 225d29bd3..057a2ee1d 100644 --- a/quickstep/src/com/android/quickstep/TouchConsumer.java +++ b/quickstep/src/com/android/quickstep/TouchConsumer.java @@ -20,12 +20,12 @@ import android.os.Build; import android.view.Choreographer; import android.view.MotionEvent; +import androidx.annotation.IntDef; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.function.Consumer; -import androidx.annotation.IntDef; - @TargetApi(Build.VERSION_CODES.O) @FunctionalInterface public interface TouchConsumer extends Consumer { diff --git a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java index 33c7c4d71..d118e992a 100644 --- a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java +++ b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java @@ -20,21 +20,32 @@ import static com.android.launcher3.BaseActivity.STATE_HANDLER_INVISIBILITY_FLAG import static com.android.launcher3.Utilities.SINGLE_FRAME_MS; import static com.android.launcher3.Utilities.postAsyncCallback; import static com.android.launcher3.anim.Interpolators.DEACCEL; +import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN; import static com.android.launcher3.anim.Interpolators.LINEAR; import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2; import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE; import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS; import static com.android.launcher3.util.RaceConditionTracker.ENTER; import static com.android.launcher3.util.RaceConditionTracker.EXIT; +import static com.android.launcher3.config.FeatureFlags.SWIPE_HOME; +import static com.android.quickstep.ActivityControlHelper.AnimationFactory.ShelfAnimState.HIDE; +import static com.android.quickstep.ActivityControlHelper.AnimationFactory.ShelfAnimState.PEEK; import static com.android.quickstep.QuickScrubController.QUICK_SCRUB_FROM_APP_START_DURATION; import static com.android.quickstep.QuickScrubController.QUICK_SWITCH_FROM_APP_START_DURATION; import static com.android.quickstep.TouchConsumer.INTERACTION_NORMAL; import static com.android.quickstep.TouchConsumer.INTERACTION_QUICK_SCRUB; +import static com.android.quickstep.WindowTransformSwipeHandler.GestureEndTarget.HOME; +import static com.android.quickstep.WindowTransformSwipeHandler.GestureEndTarget.LAST_TASK; +import static com.android.quickstep.WindowTransformSwipeHandler.GestureEndTarget.NEW_TASK; +import static com.android.quickstep.WindowTransformSwipeHandler.GestureEndTarget.RECENTS; import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; import android.animation.ObjectAnimator; +import android.animation.TimeInterpolator; +import android.animation.ValueAnimator; import android.annotation.TargetApi; import android.app.ActivityManager.RunningTaskInfo; import android.content.Context; @@ -42,6 +53,7 @@ import android.content.res.Resources; import android.graphics.Canvas; import android.graphics.Point; import android.graphics.Rect; +import android.graphics.RectF; import android.os.Build; import android.os.Bundle; import android.os.Handler; @@ -57,6 +69,7 @@ import android.view.WindowManager; import android.view.animation.Interpolator; import androidx.annotation.AnyThread; +import androidx.annotation.NonNull; import androidx.annotation.UiThread; import androidx.annotation.WorkerThread; @@ -80,6 +93,7 @@ import com.android.launcher3.util.RaceConditionTracker; import com.android.launcher3.util.TraceHelper; import com.android.quickstep.ActivityControlHelper.ActivityInitListener; import com.android.quickstep.ActivityControlHelper.AnimationFactory; +import com.android.quickstep.ActivityControlHelper.AnimationFactory.ShelfAnimState; import com.android.quickstep.ActivityControlHelper.LayoutListener; import com.android.quickstep.TouchConsumer.InteractionType; import com.android.quickstep.TouchInteractionService.OverviewTouchConsumer; @@ -89,6 +103,7 @@ import com.android.quickstep.util.TransformedRect; import com.android.quickstep.views.RecentsView; import com.android.quickstep.views.TaskView; import com.android.systemui.shared.recents.model.ThumbnailData; +import com.android.systemui.shared.recents.utilities.RectFEvaluator; import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.InputConsumerController; import com.android.systemui.shared.system.LatencyTrackerCompat; @@ -115,27 +130,28 @@ public class WindowTransformSwipeHandler { private static final int STATE_APP_CONTROLLER_RECEIVED = 1 << 4; // Interaction finish states - private static final int STATE_SCALED_CONTROLLER_RECENTS = 1 << 5; - private static final int STATE_SCALED_CONTROLLER_LAST_TASK = 1 << 6; + private static final int STATE_SCALED_CONTROLLER_HOME = 1 << 5; + private static final int STATE_SCALED_CONTROLLER_RECENTS = 1 << 6; + private static final int STATE_SCALED_CONTROLLER_LAST_TASK = 1 << 7; - private static final int STATE_HANDLER_INVALIDATED = 1 << 7; - private static final int STATE_GESTURE_STARTED_QUICKSTEP = 1 << 8; - private static final int STATE_GESTURE_STARTED_QUICKSCRUB = 1 << 9; - private static final int STATE_GESTURE_CANCELLED = 1 << 10; - private static final int STATE_GESTURE_COMPLETED = 1 << 11; + private static final int STATE_HANDLER_INVALIDATED = 1 << 8; + private static final int STATE_GESTURE_STARTED_QUICKSTEP = 1 << 9; + private static final int STATE_GESTURE_STARTED_QUICKSCRUB = 1 << 10; + private static final int STATE_GESTURE_CANCELLED = 1 << 11; + private static final int STATE_GESTURE_COMPLETED = 1 << 12; // States for quick switch/scrub - private static final int STATE_CURRENT_TASK_FINISHED = 1 << 12; - private static final int STATE_QUICK_SCRUB_START = 1 << 13; - private static final int STATE_QUICK_SCRUB_END = 1 << 14; + private static final int STATE_CURRENT_TASK_FINISHED = 1 << 13; + private static final int STATE_QUICK_SCRUB_START = 1 << 14; + private static final int STATE_QUICK_SCRUB_END = 1 << 15; - private static final int STATE_CAPTURE_SCREENSHOT = 1 << 15; - private static final int STATE_SCREENSHOT_CAPTURED = 1 << 16; - private static final int STATE_SCREENSHOT_VIEW_SHOWN = 1 << 17; + private static final int STATE_CAPTURE_SCREENSHOT = 1 << 16; + private static final int STATE_SCREENSHOT_CAPTURED = 1 << 17; + private static final int STATE_SCREENSHOT_VIEW_SHOWN = 1 << 18; - private static final int STATE_RESUME_LAST_TASK = 1 << 18; - private static final int STATE_START_NEW_TASK = 1 << 19; - private static final int STATE_ASSIST_DATA_RECEIVED = 1 << 20; + private static final int STATE_RESUME_LAST_TASK = 1 << 19; + private static final int STATE_START_NEW_TASK = 1 << 20; + private static final int STATE_ASSIST_DATA_RECEIVED = 1 << 21; private static final int LAUNCHER_UI_STATES = @@ -160,6 +176,7 @@ public class WindowTransformSwipeHandler { "STATE_LAUNCHER_DRAWN", "STATE_ACTIVITY_MULTIPLIER_COMPLETE", "STATE_APP_CONTROLLER_RECEIVED", + "STATE_SCALED_CONTROLLER_HOME", "STATE_SCALED_CONTROLLER_RECENTS", "STATE_SCALED_CONTROLLER_LAST_TASK", "STATE_HANDLER_INVALIDATED", @@ -178,6 +195,30 @@ public class WindowTransformSwipeHandler { "STATE_ASSIST_DATA_RECEIVED", }; + enum GestureEndTarget { + HOME(1, STATE_SCALED_CONTROLLER_HOME, true, ContainerType.WORKSPACE), + + RECENTS(1, STATE_SCALED_CONTROLLER_RECENTS | STATE_CAPTURE_SCREENSHOT + | STATE_SCREENSHOT_VIEW_SHOWN, true, ContainerType.TASKSWITCHER), + + NEW_TASK(0, STATE_START_NEW_TASK, false, ContainerType.APP), + + LAST_TASK(0, STATE_SCALED_CONTROLLER_LAST_TASK, false, ContainerType.APP); + + GestureEndTarget(float endShift, int endState, boolean isLauncher, int containerType) { + this.endShift = endShift; + this.endState = endState; + this.isLauncher = isLauncher; + this.containerType = containerType; + } + + // 0 is app, 1 is overview + public final float endShift; + public final int endState; + public final boolean isLauncher; + public final int containerType; + } + public static final long MAX_SWIPE_DURATION = 350; public static final long MIN_SWIPE_DURATION = 80; public static final long MIN_OVERSHOOT_DURATION = 120; @@ -187,11 +228,15 @@ public class WindowTransformSwipeHandler { Math.min(1 / MIN_PROGRESS_FOR_OVERVIEW, 1 / (1 - MIN_PROGRESS_FOR_OVERVIEW)); private static final String SCREENSHOT_CAPTURED_EVT = "ScreenshotCaptured"; + private static final long SHELF_ANIM_DURATION = 120; + private final ClipAnimationHelper mClipAnimationHelper; private final ClipAnimationHelper.TransformParams mTransformParams; protected Runnable mGestureEndCallback; protected boolean mIsGoingToRecents; + protected boolean mIsGoingToHome; + private boolean mIsShelfPeeking; private DeviceProfile mDp; private int mTransitionDragLength; @@ -325,6 +370,13 @@ public class WindowTransformSwipeHandler { | STATE_SCALED_CONTROLLER_RECENTS, this::finishCurrentTransitionToRecents); + mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_GESTURE_COMPLETED + | STATE_SCALED_CONTROLLER_HOME | STATE_APP_CONTROLLER_RECEIVED + | STATE_ACTIVITY_MULTIPLIER_COMPLETE, + this::finishCurrentTransitionToHome); + mStateCallback.addCallback(STATE_SCALED_CONTROLLER_HOME | STATE_CURRENT_TASK_FINISHED, + this::reset); + mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_APP_CONTROLLER_RECEIVED | STATE_ACTIVITY_MULTIPLIER_COMPLETE | STATE_SCALED_CONTROLLER_RECENTS | STATE_CURRENT_TASK_FINISHED | STATE_GESTURE_COMPLETED @@ -426,7 +478,7 @@ public class WindowTransformSwipeHandler { mSyncTransactionApplier = applier; }); mRecentsView.setOnScrollChangeListener((v, scrollX, scrollY, oldScrollX, oldScrollY) -> { - if (!mBgLongSwipeMode) { + if (!mBgLongSwipeMode && !mIsGoingToHome) { updateFinalShift(); } }); @@ -598,7 +650,7 @@ public class WindowTransformSwipeHandler { if (displacement > mTransitionDragLength && mTransitionDragLength > 0) { mCurrentShift.updateValue(1); - if (!mBgLongSwipeMode) { + if (!mBgLongSwipeMode && !FeatureFlags.SWIPE_HOME.get()) { mBgLongSwipeMode = true; executeOnUiThread(this::onLongSwipeEnabledUi); } @@ -615,6 +667,23 @@ public class WindowTransformSwipeHandler { } } + public void onMotionPauseChanged(boolean isPaused) { + setShelfState(isPaused ? PEEK : HIDE, FAST_OUT_SLOW_IN, SHELF_ANIM_DURATION); + } + + public void setShelfState(ShelfAnimState shelfState, Interpolator interpolator, long duration) { + if (mInteractionType == INTERACTION_NORMAL) { + executeOnUiThread(() -> { + mAnimationFactory.setShelfState(shelfState, interpolator, duration); + mIsShelfPeeking = shelfState == PEEK; + if (mRecentsView != null && shelfState.shouldPreformHaptic) { + mRecentsView.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY, + HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING); + } + }); + } + } + /** * Called by {@link #mLayoutListener} when launcher layout changes */ @@ -676,7 +745,8 @@ public class WindowTransformSwipeHandler { final boolean passed = mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW; if (passed != mPassedOverviewThreshold) { mPassedOverviewThreshold = passed; - if (mInteractionType == INTERACTION_NORMAL && mRecentsView != null) { + if (mInteractionType == INTERACTION_NORMAL && mRecentsView != null + && !SWIPE_HOME.get()) { mRecentsView.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY, HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING); } @@ -702,6 +772,9 @@ public class WindowTransformSwipeHandler { } private void updateLauncherTransitionProgress() { + if (mIsGoingToHome) { + return; + } float progress = mCurrentShift.value; mLauncherTransitionController.setPlayFraction( progress <= mShiftAtGestureStart || mShiftAtGestureStart >= 1 @@ -816,7 +889,7 @@ public class WindowTransformSwipeHandler { float velocityXPxPerMs = velocityX / 1000; long duration = MAX_SWIPE_DURATION; float currentShift = mCurrentShift.value; - final boolean goingToRecents; + final GestureEndTarget endTarget; float endShift; final float startShift; Interpolator interpolator = DEACCEL; @@ -825,24 +898,40 @@ public class WindowTransformSwipeHandler { boolean goingToNewTask = mRecentsView != null && nextPage != runningTaskIndex; final boolean reachedOverviewThreshold = currentShift >= MIN_PROGRESS_FOR_OVERVIEW; if (!isFling) { - goingToRecents = reachedOverviewThreshold && mGestureStarted; - endShift = goingToRecents ? 1 : 0; + if (SWIPE_HOME.get()) { + if (mIsShelfPeeking) { + endTarget = RECENTS; + } else if (goingToNewTask) { + endTarget = NEW_TASK; + } else { + endTarget = currentShift < MIN_PROGRESS_FOR_OVERVIEW ? LAST_TASK : HOME; + } + } else { + endTarget = reachedOverviewThreshold && mGestureStarted ? RECENTS : LAST_TASK; + } + endShift = endTarget.endShift; long expectedDuration = Math.abs(Math.round((endShift - currentShift) * MAX_SWIPE_DURATION * SWIPE_DURATION_MULTIPLIER)); duration = Math.min(MAX_SWIPE_DURATION, expectedDuration); startShift = currentShift; - interpolator = goingToRecents ? OVERSHOOT_1_2 : DEACCEL; + interpolator = endTarget == RECENTS ? OVERSHOOT_1_2 : DEACCEL; } else { - // If user scrolled to a new task, only go to recents if they already passed - // the overview threshold. Otherwise, we'll snap to the new task and launch it. - goingToRecents = endVelocity < 0 && (!goingToNewTask || reachedOverviewThreshold); - endShift = goingToRecents ? 1 : 0; + if (SWIPE_HOME.get() && endVelocity < 0 && !mIsShelfPeeking) { + endTarget = HOME; + } else if (endVelocity < 0 && (!goingToNewTask || reachedOverviewThreshold)) { + // If user scrolled to a new task, only go to recents if they already passed + // the overview threshold. Otherwise, we'll snap to the new task and launch it. + endTarget = RECENTS; + } else { + endTarget = goingToNewTask ? NEW_TASK : LAST_TASK; + } + endShift = endTarget.endShift; startShift = Utilities.boundToRange(currentShift - velocityPxPerMs * SINGLE_FRAME_MS / mTransitionDragLength, 0, 1); float minFlingVelocity = mContext.getResources() .getDimension(R.dimen.quickstep_fling_min_velocity); if (Math.abs(endVelocity) > minFlingVelocity && mTransitionDragLength > 0) { - if (goingToRecents) { + if (endTarget == RECENTS) { Interpolators.OvershootParams overshoot = new Interpolators.OvershootParams( startShift, endShift, endShift, velocityPxPerMs, mTransitionDragLength); endShift = overshoot.end; @@ -860,12 +949,19 @@ public class WindowTransformSwipeHandler { } } } - if (goingToRecents) { + + if (endTarget == HOME) { + setShelfState(ShelfAnimState.CANCEL, LINEAR, 0); + duration = Math.max(MIN_OVERSHOOT_DURATION, duration); + } else if (endTarget == RECENTS) { mRecentsAnimationWrapper.enableTouchProxy(); - } else if (goingToNewTask) { + if (SWIPE_HOME.get()) { + setShelfState(ShelfAnimState.OVERVIEW, interpolator, duration); + } + } else if (endTarget == NEW_TASK) { // We aren't goingToRecents, and user scrolled/flung to a new task; snap to the closest // task in that direction and launch it (in startNewTask()). - int taskToLaunch = runningTaskIndex + (nextPage > runningTaskIndex ? 1 : - 1); + int taskToLaunch = runningTaskIndex + (nextPage > runningTaskIndex ? 1 : -1); if (taskToLaunch >= mRecentsView.getTaskViewCount()) { // Scrolled to Clear all button, snap back to current task and resume it. mRecentsView.snapToPage(runningTaskIndex, Math.toIntExact(duration)); @@ -882,17 +978,16 @@ public class WindowTransformSwipeHandler { duration = Math.max(duration, durationX); } } - - animateToProgress(startShift, endShift, duration, interpolator, goingToRecents, - goingToNewTask, velocityPxPerMs); + animateToProgress(startShift, endShift, duration, interpolator, endTarget, velocityPxPerMs); } - private void doLogGesture(boolean toLauncher) { + private void doLogGesture(GestureEndTarget endTarget) { DeviceProfile dp = mDp; if (dp == null) { // We probably never received an animation controller, skip logging. return; } + boolean toLauncher = endTarget.isLauncher; final int direction; if (dp.isVerticalBarLayout()) { direction = (dp.isSeascape() ^ toLauncher) ? Direction.LEFT : Direction.RIGHT; @@ -900,60 +995,92 @@ public class WindowTransformSwipeHandler { direction = toLauncher ? Direction.UP : Direction.DOWN; } - int dstContainerType = toLauncher ? ContainerType.TASKSWITCHER : ContainerType.APP; UserEventDispatcher.newInstance(mContext).logStateChangeAction( mLogAction, direction, ContainerType.NAVBAR, ContainerType.APP, - dstContainerType, + endTarget.containerType, 0); } /** Animates to the given progress, where 0 is the current app and 1 is overview. */ private void animateToProgress(float start, float end, long duration, Interpolator interpolator, - boolean goingToRecents, boolean goingToNewTask, float velocityPxPerMs) { + GestureEndTarget target, float velocityPxPerMs) { mRecentsAnimationWrapper.runOnInit(() -> animateToProgressInternal(start, end, duration, - interpolator, goingToRecents, goingToNewTask, velocityPxPerMs)); + interpolator, target, velocityPxPerMs)); } private void animateToProgressInternal(float start, float end, long duration, - Interpolator interpolator, boolean goingToRecents, boolean goingToNewTask, - float velocityPxPerMs) { - mIsGoingToRecents = goingToRecents; - ObjectAnimator anim = mCurrentShift.animateToValue(start, end).setDuration(duration); - anim.setInterpolator(interpolator); - anim.addListener(new AnimationSuccessListener() { + Interpolator interpolator, GestureEndTarget target, float velocityPxPerMs) { + mIsGoingToHome = target == HOME; + mIsGoingToRecents = target == RECENTS; + ActivityControlHelper.HomeAnimationFactory homeAnimFactory; + Animator windowAnim; + if (mIsGoingToHome) { + if (mActivity != null) { + homeAnimFactory = mActivityControlHelper.prepareHomeUI(mActivity); + } else { + homeAnimFactory = new ActivityControlHelper.HomeAnimationFactory() { + @NonNull + @Override + public RectF getWindowTargetRect() { + RectF fallbackTarget = new RectF(mClipAnimationHelper.getTargetRect()); + Utilities.scaleRectFAboutCenter(fallbackTarget, 0.25f); + return fallbackTarget; + } + + @NonNull + @Override + public Animator createActivityAnimationToHome() { + return new AnimatorSet(); + } + }; + mStateCallback.addChangeHandler(STATE_LAUNCHER_PRESENT | STATE_HANDLER_INVALIDATED, + isPresent -> mRecentsView.startHome()); + } + windowAnim = createWindowAnimationToHome(start, homeAnimFactory.getWindowTargetRect()); + mLauncherTransitionController = null; + } else { + windowAnim = mCurrentShift.animateToValue(start, end); + homeAnimFactory = null; + } + windowAnim.setDuration(duration).setInterpolator(interpolator); + windowAnim.addListener(new AnimationSuccessListener() { @Override public void onAnimationSuccess(Animator animator) { - int recentsState = STATE_SCALED_CONTROLLER_RECENTS | STATE_CAPTURE_SCREENSHOT - | STATE_SCREENSHOT_VIEW_SHOWN; - setStateOnUiThread(mIsGoingToRecents - ? recentsState - : goingToNewTask - ? STATE_START_NEW_TASK - : STATE_SCALED_CONTROLLER_LAST_TASK); + setStateOnUiThread(target.endState); } }); - anim.start(); + windowAnim.start(); long startMillis = SystemClock.uptimeMillis(); + // Always play the entire launcher animation when going home, since it is separate from + // the animation that has been controlled thus far. + final float finalStart = mIsGoingToHome ? 0 : start; executeOnUiThread(() -> { // Animate the launcher components at the same time as the window, always on UI thread. + // Adjust start progress and duration in case we are on a different thread. + long elapsedMillis = SystemClock.uptimeMillis() - startMillis; + elapsedMillis = Utilities.boundToRange(elapsedMillis, 0, duration); + float elapsedProgress = (float) elapsedMillis / duration; + float adjustedStart = Utilities.mapRange(elapsedProgress, finalStart, end); + long adjustedDuration = duration - elapsedMillis; + // We want to use the same interpolator as the window, but need to adjust it to + // interpolate over the remaining progress (end - start). + TimeInterpolator adjustedInterpolator = Interpolators.mapToProgress( + interpolator, adjustedStart, end); + if (homeAnimFactory != null) { + Animator homeAnim = homeAnimFactory.createActivityAnimationToHome(); + homeAnim.setDuration(adjustedDuration).setInterpolator(adjustedInterpolator); + homeAnim.start(); + mLauncherTransitionController = null; + } if (mLauncherTransitionController == null) { return; } - if (start == end || duration <= 0) { + if (finalStart == end || duration <= 0) { mLauncherTransitionController.dispatchSetInterpolator(t -> end); mLauncherTransitionController.getAnimationPlayer().end(); } else { - // Adjust start progress and duration in case we are on a different thread. - long elapsedMillis = SystemClock.uptimeMillis() - startMillis; - elapsedMillis = Utilities.boundToRange(elapsedMillis, 0, duration); - float elapsedProgress = (float) elapsedMillis / duration; - float adjustedStart = Utilities.mapRange(elapsedProgress, start, end); - long adjustedDuration = duration - elapsedMillis; - // We want to use the same interpolator as the window, but need to adjust it to - // interpolate over the remaining progress (end - start). - mLauncherTransitionController.dispatchSetInterpolator(Interpolators.mapToProgress( - interpolator, adjustedStart, end)); + mLauncherTransitionController.dispatchSetInterpolator(adjustedInterpolator); mLauncherTransitionController.getAnimationPlayer().setDuration(adjustedDuration); if (QUICKSTEP_SPRINGS.get()) { @@ -965,10 +1092,49 @@ public class WindowTransformSwipeHandler { }); } + /** + * Creates an Animator that transforms the current app window into the home app. + * @param startProgress The progress of {@link #mCurrentShift} to start the window from. + * @param endTarget Where to animate the window towards. + */ + private Animator createWindowAnimationToHome(float startProgress, RectF endTarget) { + final RemoteAnimationTargetSet targetSet = mRecentsAnimationWrapper.targetSet; + RectF startRect = new RectF(mClipAnimationHelper.applyTransform(targetSet, + mTransformParams.setProgress(startProgress))); + RectF originalTarget = new RectF(mClipAnimationHelper.getTargetRect()); + final RectF finalTarget = endTarget; + + final RectFEvaluator rectFEvaluator = new RectFEvaluator(); + final RectF targetRect = new RectF(); + final RectF currentRect = new RectF(); + + ValueAnimator anim = ValueAnimator.ofFloat(0, 1); + anim.addUpdateListener(animation -> { + float progress = animation.getAnimatedFraction(); + float interpolatedProgress = Interpolators.ACCEL_2.getInterpolation(progress); + // Initially go towards original target (task view in recents), + // but accelerate towards the final target. + // TODO: This is technically not correct. Instead, motion should continue at + // the released velocity but accelerate towards the target. + targetRect.set(rectFEvaluator.evaluate(interpolatedProgress, + originalTarget, finalTarget)); + currentRect.set(rectFEvaluator.evaluate(progress, startRect, targetRect)); + float alpha = 1 - interpolatedProgress; + SyncRtSurfaceTransactionApplierCompat syncTransactionApplier + = Looper.myLooper() == mMainThreadHandler.getLooper() + ? mSyncTransactionApplier + : null; + mTransformParams.setCurrentRectAndTargetAlpha(currentRect, alpha) + .setSyncTransactionApplier(syncTransactionApplier); + mClipAnimationHelper.applyTransform(targetSet, mTransformParams); + }); + return anim; + } + @UiThread private void resumeLastTaskForQuickstep() { setStateOnUiThread(STATE_RESUME_LAST_TASK); - doLogGesture(false /* toLauncher */); + doLogGesture(LAST_TASK); reset(); } @@ -987,7 +1153,7 @@ public class WindowTransformSwipeHandler { mMainThreadHandler); }); mTouchInteractionLog.finishRecentsAnimation(false); - doLogGesture(false /* toLauncher */); + doLogGesture(NEW_TASK); } public void reset() { @@ -1007,10 +1173,6 @@ public class WindowTransformSwipeHandler { mActivityInitListener.unregister(); mTaskSnapshot = null; - - if (mRecentsView != null) { - mRecentsView.setOnScrollChangeListener(null); - } } private void invalidateHandlerWithLauncher() { @@ -1019,6 +1181,7 @@ public class WindowTransformSwipeHandler { mActivityControlHelper.getAlphaProperty(mActivity).setValue(1); mRecentsView.setRunningTaskIconScaledDown(false); + mRecentsView.setOnScrollChangeListener(null); mQuickScrubController.cancelActiveQuickscrub(); } @@ -1101,6 +1264,15 @@ public class WindowTransformSwipeHandler { mTouchInteractionLog.finishRecentsAnimation(true); } + private void finishCurrentTransitionToHome() { + synchronized (mRecentsAnimationWrapper) { + mRecentsAnimationWrapper.finish(true /* toRecents */, + () -> setStateOnUiThread(STATE_CURRENT_TASK_FINISHED)); + } + mTouchInteractionLog.finishRecentsAnimation(true); + doLogGesture(HOME); + } + private void setupLauncherUiAfterSwipeUpAnimation() { if (mLauncherTransitionController != null) { mLauncherTransitionController.getAnimationPlayer().end(); @@ -1114,7 +1286,7 @@ public class WindowTransformSwipeHandler { RecentsModel.INSTANCE.get(mContext).onOverviewShown(false, TAG); - doLogGesture(true /* toLauncher */); + doLogGesture(RECENTS); reset(); } @@ -1132,8 +1304,7 @@ public class WindowTransformSwipeHandler { long duration = FeatureFlags.QUICK_SWITCH.get() ? QUICK_SWITCH_FROM_APP_START_DURATION : QUICK_SCRUB_FROM_APP_START_DURATION; - animateToProgress(mCurrentShift.value, 1f, duration, LINEAR, true /* goingToRecents */, - false /* goingToNewTask */, 1f); + animateToProgress(mCurrentShift.value, 1f, duration, LINEAR, RECENTS, 1f); } private void onQuickScrubStartUi() { diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java index 261f45dc2..9679b81ca 100644 --- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java +++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java @@ -39,7 +39,7 @@ public class FallbackRecentsView extends RecentsView { } @Override - protected void startHome() { + public void startHome() { mActivity.startHome(); } diff --git a/quickstep/src/com/android/quickstep/util/ClipAnimationHelper.java b/quickstep/src/com/android/quickstep/util/ClipAnimationHelper.java index 31de68395..c612b053a 100644 --- a/quickstep/src/com/android/quickstep/util/ClipAnimationHelper.java +++ b/quickstep/src/com/android/quickstep/util/ClipAnimationHelper.java @@ -364,8 +364,8 @@ public class ClipAnimationHelper { public static class TransformParams { float progress; - float offsetX; - float offsetScale; + public float offsetX; + public float offsetScale; @Nullable RectF currentRect; float targetAlpha; boolean forLiveTile; diff --git a/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java b/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java new file mode 100644 index 000000000..258e9227e --- /dev/null +++ b/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java @@ -0,0 +1,141 @@ +/* + * 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.util; + +import android.content.Context; +import android.content.res.Resources; +import android.os.SystemClock; +import android.view.MotionEvent; + +import com.android.launcher3.R; + +/** + * Given positions along x- or y-axis, tracks velocity and acceleration and determines when there is + * a pause in motion. + */ +public class MotionPauseDetector { + + // The percentage of the previous speed that determines whether this is a rapid deceleration. + // The bigger this number, the easier it is to trigger the first pause. + private static final float RAPID_DECELERATION_FACTOR = 0.6f; + + private final float mSpeedVerySlow; + private final float mSpeedSomewhatFast; + private final float mSpeedFast; + private final float mMinDisplacementForPause; + + private Long mPreviousTime = null; + private Float mPreviousPosition = null; + private Float mPreviousVelocity = null; + + private Float mFirstPosition = null; + + private OnMotionPauseListener mOnMotionPauseListener; + private boolean mIsPaused; + // Bias more for the first pause to make it feel extra responsive. + private boolean mHasEverBeenPaused; + + public MotionPauseDetector(Context context) { + Resources res = context.getResources(); + mSpeedVerySlow = res.getDimension(R.dimen.motion_pause_detector_speed_very_slow); + mSpeedSomewhatFast = res.getDimension(R.dimen.motion_pause_detector_speed_somewhat_fast); + mSpeedFast = res.getDimension(R.dimen.motion_pause_detector_speed_fast); + mMinDisplacementForPause = res.getDimension(R.dimen.motion_pause_detector_min_displacement); + } + + /** + * Get callbacks for when motion pauses and resumes, including an + * immediate callback with the current pause state. + */ + public void setOnMotionPauseListener(OnMotionPauseListener listener) { + mOnMotionPauseListener = listener; + if (mOnMotionPauseListener != null) { + mOnMotionPauseListener.onMotionPauseChanged(mIsPaused); + } + } + + /** + * Computes velocity and acceleration to determine whether the motion is paused. + * @param position The x or y component of the motion being tracked. + * + * TODO: Use historical positions as well, e.g. {@link MotionEvent#getHistoricalY(int, int)}. + */ + public void addPosition(float position) { + if (mFirstPosition == null) { + mFirstPosition = position; + } + long time = SystemClock.uptimeMillis(); + if (mPreviousTime != null && mPreviousPosition != null) { + long changeInTime = Math.max(1, time - mPreviousTime); + float changeInPosition = position - mPreviousPosition; + float velocity = changeInPosition / changeInTime; + if (mPreviousVelocity != null) { + checkMotionPaused(velocity, mPreviousVelocity, Math.abs(position - mFirstPosition)); + } + mPreviousVelocity = velocity; + } + mPreviousTime = time; + mPreviousPosition = position; + } + + private void checkMotionPaused(float velocity, float prevVelocity, float totalDisplacement) { + float speed = Math.abs(velocity); + float previousSpeed = Math.abs(prevVelocity); + boolean isPaused; + if (mIsPaused) { + // Continue to be paused until moving at a fast speed. + isPaused = speed < mSpeedFast || previousSpeed < mSpeedFast; + } else { + if (velocity < 0 != prevVelocity < 0) { + // We're just changing directions, not necessarily stopping. + isPaused = false; + } else { + isPaused = speed < mSpeedVerySlow && previousSpeed < mSpeedVerySlow; + if (!isPaused && !mHasEverBeenPaused) { + // We want to be more aggressive about detecting the first pause to ensure it + // feels as responsive as possible; getting two very slow speeds back to back + // takes too long, so also check for a rapid deceleration. + boolean isRapidDeceleration = speed < previousSpeed * RAPID_DECELERATION_FACTOR; + isPaused = isRapidDeceleration && speed < mSpeedSomewhatFast; + } + } + } + boolean passedMinDisplacement = totalDisplacement >= mMinDisplacementForPause; + isPaused &= passedMinDisplacement; + if (mIsPaused != isPaused) { + mIsPaused = isPaused; + if (mIsPaused) { + mHasEverBeenPaused = true; + } + if (mOnMotionPauseListener != null) { + mOnMotionPauseListener.onMotionPauseChanged(mIsPaused); + } + } + } + + public void clear() { + mPreviousTime = null; + mPreviousPosition = null; + mPreviousVelocity = null; + mFirstPosition = null; + setOnMotionPauseListener(null); + mIsPaused = mHasEverBeenPaused = false; + } + + public interface OnMotionPauseListener { + void onMotionPauseChanged(boolean isPaused); + } +} diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java index f8eced00a..b0ca4d70b 100644 --- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java +++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java @@ -92,7 +92,7 @@ public class LauncherRecentsView extends RecentsView { } @Override - protected void startHome() { + public void startHome() { if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { takeScreenshotAndFinishRecentsAnimation(true, () -> mActivity.getStateManager().goToState(NORMAL)); diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java index 5cbae65d4..840d2bdcf 100644 --- a/quickstep/src/com/android/quickstep/views/RecentsView.java +++ b/quickstep/src/com/android/quickstep/views/RecentsView.java @@ -719,7 +719,7 @@ public abstract class RecentsView extends PagedView impl } } - protected abstract void startHome(); + public abstract void startHome(); public void reset() { setRunningTaskViewShowScreenshot(false); diff --git a/quickstep/src/com/android/quickstep/views/ShelfScrimView.java b/quickstep/src/com/android/quickstep/views/ShelfScrimView.java index d2b3bcc17..fe05c4f82 100644 --- a/quickstep/src/com/android/quickstep/views/ShelfScrimView.java +++ b/quickstep/src/com/android/quickstep/views/ShelfScrimView.java @@ -15,6 +15,7 @@ */ package com.android.quickstep.views; +import static com.android.launcher3.LauncherState.BACKGROUND_APP; import static com.android.launcher3.LauncherState.OVERVIEW; import static com.android.launcher3.anim.Interpolators.ACCEL; import static com.android.launcher3.anim.Interpolators.LINEAR; @@ -33,6 +34,7 @@ import com.android.launcher3.DeviceProfile; import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.anim.Interpolators; +import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.util.Themes; import com.android.launcher3.views.ScrimView; @@ -135,6 +137,11 @@ public class ShelfScrimView extends ScrimView { if (mProgress >= 1) { mRemainingScreenColor = 0; mShelfColor = 0; + if (FeatureFlags.SWIPE_HOME.get() + && mLauncher.getStateManager().getState() == BACKGROUND_APP) { + // Show the shelf background when peeking during swipe up. + mShelfColor = setColorAlphaBound(mEndScrim, mMidAlpha); + } } else if (mProgress >= mMidProgress) { mRemainingScreenColor = 0; -- cgit v1.2.3