Add long swipe from app to overview gesture (with flag).
High level:
- As you swipe up from an app (OtherActivityInputConsumer),
a state transition animation to All Apps is created in
AnimatorControllerWithResistance. The animation is played
alongside the Recents resistance animation (i.e. past the
settling point of Overview, which is at mCurrentShift 1).
- The actual state transition to All Apps only happens if you
release your finger in the "all apps region." This is set to
mCurrentShift 2, so double the distance that Overview rests.
- A haptic plays whenever you enter or exit this region, and
the all apps animation is set to 0 until the region is
active. This is so it's clear that something different is
happening.
- The panel that was previously used for tablets is now used
for phones during this transition. It comes in at full
opacity when you enter the region, and the contents (apps
and search suggestions) fade in as you continue swiping.
- The only gesture that is recognized in the all apps region
is a fling downwards, which will return you to the previous
app. Otherwise a left/right/up fling or slow release will
finish the all apps transition.
- The threshold is ignored if the flag is disabled (default)
or if FallbackActivityInterface is active.
Flag:
The threshold is ignored if ENABLE_ALL_APPS_FROM_OVERVIEW is
disabled (default).
Bug: 259619990
Bug: 275132633
Test: Manual with and without the flag enabled
Change-Id: Ie311b77252416d97677b2c56fad61dfd392b6fe8
diff --git a/protos/launcher_trace.proto b/protos/launcher_trace.proto
index 65fcfe5..e5a86a0 100644
--- a/protos/launcher_trace.proto
+++ b/protos/launcher_trace.proto
@@ -63,5 +63,6 @@
RECENTS = 2;
NEW_TASK = 3;
LAST_TASK = 4;
+ ALL_APPS = 5;
}
}
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index d64347f..b7a29e0 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -39,6 +39,7 @@
import static com.android.launcher3.util.SystemUiController.UI_STATE_FULLSCREEN_TASK;
import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
import static com.android.launcher3.util.window.RefreshRateTracker.getSingleFrameMs;
+import static com.android.quickstep.GestureState.GestureEndTarget.ALL_APPS;
import static com.android.quickstep.GestureState.GestureEndTarget.HOME;
import static com.android.quickstep.GestureState.GestureEndTarget.LAST_TASK;
import static com.android.quickstep.GestureState.GestureEndTarget.NEW_TASK;
@@ -161,6 +162,9 @@
private static final ArrayList<String> STATE_NAMES = new ArrayList<>();
+ /** Shift distance to transition to All Apps if ENABLE_ALL_APPS_FROM_OVERVIEW. */
+ public static final float ALL_APPS_SHIFT_THRESHOLD = 2f;
+
protected final BaseActivityInterface<S, T> mActivityInterface;
protected final InputConsumerProxy mInputConsumerProxy;
protected final ActivityInitListener mActivityInitListener;
@@ -247,6 +251,8 @@
getNextStateFlag("STATE_CURRENT_TASK_FINISHED");
private static final int STATE_FINISH_WITH_NO_END =
getNextStateFlag("STATE_FINISH_WITH_NO_END");
+ private static final int STATE_SETTLED_ON_ALL_APPS =
+ getNextStateFlag("STATE_SETTLED_ON_ALL_APPS");
private static final int LAUNCHER_UI_STATES =
STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN | STATE_LAUNCHER_STARTED |
@@ -299,6 +305,7 @@
private boolean mGestureStarted;
private boolean mLogDirectionUpOrLeft = true;
private boolean mIsLikelyToStartNewTask;
+ private boolean mIsInAllAppsRegion;
private final long mTouchTimeMs;
private long mLauncherFrameDrawnTime;
@@ -432,6 +439,9 @@
this::finishCurrentTransitionToHome);
mStateCallback.runOnceAtState(STATE_SCALED_CONTROLLER_HOME | STATE_CURRENT_TASK_FINISHED,
this::reset);
+ mStateCallback.runOnceAtState(STATE_SETTLED_ON_ALL_APPS | STATE_SCREENSHOT_CAPTURED
+ | STATE_GESTURE_COMPLETED,
+ this::finishCurrentTransitionToAllApps);
mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_APP_CONTROLLER_RECEIVED
| STATE_LAUNCHER_DRAWN | STATE_SCALED_CONTROLLER_RECENTS
@@ -681,7 +691,9 @@
maybeUpdateRecentsAttachedState(true/* animate */, true/* moveRunningTask */);
Optional.ofNullable(mActivityInterface.getTaskbarController())
.ifPresent(TaskbarUIController::startTranslationSpring);
- performHapticFeedback();
+ if (!mIsInAllAppsRegion) {
+ performHapticFeedback();
+ }
}
@Override
@@ -695,7 +707,7 @@
maybeUpdateRecentsAttachedState(true /* animate */);
}
- private void maybeUpdateRecentsAttachedState(boolean animate) {
+ protected void maybeUpdateRecentsAttachedState(boolean animate) {
maybeUpdateRecentsAttachedState(animate, false /* moveRunningTask */);
}
@@ -716,7 +728,9 @@
? mRecentsAnimationTargets.findTask(mGestureState.getRunningTaskId())
: null;
final boolean recentsAttachedToAppWindow;
- if (mGestureState.getEndTarget() != null) {
+ if (mIsInAllAppsRegion) {
+ recentsAttachedToAppWindow = false;
+ } else if (mGestureState.getEndTarget() != null) {
recentsAttachedToAppWindow = mGestureState.getEndTarget().recentsAttachedToAppWindow;
} else if (mContinuingLastGesture
&& mRecentsView.getRunningTaskIndex() != mRecentsView.getNextPage()) {
@@ -772,6 +786,26 @@
}
}
+ /**
+ * Update whether user is currently dragging in a region that will trigger all apps.
+ */
+ private void setIsInAllAppsRegion(boolean isInAllAppsRegion) {
+ if (mIsInAllAppsRegion == isInAllAppsRegion
+ || !mActivityInterface.allowAllAppsFromOverview()) {
+ return;
+ }
+ mIsInAllAppsRegion = isInAllAppsRegion;
+
+ // Newly entering or exiting the zone - do haptic and animate recent tasks.
+ VibratorWrapper.INSTANCE.get(mContext).vibrate(OVERVIEW_HAPTIC);
+ maybeUpdateRecentsAttachedState(true);
+
+ // Draw active task below Launcher so that All Apps can appear over it.
+ runActionOnRemoteHandles(remoteTargetHandle ->
+ remoteTargetHandle.getTaskViewSimulator().setDrawsBelowRecents(isInAllAppsRegion));
+ }
+
+
private void buildAnimationController() {
if (!canCreateNewOrUpdateExistingLauncherTransitionController()) {
return;
@@ -792,10 +826,15 @@
@Override
public WindowInsets onApplyWindowInsets(View view, WindowInsets windowInsets) {
WindowInsets result = view.onApplyWindowInsets(windowInsets);
+ // Don't rebuild animation when we are animating the IME, because it will cause a loop
+ // where the insets change -> animation changes (updating ime) -> insets change -> ...
+ if (windowInsets.isVisible(WindowInsets.Type.ime())) {
+ return result;
+ }
buildAnimationController();
// Reapply the current shift to ensure it takes new insets into account, e.g. when long
// pressing to stash taskbar without moving the finger.
- updateFinalShift();
+ onCurrentShiftUpdated();
return result;
}
@@ -822,7 +861,8 @@
*/
@UiThread
@Override
- public void updateFinalShift() {
+ public void onCurrentShiftUpdated() {
+ setIsInAllAppsRegion(mCurrentShift.value >= ALL_APPS_SHIFT_THRESHOLD);
updateSysUiFlags(mCurrentShift.value);
applyScrollAndTransform();
@@ -1085,6 +1125,9 @@
}
switch (endTarget) {
+ case ALL_APPS:
+ mStateCallback.setState(STATE_SETTLED_ON_ALL_APPS | STATE_CAPTURE_SCREENSHOT);
+ break;
case HOME:
mStateCallback.setState(STATE_SCALED_CONTROLLER_HOME | STATE_CAPTURE_SCREENSHOT);
// Notify the SysUI to use fade-in animation when entering PiP
@@ -1173,6 +1216,9 @@
final boolean willGoToNewTask =
isScrollingToNewTask() && Math.abs(velocity.x) > Math.abs(endVelocity);
final boolean isSwipeUp = endVelocity < 0;
+ if (mIsInAllAppsRegion) {
+ return isSwipeUp ? ALL_APPS : LAST_TASK;
+ }
if (!isSwipeUp) {
final boolean isCenteredOnNewTask =
mRecentsView.getDestinationPage() != mRecentsView.getRunningTaskIndex();
@@ -1188,7 +1234,9 @@
// Fully gestural mode.
final boolean isFlingX = Math.abs(velocity.x) > mContext.getResources()
.getDimension(R.dimen.quickstep_fling_threshold_speed);
- if (isScrollingToNewTask && isFlingX) {
+ if (mIsInAllAppsRegion) {
+ return ALL_APPS;
+ } else if (isScrollingToNewTask && isFlingX) {
// Flinging towards new task takes precedence over mIsMotionPaused (which only
// checks y-velocity).
return NEW_TASK;
@@ -1236,7 +1284,8 @@
mGestureState.setEndTarget(endTarget, false /* isAtomic */);
mAnimationFactory.setEndTarget(endTarget);
- float endShift = endTarget.isLauncher ? 1 : 0;
+ float endShift = endTarget == ALL_APPS ? mDragLengthFactor
+ : endTarget.isLauncher ? 1 : 0;
final float startShift;
if (!isFling) {
long expectedDuration = Math.abs(Math.round((endShift - currentShift)
@@ -1793,6 +1842,12 @@
reset();
}
+ @UiThread
+ private void finishCurrentTransitionToAllApps() {
+ finishCurrentTransitionToHome();
+ reset();
+ }
+
private void reset() {
mStateCallback.setStateOnUiThread(STATE_HANDLER_INVALIDATED);
if (mActivity != null) {
@@ -1926,7 +1981,8 @@
private boolean updateThumbnail(int runningTaskId, boolean refreshView) {
boolean finishTransitionPosted = false;
final TaskView taskView;
- if (mGestureState.getEndTarget() == HOME || mGestureState.getEndTarget() == NEW_TASK) {
+ if (mGestureState.getEndTarget() == HOME || mGestureState.getEndTarget() == NEW_TASK
+ || mGestureState.getEndTarget() == ALL_APPS) {
// Capture the screenshot before finishing the transition to home or quickswitching to
// ensure it's taken in the correct orientation, but no need to update the thumbnail.
taskView = null;
@@ -2072,7 +2128,7 @@
private void onRecentsViewScroll() {
if (moveWindowWithRecentsScroll()) {
- updateFinalShift();
+ onCurrentShiftUpdated();
}
}
diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
index fd7aa58..60083c6 100644
--- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
@@ -187,6 +187,9 @@
public abstract boolean allowMinimizeSplitScreen();
+ /** @return whether to allow going to All Apps from Overview. */
+ public abstract boolean allowAllAppsFromOverview();
+
public boolean deferStartingActivity(RecentsAnimationDeviceState deviceState, MotionEvent ev) {
return deviceState.isInDeferredGestureRegion(ev) || deviceState.isImeRenderingNavButtons()
|| isTrackpadMultiFingerSwipe(ev);
diff --git a/quickstep/src/com/android/quickstep/FallbackActivityInterface.java b/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
index 8bb189a..5c96000 100644
--- a/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
@@ -139,6 +139,11 @@
}
@Override
+ public boolean allowAllAppsFromOverview() {
+ return false;
+ }
+
+ @Override
public boolean deferStartingActivity(RecentsAnimationDeviceState deviceState, MotionEvent ev) {
// In non-gesture mode, user might be clicking on the home button which would directly
// start the home activity instead of going through recents. In that case, defer starting
@@ -196,6 +201,7 @@
case LAST_TASK:
return BACKGROUND_APP;
case HOME:
+ case ALL_APPS:
default:
return HOME;
}
diff --git a/quickstep/src/com/android/quickstep/GestureState.java b/quickstep/src/com/android/quickstep/GestureState.java
index 02f9f57..9d7ccb4 100644
--- a/quickstep/src/com/android/quickstep/GestureState.java
+++ b/quickstep/src/com/android/quickstep/GestureState.java
@@ -18,11 +18,13 @@
import static com.android.launcher3.MotionEventsUtils.isTrackpadFourFingerSwipe;
import static com.android.launcher3.MotionEventsUtils.isTrackpadMultiFingerSwipe;
import static com.android.launcher3.MotionEventsUtils.isTrackpadThreeFingerSwipe;
+import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_ALLAPPS;
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_HOME;
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_OVERVIEW;
import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.SET_END_TARGET;
+import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.SET_END_TARGET_ALL_APPS;
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.SET_END_TARGET_HOME;
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.SET_END_TARGET_NEW_TASK;
@@ -68,7 +70,9 @@
GestureStateProto.GestureEndTarget.NEW_TASK),
LAST_TASK(false, LAUNCHER_STATE_BACKGROUND, true,
- GestureStateProto.GestureEndTarget.LAST_TASK);
+ GestureStateProto.GestureEndTarget.LAST_TASK),
+
+ ALL_APPS(true, LAUNCHER_STATE_ALLAPPS, false, GestureStateProto.GestureEndTarget.ALL_APPS);
GestureEndTarget(boolean isLauncher, int containerType, boolean recentsAttachedToAppWindow,
GestureStateProto.GestureEndTarget protoEndTarget) {
@@ -385,6 +389,9 @@
case NEW_TASK:
ActiveGestureLog.INSTANCE.trackEvent(SET_END_TARGET_NEW_TASK);
break;
+ case ALL_APPS:
+ ActiveGestureLog.INSTANCE.trackEvent(SET_END_TARGET_ALL_APPS);
+ break;
case LAST_TASK:
case RECENTS:
default:
diff --git a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
index ea9f032..0e0b022 100644
--- a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
@@ -15,6 +15,7 @@
*/
package com.android.quickstep;
+import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.BACKGROUND_APP;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
@@ -39,6 +40,7 @@
import com.android.launcher3.LauncherInitListener;
import com.android.launcher3.LauncherState;
import com.android.launcher3.anim.PendingAnimation;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.statehandlers.DepthController;
import com.android.launcher3.statehandlers.DesktopVisibilityController;
import com.android.launcher3.statemanager.StateManager;
@@ -264,6 +266,11 @@
}
@Override
+ public boolean allowAllAppsFromOverview() {
+ return FeatureFlags.ENABLE_ALL_APPS_FROM_OVERVIEW.get();
+ }
+
+ @Override
public boolean isInLiveTileMode() {
Launcher launcher = getCreatedActivity();
return launcher != null && launcher.getStateManager().getState() == OVERVIEW &&
@@ -347,6 +354,8 @@
case NEW_TASK:
case LAST_TASK:
return BACKGROUND_APP;
+ case ALL_APPS:
+ return ALL_APPS;
case HOME:
default:
return NORMAL;
diff --git a/quickstep/src/com/android/quickstep/RotationTouchHelper.java b/quickstep/src/com/android/quickstep/RotationTouchHelper.java
index 66d1e1e..8626c40 100644
--- a/quickstep/src/com/android/quickstep/RotationTouchHelper.java
+++ b/quickstep/src/com/android/quickstep/RotationTouchHelper.java
@@ -350,7 +350,8 @@
enableMultipleRegions(true);
}
activityInterface.onExitOverview(this, mExitOverviewRunnable);
- } else if (endTarget == GestureState.GestureEndTarget.HOME) {
+ } else if (endTarget == GestureState.GestureEndTarget.HOME
+ || endTarget == GestureState.GestureEndTarget.ALL_APPS) {
enableMultipleRegions(false);
} else if (endTarget == GestureState.GestureEndTarget.NEW_TASK) {
if (mOrientationTouchTransformer.getQuickStepStartingRotation() == -1) {
diff --git a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
index f913aff..1b4fdc4 100644
--- a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
+++ b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
@@ -67,7 +67,7 @@
// 0 => preview snapShot is completely visible, and hotseat is completely translated down
// 1 => preview snapShot is completely aligned with the recents view and hotseat is completely
// visible.
- protected final AnimatedFloat mCurrentShift = new AnimatedFloat(this::updateFinalShift);
+ protected final AnimatedFloat mCurrentShift = new AnimatedFloat(this::onCurrentShiftUpdated);
protected float mCurrentDisplacement;
// The distance needed to drag to reach the task size in recents.
@@ -148,7 +148,7 @@
* Called when the value of {@link #mCurrentShift} changes
*/
@UiThread
- public abstract void updateFinalShift();
+ public abstract void onCurrentShiftUpdated();
protected PagedOrientationHandler getOrientationHandler() {
// OrientationHandler should be independent of remote target, can directly take one
diff --git a/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java
index b3243ff..a8af05e 100644
--- a/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java
@@ -309,7 +309,7 @@
}
@Override
- public void updateFinalShift() {
+ public void onCurrentShiftUpdated() {
mRemoteTargetHandles[0].getPlaybackController()
.setProgress(mCurrentShift.value, mDragLengthFactor);
mRemoteTargetHandles[0].getTaskViewSimulator().apply(
diff --git a/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java b/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java
index 6eadd2b..8335523 100644
--- a/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java
+++ b/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java
@@ -33,10 +33,11 @@
*/
public enum GestureEvent {
MOTION_DOWN, MOTION_UP, MOTION_MOVE, SET_END_TARGET, SET_END_TARGET_HOME,
- SET_END_TARGET_NEW_TASK, ON_SETTLED_ON_END_TARGET, START_RECENTS_ANIMATION,
- FINISH_RECENTS_ANIMATION, CANCEL_RECENTS_ANIMATION, SET_ON_PAGE_TRANSITION_END_CALLBACK,
- CANCEL_CURRENT_ANIMATION, CLEANUP_SCREENSHOT, SCROLLER_ANIMATION_ABORTED, TASK_APPEARED,
- EXPECTING_TASK_APPEARED, FLAG_USING_OTHER_ACTIVITY_INPUT_CONSUMER, LAUNCHER_DESTROYED,
+ SET_END_TARGET_NEW_TASK, SET_END_TARGET_ALL_APPS, ON_SETTLED_ON_END_TARGET,
+ START_RECENTS_ANIMATION, FINISH_RECENTS_ANIMATION, CANCEL_RECENTS_ANIMATION,
+ SET_ON_PAGE_TRANSITION_END_CALLBACK, CANCEL_CURRENT_ANIMATION, CLEANUP_SCREENSHOT,
+ SCROLLER_ANIMATION_ABORTED, TASK_APPEARED, EXPECTING_TASK_APPEARED,
+ FLAG_USING_OTHER_ACTIVITY_INPUT_CONSUMER, LAUNCHER_DESTROYED,
/**
* These GestureEvents are specifically associated to state flags that get set in
@@ -220,6 +221,7 @@
case MOTION_DOWN:
case SET_END_TARGET:
case SET_END_TARGET_HOME:
+ case SET_END_TARGET_ALL_APPS:
case SET_END_TARGET_NEW_TASK:
case START_RECENTS_ANIMATION:
case SET_ON_PAGE_TRANSITION_END_CALLBACK:
diff --git a/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java b/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java
index baca76c..a92ab2a 100644
--- a/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java
+++ b/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java
@@ -17,9 +17,11 @@
import static com.android.launcher3.anim.Interpolators.DEACCEL;
import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.quickstep.AbsSwipeUpHandler.ALL_APPS_SHIFT_THRESHOLD;
import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY;
import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_TRANSLATION;
+import android.animation.AnimatorSet;
import android.animation.TimeInterpolator;
import android.content.Context;
import android.graphics.Matrix;
@@ -32,11 +34,15 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.PendingAnimation;
+import com.android.launcher3.statemanager.StateManager;
+import com.android.launcher3.statemanager.StatefulActivity;
+import com.android.launcher3.states.StateAnimationConfig;
+import com.android.launcher3.touch.AllAppsSwipeController;
import com.android.launcher3.touch.PagedOrientationHandler;
-import com.android.quickstep.LauncherActivityInterface;
import com.android.quickstep.views.RecentsView;
/**
@@ -49,7 +55,9 @@
private enum RecentsResistanceParams {
FROM_APP(0.75f, 0.5f, 1f, false),
+ FROM_APP_TO_ALL_APPS(0.75f, 0.5f, 0.8f, false),
FROM_APP_TABLET(1f, 0.7f, 1f, true),
+ FROM_APP_TO_ALL_APPS_TABLET(1f, 0.5f, 0.5f, false),
FROM_OVERVIEW(1f, 0.75f, 0.5f, false);
RecentsResistanceParams(float scaleStartResist, float scaleMaxResist,
@@ -86,6 +94,8 @@
private static final TimeInterpolator RECENTS_SCALE_RESIST_INTERPOLATOR = DEACCEL;
private static final TimeInterpolator RECENTS_TRANSLATE_RESIST_INTERPOLATOR = LINEAR;
+ private static final Rect TEMP_RECT = new Rect();
+
private final AnimatorPlaybackController mNormalController;
private final AnimatorPlaybackController mResistanceController;
@@ -145,10 +155,42 @@
scaleProperty, translationTarget, translationProperty);
PendingAnimation resistAnim = createRecentsResistanceAnim(params);
+ // Apply All Apps animation during the resistance animation.
+ if (recentsOrientedState.getActivityInterface().allowAllAppsFromOverview()) {
+ StatefulActivity activity =
+ recentsOrientedState.getActivityInterface().getCreatedActivity();
+ if (activity != null) {
+ StateManager<LauncherState> stateManager = activity.getStateManager();
+ if (stateManager.isInStableState(LauncherState.BACKGROUND_APP)
+ && stateManager.isInTransition()) {
+
+ // Calculate the resistance progress threshold where All Apps will trigger.
+ float threshold = getAllAppsThreshold(context, recentsOrientedState, dp);
+
+ StateAnimationConfig config = new StateAnimationConfig();
+ AllAppsSwipeController.applyOverviewToAllAppsAnimConfig(dp, config, threshold);
+ AnimatorSet allAppsAnimator = stateManager.createAnimationToNewWorkspace(
+ LauncherState.ALL_APPS, config).getTarget();
+ resistAnim.add(allAppsAnimator);
+ }
+ }
+ }
+
AnimatorPlaybackController resistanceController = resistAnim.createPlaybackController();
return new AnimatorControllerWithResistance(normalController, resistanceController);
}
+ private static float getAllAppsThreshold(Context context,
+ RecentsOrientedState recentsOrientedState, DeviceProfile dp) {
+ int transitionDragLength =
+ recentsOrientedState.getActivityInterface().getSwipeUpDestinationAndLength(
+ dp, context, TEMP_RECT,
+ recentsOrientedState.getOrientationHandler());
+ float dragLengthFactor = (float) dp.heightPx / transitionDragLength;
+ // -1s are because 0-1 is reserved for the normal transition.
+ return (ALL_APPS_SHIFT_THRESHOLD - 1) / (dragLengthFactor - 1);
+ }
+
/**
* Creates the resistance animation for {@link #createForRecents}, or can be used separately
* when starting from recents, i.e. {@link #createRecentsResistanceFromOverviewAnim}.
@@ -158,8 +200,8 @@
Rect startRect = new Rect();
PagedOrientationHandler orientationHandler = params.recentsOrientedState
.getOrientationHandler();
- LauncherActivityInterface.INSTANCE.calculateTaskSize(params.context, params.dp, startRect,
- orientationHandler);
+ params.recentsOrientedState.getActivityInterface()
+ .calculateTaskSize(params.context, params.dp, startRect, orientationHandler);
long distanceToCover = startRect.bottom;
PendingAnimation resistAnim = params.resistAnim != null
? params.resistAnim
@@ -257,9 +299,15 @@
this.translationTarget = translationTarget;
this.translationProperty = translationProperty;
if (dp.isTablet) {
- resistanceParams = RecentsResistanceParams.FROM_APP_TABLET;
+ resistanceParams =
+ recentsOrientedState.getActivityInterface().allowAllAppsFromOverview()
+ ? RecentsResistanceParams.FROM_APP_TO_ALL_APPS_TABLET
+ : RecentsResistanceParams.FROM_APP_TABLET;
} else {
- resistanceParams = RecentsResistanceParams.FROM_APP;
+ resistanceParams =
+ recentsOrientedState.getActivityInterface().allowAllAppsFromOverview()
+ ? RecentsResistanceParams.FROM_APP_TO_ALL_APPS
+ : RecentsResistanceParams.FROM_APP;
}
}
diff --git a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
index c4ba39a..f6ad692 100644
--- a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
+++ b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
@@ -116,6 +116,7 @@
| FLAG_SWIPE_UP_NOT_RUNNING;
private final Context mContext;
+ private final BaseActivityInterface mActivityInterface;
private final OrientationEventListener mOrientationListener;
private final SettingsCache mSettingsCache;
private final SettingsCache.OnChangeListener mRotationChangeListener =
@@ -135,9 +136,10 @@
* is enabled
* @see #setRotationWatcherEnabled(boolean)
*/
- public RecentsOrientedState(Context context, BaseActivityInterface sizeStrategy,
+ public RecentsOrientedState(Context context, BaseActivityInterface activityInterface,
IntConsumer rotationChangeListener) {
mContext = context;
+ mActivityInterface = activityInterface;
mOrientationListener = new OrientationEventListener(context) {
@Override
public void onOrientationChanged(int degrees) {
@@ -149,7 +151,7 @@
}
};
- mFlags = sizeStrategy.rotationSupportedByActivity
+ mFlags = mActivityInterface.rotationSupportedByActivity
? FLAG_MULTIPLE_ORIENTATION_SUPPORTED_BY_ACTIVITY : 0;
mFlags |= FLAG_SWIPE_UP_NOT_RUNNING;
@@ -157,6 +159,10 @@
initFlags();
}
+ public BaseActivityInterface getActivityInterface() {
+ return mActivityInterface;
+ }
+
/**
* Sets the device profile for the current state.
*/
diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
index c165acc..697a1c1 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -17,6 +17,7 @@
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.CLEAR_ALL_BUTTON;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
@@ -129,7 +130,7 @@
@Override
public void onStateTransitionComplete(LauncherState finalState) {
- if (finalState == NORMAL || finalState == SPRING_LOADED) {
+ if (finalState == NORMAL || finalState == SPRING_LOADED || finalState == ALL_APPS) {
// Clean-up logic that occurs when recents is no longer in use/visible.
reset();
}
diff --git a/res/layout/launcher.xml b/res/layout/launcher.xml
index 039d8d3..a709fbc 100644
--- a/res/layout/launcher.xml
+++ b/res/layout/launcher.xml
@@ -69,15 +69,15 @@
android:background="@android:color/transparent" />
<include
+ android:id="@+id/overview_panel"
+ layout="@layout/overview_panel" />
+
+ <include
android:id="@+id/apps_view"
layout="@layout/all_apps"
android:layout_width="match_parent"
android:layout_height="match_parent" />
- <include
- android:id="@+id/overview_panel"
- layout="@layout/overview_panel" />
-
</com.android.launcher3.dragndrop.DragLayer>
</com.android.launcher3.LauncherRootView>
diff --git a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
index 470a75c..01a4876 100644
--- a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
@@ -40,6 +40,7 @@
import android.os.Process;
import android.os.UserManager;
import android.util.AttributeSet;
+import android.util.FloatProperty;
import android.util.Log;
import android.util.SparseArray;
import android.util.TypedValue;
@@ -101,6 +102,20 @@
OnDeviceProfileChangeListener, PersonalWorkSlidingTabStrip.OnActivePageChangedListener,
ScrimView.ScrimDrawingController {
+
+ public static final FloatProperty<ActivityAllAppsContainerView<?>> BOTTOM_SHEET_ALPHA =
+ new FloatProperty<>("bottomSheetAlpha") {
+ @Override
+ public Float get(ActivityAllAppsContainerView<?> containerView) {
+ return containerView.mBottomSheetAlpha;
+ }
+
+ @Override
+ public void setValue(ActivityAllAppsContainerView<?> containerView, float v) {
+ containerView.setBottomSheetAlpha(v);
+ }
+ };
+
public static final float PULL_MULTIPLIER = .02f;
public static final float FLING_VELOCITY_MULTIPLIER = 1200f;
protected static final String BUNDLE_KEY_CURRENT_PAGE = "launcher.allapps.current_page";
@@ -159,6 +174,8 @@
private ScrimView mScrimView;
private int mHeaderColor;
private int mBottomSheetBackgroundColor;
+ private float mBottomSheetAlpha = 1f;
+ private boolean mForceBottomSheetVisible;
private int mTabsProtectionAlpha;
@Nullable private AllAppsTransitionController mAllAppsTransitionController;
@@ -258,7 +275,7 @@
final TypedValue value = new TypedValue();
getContext().getTheme().resolveAttribute(android.R.attr.colorBackground, value, true);
mBottomSheetBackgroundColor = value.data;
- updateBackground(mActivityContext.getDeviceProfile());
+ updateBackgroundVisibility(mActivityContext.getDeviceProfile());
mSearchUiManager.initializeSearch(this);
}
@@ -282,6 +299,16 @@
return mBottomSheetBackground;
}
+ /**
+ * Temporarily force the bottom sheet to be visible on non-tablets.
+ *
+ * @param force {@code true} means bottom sheet will be visible on phones until {@code reset()}.
+ **/
+ public void forceBottomSheetVisible(boolean force) {
+ mForceBottomSheetVisible = force;
+ updateBackgroundVisibility(mActivityContext.getDeviceProfile());
+ }
+
public View getSearchView() {
return mSearchContainer;
}
@@ -408,6 +435,7 @@
if (mHeader != null && mHeader.getVisibility() == VISIBLE) {
mHeader.reset(animate);
}
+ forceBottomSheetVisible(false);
// Reset the base recycler view after transitioning home.
updateHeaderScroll(0);
if (exitSearch) {
@@ -830,7 +858,7 @@
holder.mRecyclerView.getRecycledViewPool().clear();
}
}
- updateBackground(dp);
+ updateBackgroundVisibility(dp);
int navBarScrimColor = Themes.getNavBarScrimColor(mActivityContext);
if (mNavBarScrimPaint.getColor() != navBarScrimColor) {
@@ -839,13 +867,19 @@
}
}
- protected void updateBackground(DeviceProfile deviceProfile) {
- mBottomSheetBackground.setVisibility(deviceProfile.isTablet ? View.VISIBLE : View.GONE);
+ protected void updateBackgroundVisibility(DeviceProfile deviceProfile) {
+ boolean visible = deviceProfile.isTablet || mForceBottomSheetVisible;
+ mBottomSheetBackground.setVisibility(visible ? View.VISIBLE : View.GONE);
// Note: For tablets, the opaque background and header protection are added in drawOnScrim.
// For the taskbar entrypoint, the scrim is drawn differently, so a static background is
// added in TaskbarAllAppsContainerView and header protection is not yet supported.
}
+ private void setBottomSheetAlpha(float alpha) {
+ // Bottom sheet alpha is always 1 for tablets.
+ mBottomSheetAlpha = mActivityContext.getDeviceProfile().isTablet ? 1f : alpha;
+ }
+
private void onAppsUpdated() {
mHasWorkApps = Stream.of(mAllAppsStore.getApps()).anyMatch(mWorkManager.getMatcher());
if (TestProtocol.sDebugTracing) {
@@ -1148,8 +1182,8 @@
@Override
public void drawOnScrimWithScale(Canvas canvas, float scale) {
- final boolean isTablet = mActivityContext.getDeviceProfile().isTablet;
final View panel = mBottomSheetBackground;
+ final boolean hasBottomSheet = panel.getVisibility() == VISIBLE;
final float translationY = ((View) panel.getParent()).getTranslationY();
final float horizontalScaleOffset = (1 - scale) * panel.getWidth() / 2;
@@ -1160,8 +1194,9 @@
final float leftWithScale = panel.getLeft() + horizontalScaleOffset;
final float rightWithScale = panel.getRight() - horizontalScaleOffset;
// Draw full background panel for tablets.
- if (isTablet) {
+ if (hasBottomSheet) {
mHeaderPaint.setColor(mBottomSheetBackgroundColor);
+ mHeaderPaint.setAlpha((int) (255 * mBottomSheetAlpha));
mTmpRectF.set(
leftWithScale,
@@ -1192,7 +1227,7 @@
final float headerBottomOffset = (getVisibleContainerView().getHeight() * (1 - scale) / 2);
final float headerBottomWithScaleOnPhone = headerBottomNoScale * scale + headerBottomOffset;
final FloatingHeaderView headerView = getFloatingHeaderView();
- if (isTablet) {
+ if (hasBottomSheet) {
// Start adding header protection if search bar or tabs will attach to the top.
if (!FeatureFlags.ENABLE_FLOATING_SEARCH_BAR.get() || mUsingTabs) {
mTmpRectF.set(
@@ -1219,12 +1254,12 @@
}
float left = 0f;
float right = canvas.getWidth();
- if (isTablet) {
+ if (hasBottomSheet) {
left = mBottomSheetBackground.getLeft() + horizontalScaleOffset;
right = mBottomSheetBackground.getRight() - horizontalScaleOffset;
}
- final float tabTopWithScale = isTablet
+ final float tabTopWithScale = hasBottomSheet
? headerBottomWithScaleOnTablet
: headerBottomWithScaleOnPhone;
final float tabBottomWithScale = tabTopWithScale + tabsHeight * scale;
@@ -1263,7 +1298,7 @@
* Returns a view that denotes the visible part of all apps container view.
*/
public View getVisibleContainerView() {
- return mActivityContext.getDeviceProfile().isTablet ? mBottomSheetBackground : this;
+ return mBottomSheetBackground.getVisibility() == VISIBLE ? mBottomSheetBackground : this;
}
protected void onInitializeRecyclerView(RecyclerView rv) {
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index 4d1006a..d4f152a 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -21,8 +21,10 @@
import static com.android.launcher3.LauncherState.ALL_APPS_CONTENT;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.anim.Interpolators.DEACCEL_1_7;
+import static com.android.launcher3.anim.Interpolators.INSTANT;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.anim.PropertySetter.NO_ANIM_PROPERTY_SETTER;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_BOTTOM_SHEET_FADE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_VERTICAL_PROGRESS;
import static com.android.launcher3.util.SystemUiController.FLAG_DARK_NAV;
@@ -410,8 +412,12 @@
setter.setFloat(getAppsViewPullbackAlpha(), MultiPropertyFactory.MULTI_PROPERTY_VALUE,
hasAllAppsContent ? 1 : 0, allAppsFade);
- boolean shouldProtectHeader =
- ALL_APPS == state || mLauncher.getStateManager().getState() == ALL_APPS;
+ setter.setFloat(mLauncher.getAppsView(),
+ ActivityAllAppsContainerView.BOTTOM_SHEET_ALPHA, hasAllAppsContent ? 1 : 0,
+ config.getInterpolator(ANIM_ALL_APPS_BOTTOM_SHEET_FADE, INSTANT));
+
+ boolean shouldProtectHeader = !config.hasAnimationFlag(StateAnimationConfig.SKIP_SCRIM)
+ && (ALL_APPS == state || mLauncher.getStateManager().getState() == ALL_APPS);
mScrimView.setDrawingController(shouldProtectHeader ? mAppsView : null);
}
diff --git a/src/com/android/launcher3/allapps/SecondaryLauncherAllAppsContainerView.java b/src/com/android/launcher3/allapps/SecondaryLauncherAllAppsContainerView.java
index 684e98e..d7f5463 100644
--- a/src/com/android/launcher3/allapps/SecondaryLauncherAllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/SecondaryLauncherAllAppsContainerView.java
@@ -41,7 +41,7 @@
}
@Override
- protected void updateBackground(DeviceProfile deviceProfile) {}
+ protected void updateBackgroundVisibility(DeviceProfile deviceProfile) {}
@Override
public boolean isInAllApps() {
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index cdebe44..0ba4dee 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -115,9 +115,13 @@
// TODO(Block 4): Cleanup flags
public static final BooleanFlag ENABLE_FLOATING_SEARCH_BAR =
- getReleaseFlag(270390286, "ENABLE_FLOATING_SEARCH_BAR", DISABLED,
+ getReleaseFlag(268388460, "ENABLE_FLOATING_SEARCH_BAR", DISABLED,
"Keep All Apps search bar at the bottom (but above keyboard if open)");
+ public static final BooleanFlag ENABLE_ALL_APPS_FROM_OVERVIEW =
+ getDebugFlag(275132633, "ENABLE_ALL_APPS_FROM_OVERVIEW", DISABLED,
+ "Allow entering All Apps from Overview (e.g. long swipe up from app)");
+
public static final BooleanFlag ENABLE_SHOW_KEYBOARD_OPTION_IN_ALL_APPS = getReleaseFlag(
270394468, "ENABLE_SHOW_KEYBOARD_OPTION_IN_ALL_APPS", ENABLED,
"Enable option to show keyboard when going to all-apps");
diff --git a/src/com/android/launcher3/states/StateAnimationConfig.java b/src/com/android/launcher3/states/StateAnimationConfig.java
index 54735f0..d1e816b 100644
--- a/src/com/android/launcher3/states/StateAnimationConfig.java
+++ b/src/com/android/launcher3/states/StateAnimationConfig.java
@@ -65,7 +65,8 @@
ANIM_OVERVIEW_ACTIONS_FADE,
ANIM_WORKSPACE_PAGE_TRANSLATE_X,
ANIM_OVERVIEW_SPLIT_SELECT_FLOATING_TASK_TRANSLATE_OFFSCREEN,
- ANIM_OVERVIEW_SPLIT_SELECT_INSTRUCTIONS_FADE
+ ANIM_OVERVIEW_SPLIT_SELECT_INSTRUCTIONS_FADE,
+ ANIM_ALL_APPS_BOTTOM_SHEET_FADE
})
@Retention(RetentionPolicy.SOURCE)
public @interface AnimType {}
@@ -88,8 +89,9 @@
public static final int ANIM_WORKSPACE_PAGE_TRANSLATE_X = 15;
public static final int ANIM_OVERVIEW_SPLIT_SELECT_FLOATING_TASK_TRANSLATE_OFFSCREEN = 17;
public static final int ANIM_OVERVIEW_SPLIT_SELECT_INSTRUCTIONS_FADE = 18;
+ public static final int ANIM_ALL_APPS_BOTTOM_SHEET_FADE = 19;
- private static final int ANIM_TYPES_COUNT = 19;
+ private static final int ANIM_TYPES_COUNT = 20;
protected final Interpolator[] mInterpolators = new Interpolator[ANIM_TYPES_COUNT];
diff --git a/src/com/android/launcher3/touch/AllAppsSwipeController.java b/src/com/android/launcher3/touch/AllAppsSwipeController.java
index a53751f..d028f24 100644
--- a/src/com/android/launcher3/touch/AllAppsSwipeController.java
+++ b/src/com/android/launcher3/touch/AllAppsSwipeController.java
@@ -23,6 +23,8 @@
import static com.android.launcher3.anim.Interpolators.FINAL_FRAME;
import static com.android.launcher3.anim.Interpolators.INSTANT;
import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.anim.Interpolators.clampToProgress;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_BOTTOM_SHEET_FADE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_DEPTH;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_HOTSEAT_FADE;
@@ -32,11 +34,14 @@
import static com.android.launcher3.states.StateAnimationConfig.ANIM_VERTICAL_PROGRESS;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_FADE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_SCALE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_TRANSLATE;
+import static com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW;
import android.view.MotionEvent;
import android.view.animation.Interpolator;
import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.anim.Interpolators;
@@ -269,4 +274,44 @@
: ALL_APPS_VERTICAL_PROGRESS_ATOMIC);
}
}
+
+ /**
+ * Applies Animation config values for transition from overview to all apps.
+ *
+ * @param threshold progress at which all apps will open upon release
+ */
+ public static void applyOverviewToAllAppsAnimConfig(
+ DeviceProfile deviceProfile, StateAnimationConfig config, float threshold) {
+ config.userControlled = true;
+ config.animFlags = SKIP_OVERVIEW;
+ if (deviceProfile.isTablet) {
+ config.setInterpolator(ANIM_ALL_APPS_FADE, INSTANT);
+ config.setInterpolator(ANIM_SCRIM_FADE, ALL_APPS_SCRIM_RESPONDER);
+ // The fact that we end on Workspace is not very ideal, but since we do, fade it in at
+ // the end of the transition. Don't scale/translate it.
+ config.setInterpolator(ANIM_WORKSPACE_FADE, clampToProgress(LINEAR, 0.8f, 1));
+ config.setInterpolator(ANIM_WORKSPACE_SCALE, INSTANT);
+ config.setInterpolator(ANIM_WORKSPACE_TRANSLATE, INSTANT);
+ } else {
+ // Remove scrim for this transition.
+ config.setInterpolator(ANIM_SCRIM_FADE, progress -> 0);
+
+ // For now, pop the background panel in at full opacity at the threshold.
+ config.setInterpolator(ANIM_ALL_APPS_BOTTOM_SHEET_FADE,
+ thresholdInterpolator(threshold, INSTANT));
+
+ // Fade the apps in when the scrim normally does, so it's apparent sooner what is
+ // happening (in this case we are fading them on top of the background panel).
+ config.setInterpolator(ANIM_ALL_APPS_FADE,
+ thresholdInterpolator(threshold, SCRIM_FADE_MANUAL));
+
+ config.setInterpolator(ANIM_VERTICAL_PROGRESS,
+ thresholdInterpolator(threshold, ALL_APPS_VERTICAL_PROGRESS_MANUAL));
+ }
+ }
+
+ /** Creates an interpolator that is 0 until the threshold, then follows given interpolator. */
+ private static Interpolator thresholdInterpolator(float threshold, Interpolator interpolator) {
+ return progress -> progress <= threshold ? 0 : interpolator.getInterpolation(progress);
+ }
}