From 830e4b7ce0adbed7e7303bcf1a93465904b405de Mon Sep 17 00:00:00 2001 From: Andy Wickham Date: Wed, 19 Apr 2023 07:08:44 -0700 Subject: [PATCH] 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 --- protos/launcher_trace.proto | 1 + .../android/quickstep/AbsSwipeUpHandler.java | 74 ++++++++++++++++--- .../quickstep/BaseActivityInterface.java | 3 + .../quickstep/FallbackActivityInterface.java | 6 ++ .../com/android/quickstep/GestureState.java | 9 ++- .../quickstep/LauncherActivityInterface.java | 9 +++ .../quickstep/RotationTouchHelper.java | 3 +- .../quickstep/SwipeUpAnimationLogic.java | 4 +- .../SwipeUpGestureTutorialController.java | 2 +- .../util/ActiveGestureErrorDetector.java | 10 ++- .../AnimatorControllerWithResistance.java | 58 +++++++++++++-- .../quickstep/util/RecentsOrientedState.java | 10 ++- .../quickstep/views/LauncherRecentsView.java | 3 +- res/layout/launcher.xml | 8 +- .../allapps/ActivityAllAppsContainerView.java | 55 +++++++++++--- .../allapps/AllAppsTransitionController.java | 10 ++- ...SecondaryLauncherAllAppsContainerView.java | 2 +- .../launcher3/config/FeatureFlags.java | 6 +- .../states/StateAnimationConfig.java | 6 +- .../touch/AllAppsSwipeController.java | 45 +++++++++++ 20 files changed, 278 insertions(+), 46 deletions(-) diff --git a/protos/launcher_trace.proto b/protos/launcher_trace.proto index 65fcfe512b..e5a86a0108 100644 --- a/protos/launcher_trace.proto +++ b/protos/launcher_trace.proto @@ -63,5 +63,6 @@ message GestureStateProto { 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 d64347f977..b7a29e0034 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.Executors.UI_HELPER_EXECUTOR; 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 @@ public abstract class AbsSwipeUpHandler, private static final ArrayList 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 mActivityInterface; protected final InputConsumerProxy mInputConsumerProxy; protected final ActivityInitListener mActivityInitListener; @@ -247,6 +251,8 @@ public abstract class AbsSwipeUpHandler, 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 @@ public abstract class AbsSwipeUpHandler, private boolean mGestureStarted; private boolean mLogDirectionUpOrLeft = true; private boolean mIsLikelyToStartNewTask; + private boolean mIsInAllAppsRegion; private final long mTouchTimeMs; private long mLauncherFrameDrawnTime; @@ -432,6 +439,9 @@ public abstract class AbsSwipeUpHandler, 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 @@ public abstract class AbsSwipeUpHandler, maybeUpdateRecentsAttachedState(true/* animate */, true/* moveRunningTask */); Optional.ofNullable(mActivityInterface.getTaskbarController()) .ifPresent(TaskbarUIController::startTranslationSpring); - performHapticFeedback(); + if (!mIsInAllAppsRegion) { + performHapticFeedback(); + } } @Override @@ -695,7 +707,7 @@ public abstract class AbsSwipeUpHandler, maybeUpdateRecentsAttachedState(true /* animate */); } - private void maybeUpdateRecentsAttachedState(boolean animate) { + protected void maybeUpdateRecentsAttachedState(boolean animate) { maybeUpdateRecentsAttachedState(animate, false /* moveRunningTask */); } @@ -716,7 +728,9 @@ public abstract class AbsSwipeUpHandler, ? 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 @@ public abstract class AbsSwipeUpHandler, } } + /** + * 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 @@ public abstract class AbsSwipeUpHandler, @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 @@ public abstract class AbsSwipeUpHandler, */ @UiThread @Override - public void updateFinalShift() { + public void onCurrentShiftUpdated() { + setIsInAllAppsRegion(mCurrentShift.value >= ALL_APPS_SHIFT_THRESHOLD); updateSysUiFlags(mCurrentShift.value); applyScrollAndTransform(); @@ -1085,6 +1125,9 @@ public abstract class AbsSwipeUpHandler, } 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 @@ public abstract class AbsSwipeUpHandler, 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 @@ public abstract class AbsSwipeUpHandler, // 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 @@ public abstract class AbsSwipeUpHandler, 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 @@ public abstract class AbsSwipeUpHandler, reset(); } + @UiThread + private void finishCurrentTransitionToAllApps() { + finishCurrentTransitionToHome(); + reset(); + } + private void reset() { mStateCallback.setStateOnUiThread(STATE_HANDLER_INVALIDATED); if (mActivity != null) { @@ -1926,7 +1981,8 @@ public abstract class AbsSwipeUpHandler, 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 @@ public abstract class AbsSwipeUpHandler, 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 fd7aa581b7..60083c67e7 100644 --- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java +++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java @@ -187,6 +187,9 @@ public abstract class BaseActivityInterface 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 @@ public abstract class SwipeUpAnimationLogic implements * 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 b3243ff31f..a8af05e7d8 100644 --- a/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java +++ b/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java @@ -309,7 +309,7 @@ abstract class SwipeUpGestureTutorialController extends TutorialController { } @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 6eadd2bf15..8335523758 100644 --- a/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java +++ b/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java @@ -33,10 +33,11 @@ public class ActiveGestureErrorDetector { */ 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 @@ public class ActiveGestureErrorDetector { 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 baca76cfba..a92ab2a417 100644 --- a/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java +++ b/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java @@ -17,9 +17,11 @@ package com.android.quickstep.util; 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 androidx.annotation.Nullable; 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 @@ public class AnimatorControllerWithResistance { 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 @@ public class AnimatorControllerWithResistance { 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 @@ public class AnimatorControllerWithResistance { 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 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 @@ public class AnimatorControllerWithResistance { 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 @@ public class AnimatorControllerWithResistance { 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 c4ba39a76e..f6ad692fcb 100644 --- a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java +++ b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java @@ -116,6 +116,7 @@ public class RecentsOrientedState implements | 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 @@ public class RecentsOrientedState implements * 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 @@ public class RecentsOrientedState implements } }; - mFlags = sizeStrategy.rotationSupportedByActivity + mFlags = mActivityInterface.rotationSupportedByActivity ? FLAG_MULTIPLE_ORIENTATION_SUPPORTED_BY_ACTIVITY : 0; mFlags |= FLAG_SWIPE_UP_NOT_RUNNING; @@ -157,6 +159,10 @@ public class RecentsOrientedState implements 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 c165accc52..697a1c16b7 100644 --- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java +++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java @@ -17,6 +17,7 @@ package com.android.quickstep.views; 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 @@ public class LauncherRecentsView extends RecentsView + + - - diff --git a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java index 470a75c611..01a4876b49 100644 --- a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java +++ b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java @@ -40,6 +40,7 @@ import android.os.Parcelable; 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 @@ public class ActivityAllAppsContainerView OnDeviceProfileChangeListener, PersonalWorkSlidingTabStrip.OnActivePageChangedListener, ScrimView.ScrimDrawingController { + + public static final FloatProperty> 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 @@ public class ActivityAllAppsContainerView 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 @@ public class ActivityAllAppsContainerView 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 @@ public class ActivityAllAppsContainerView 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 @@ public class ActivityAllAppsContainerView 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 @@ public class ActivityAllAppsContainerView holder.mRecyclerView.getRecycledViewPool().clear(); } } - updateBackground(dp); + updateBackgroundVisibility(dp); int navBarScrimColor = Themes.getNavBarScrimColor(mActivityContext); if (mNavBarScrimPaint.getColor() != navBarScrimColor) { @@ -839,13 +867,19 @@ public class ActivityAllAppsContainerView } } - 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 @@ public class ActivityAllAppsContainerView @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 @@ public class ActivityAllAppsContainerView 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 @@ public class ActivityAllAppsContainerView 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 @@ public class ActivityAllAppsContainerView } 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 @@ public class ActivityAllAppsContainerView * 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 4d1006adba..d4f152aa0a 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; 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 @@ public class AllAppsTransitionController 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 684e98ed96..d7f54635e5 100644 --- a/src/com/android/launcher3/allapps/SecondaryLauncherAllAppsContainerView.java +++ b/src/com/android/launcher3/allapps/SecondaryLauncherAllAppsContainerView.java @@ -41,7 +41,7 @@ public class SecondaryLauncherAllAppsContainerView extends } @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 cdebe440a1..0ba4dee546 100644 --- a/src/com/android/launcher3/config/FeatureFlags.java +++ b/src/com/android/launcher3/config/FeatureFlags.java @@ -115,9 +115,13 @@ public final class FeatureFlags { // 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 54735f0094..d1e816bec6 100644 --- a/src/com/android/launcher3/states/StateAnimationConfig.java +++ b/src/com/android/launcher3/states/StateAnimationConfig.java @@ -65,7 +65,8 @@ public class StateAnimationConfig { 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 class StateAnimationConfig { 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 a53751fcb8..d028f24b0a 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.EMPHASIZED_DECELERATE; 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_SCRIM_FADE; 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 @@ public class AllAppsSwipeController extends AbstractStateChangeTouchController { : 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); + } }