diff --git a/aconfig/launcher.aconfig b/aconfig/launcher.aconfig index 462d947a24..20c524813f 100644 --- a/aconfig/launcher.aconfig +++ b/aconfig/launcher.aconfig @@ -90,6 +90,9 @@ flag { namespace: "launcher" description: "Enables full width two pane widget picker for tablets in landscape and portrait" bug: "315055849" + metadata { + purpose: PURPOSE_BUGFIX + } } flag { @@ -175,9 +178,6 @@ flag { namespace: "launcher" description: "When adding app widget through config activity, directly add it to workspace to reduce flicker" bug: "284236964" - metadata { - purpose: PURPOSE_BUGFIX - } } flag { @@ -206,3 +206,10 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "enable_scaling_reveal_home_animation" + namespace: "launcher" + description: "Enables the Home gesture animation" + bug: "308801666" +} diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java index 569e95a6ce..be532b4047 100644 --- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java +++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java @@ -43,6 +43,7 @@ import static com.android.launcher3.BaseActivity.INVISIBLE_ALL; import static com.android.launcher3.BaseActivity.INVISIBLE_BY_APP_TRANSITIONS; import static com.android.launcher3.BaseActivity.INVISIBLE_BY_PENDING_FLAGS; import static com.android.launcher3.BaseActivity.PENDING_INVISIBLE_BY_WALLPAPER_ANIMATION; +import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation; import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY; import static com.android.launcher3.LauncherAnimUtils.VIEW_BACKGROUND_COLOR; import static com.android.launcher3.LauncherState.ALL_APPS; @@ -215,7 +216,8 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener public static final int TASKBAR_TO_APP_DURATION = 600; // TODO(b/236145847): Tune TASKBAR_TO_HOME_DURATION to 383 after conflict with unlock animation // is solved. - public static final int TASKBAR_TO_HOME_DURATION = 300; + private static final int TASKBAR_TO_HOME_DURATION_FAST = 300; + private static final int TASKBAR_TO_HOME_DURATION_SLOW = 1000; protected static final int CONTENT_SCALE_DURATION = 350; protected static final int CONTENT_SCRIM_DURATION = 350; @@ -1704,6 +1706,14 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener return new Pair(rectFSpringAnim, anim); } + public static int getTaskbarToHomeDuration() { + if (enableScalingRevealHomeAnimation()) { + return TASKBAR_TO_HOME_DURATION_SLOW; + } else { + return TASKBAR_TO_HOME_DURATION_FAST; + } + } + /** * Remote animation runner for animation from the app to Launcher, including recents. */ diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java index 1e861d2fb5..36ce049ebf 100644 --- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java +++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java @@ -191,7 +191,7 @@ public class LauncherTaskbarUIController extends TaskbarUIController { ? TRANSIENT_TASKBAR_TRANSITION_DURATION : (!isVisible ? QuickstepTransitionManager.TASKBAR_TO_APP_DURATION - : QuickstepTransitionManager.TASKBAR_TO_HOME_DURATION)); + : QuickstepTransitionManager.getTaskbarToHomeDuration())); } @Nullable diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java index 8d8371651a..a14e3fd784 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java @@ -193,7 +193,7 @@ public class TaskbarLauncherStateController { updateStateForFlag(FLAG_LAUNCHER_IN_STATE_TRANSITION, true); if (!mShouldDelayLauncherStateAnim) { if (toState == LauncherState.NORMAL) { - applyState(QuickstepTransitionManager.TASKBAR_TO_HOME_DURATION); + applyState(QuickstepTransitionManager.getTaskbarToHomeDuration()); } else { applyState(); } diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java index 577eba661f..b6002e8b76 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java +++ b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java @@ -16,6 +16,7 @@ package com.android.launcher3.uioverrides.states; import static com.android.app.animation.Interpolators.DECELERATE_2; +import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation; import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_ALLAPPS; import android.content.Context; @@ -28,6 +29,7 @@ import com.android.launcher3.R; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.util.Themes; import com.android.launcher3.views.ActivityContext; +import com.android.quickstep.util.BaseDepthController; import com.android.systemui.shared.system.InteractionJankMonitorWrapper; /** @@ -105,8 +107,14 @@ public class AllAppsState extends LauncherState { return context.getDeviceProfile().bottomSheetDepth; } else { // The scrim fades in at approximately 50% of the swipe gesture. - // This means that the depth should be greater than 1, in order to fully zoom out. - return 2f; + if (enableScalingRevealHomeAnimation()) { + // This means that the depth should be twice of what we want, in order to fully zoom + // out during the visible portion of the animation. + return BaseDepthController.DEPTH_60_PERCENT; + } else { + // This means that the depth should be greater than 1, in order to fully zoom out. + return 2f; + } } } diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java b/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java index d11a08bff5..6a25c21a73 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java +++ b/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java @@ -15,6 +15,7 @@ */ package com.android.launcher3.uioverrides.states; +import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation; import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND; import static com.android.quickstep.TaskAnimationManager.ENABLE_SHELL_TRANSITIONS; import static com.android.quickstep.views.DesktopTaskView.isDesktopModeSupported; @@ -26,6 +27,7 @@ import com.android.launcher3.BaseDraggingActivity; import com.android.launcher3.DeviceProfile; import com.android.launcher3.Launcher; import com.android.launcher3.allapps.AllAppsTransitionController; +import com.android.quickstep.util.BaseDepthController; import com.android.quickstep.util.LayoutUtils; import com.android.quickstep.views.RecentsView; @@ -90,13 +92,14 @@ public class BackgroundAppState extends OverviewState { @Override protected float getDepthUnchecked(Context context) { - if (isDesktopModeSupported()) { - if (Launcher.getLauncher(context).areFreeformTasksVisible()) { - // Don't blur the background while freeform tasks are visible - return 0; - } + if (isDesktopModeSupported() && Launcher.getLauncher(context).areFreeformTasksVisible()) { + // Don't blur the background while freeform tasks are visible + return BaseDepthController.DEPTH_0_PERCENT; + } else if (enableScalingRevealHomeAnimation()) { + return BaseDepthController.DEPTH_70_PERCENT; + } else { + return 1f; } - return 1; } @Override diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java index 76502358ea..8c2efc2910 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java +++ b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java @@ -16,6 +16,7 @@ package com.android.launcher3.uioverrides.states; import static com.android.app.animation.Interpolators.DECELERATE_2; +import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation; import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_OVERVIEW; import static com.android.wm.shell.Flags.enableSplitContextual; @@ -29,6 +30,7 @@ import com.android.launcher3.LauncherState; import com.android.launcher3.R; import com.android.launcher3.util.DisplayController; import com.android.launcher3.util.Themes; +import com.android.quickstep.util.BaseDepthController; import com.android.quickstep.util.LayoutUtils; import com.android.quickstep.views.RecentsView; import com.android.quickstep.views.TaskView; @@ -186,8 +188,14 @@ public class OverviewState extends LauncherState { @Override protected float getDepthUnchecked(Context context) { - //TODO revert when b/178661709 is fixed - return SystemProperties.getBoolean("ro.launcher.depth.overview", true) ? 1 : 0; + // TODO(178661709): revert to always scaled + if (enableScalingRevealHomeAnimation()) { + return SystemProperties.getBoolean("ro.launcher.depth.overview", true) + ? BaseDepthController.DEPTH_70_PERCENT + : BaseDepthController.DEPTH_0_PERCENT; + } else { + return SystemProperties.getBoolean("ro.launcher.depth.overview", true) ? 1 : 0; + } } @Override diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java b/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java index 8c92c7db49..b40186819f 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java +++ b/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java @@ -37,7 +37,6 @@ import static com.android.launcher3.LauncherState.HINT_STATE_TWO_BUTTON; import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.LauncherState.OVERVIEW; import static com.android.launcher3.LauncherState.OVERVIEW_SPLIT_SELECT; -import static com.android.launcher3.QuickstepTransitionManager.TASKBAR_TO_HOME_DURATION; import static com.android.launcher3.WorkspaceStateTransitionAnimation.getWorkspaceSpringScaleAnimator; import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE; import static com.android.launcher3.states.StateAnimationConfig.ANIM_DEPTH; @@ -59,6 +58,7 @@ import android.animation.ValueAnimator; import com.android.launcher3.CellLayout; import com.android.launcher3.Hotseat; import com.android.launcher3.LauncherState; +import com.android.launcher3.QuickstepTransitionManager; import com.android.launcher3.Workspace; import com.android.launcher3.states.StateAnimationConfig; import com.android.launcher3.touch.AllAppsSwipeController; @@ -108,7 +108,8 @@ public class QuickstepAtomicAnimationFactory extends // We sync the scrim fade with the taskbar animation duration to avoid any flickers for // taskbar icons disappearing before hotseat icons show up. - float scrimUpperBoundFromSplit = TASKBAR_TO_HOME_DURATION / (float) config.duration; + float scrimUpperBoundFromSplit = + QuickstepTransitionManager.getTaskbarToHomeDuration() / (float) config.duration; config.setInterpolator(ANIM_OVERVIEW_ACTIONS_FADE, clampToProgress(LINEAR, 0, 0.25f)); config.setInterpolator(ANIM_SCRIM_FADE, fromState == OVERVIEW_SPLIT_SELECT @@ -136,7 +137,8 @@ public class QuickstepAtomicAnimationFactory extends // Sync scroll so that it ends before or at the same time as the taskbar animation. if (mActivity.getDeviceProfile().isTaskbarPresent) { - config.duration = Math.min(config.duration, TASKBAR_TO_HOME_DURATION); + config.duration = Math.min( + config.duration, QuickstepTransitionManager.getTaskbarToHomeDuration()); } overview.snapToPage(DEFAULT_PAGE, Math.toIntExact(config.duration)); } else { diff --git a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java index a9d8afcecf..f678bea778 100644 --- a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java +++ b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java @@ -17,6 +17,7 @@ package com.android.quickstep; import static com.android.app.animation.Interpolators.EXAGGERATED_EASE; import static com.android.app.animation.Interpolators.LINEAR; +import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation; import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.Utilities.mapBoundToRange; import static com.android.launcher3.model.data.ItemInfo.NO_MATCHING_ID; @@ -46,6 +47,7 @@ import com.android.launcher3.views.FloatingIconView; import com.android.launcher3.views.FloatingView; import com.android.launcher3.widget.LauncherAppWidgetHostView; import com.android.quickstep.util.RectFSpringAnim; +import com.android.quickstep.util.ScalingWorkspaceRevealAnim; import com.android.quickstep.util.StaggeredWorkspaceAnim; import com.android.quickstep.util.TaskViewSimulator; import com.android.quickstep.views.FloatingWidgetView; @@ -296,9 +298,15 @@ public class LauncherSwipeHandlerV2 extends @Override public void playAtomicAnimation(float velocity) { - new StaggeredWorkspaceAnim(mActivity, velocity, true /* animateOverviewScrim */, - getViewIgnoredInWorkspaceRevealAnimation()) - .start(); + if (enableScalingRevealHomeAnimation()) { + if (mActivity != null) { + new ScalingWorkspaceRevealAnim(mActivity).start(); + } + } else { + new StaggeredWorkspaceAnim(mActivity, velocity, true /* animateOverviewScrim */, + getViewIgnoredInWorkspaceRevealAnimation()) + .start(); + } } } } diff --git a/quickstep/src/com/android/quickstep/util/BaseDepthController.java b/quickstep/src/com/android/quickstep/util/BaseDepthController.java index 99f564c8f7..5d6bb1d2e6 100644 --- a/quickstep/src/com/android/quickstep/util/BaseDepthController.java +++ b/quickstep/src/com/android/quickstep/util/BaseDepthController.java @@ -15,6 +15,8 @@ */ package com.android.quickstep.util; +import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation; + import android.app.WallpaperManager; import android.os.IBinder; import android.util.FloatProperty; @@ -33,6 +35,9 @@ import com.android.systemui.shared.system.BlurUtils; * Utility class for applying depth effect */ public class BaseDepthController { + public static final float DEPTH_0_PERCENT = 0f; + public static final float DEPTH_60_PERCENT = 0.6f; + public static final float DEPTH_70_PERCENT = 0.7f; private static final FloatProperty DEPTH = new FloatProperty("depth") { @@ -127,10 +132,14 @@ public class BaseDepthController { float depth = mDepth; IBinder windowToken = mLauncher.getRootView().getWindowToken(); if (windowToken != null) { - // The API's full zoom-out is three times larger than the zoom-out we apply to the - // icons. To keep the two consistent throughout the animation while keeping Launcher's - // concept of full depth unchanged, we divide the depth by 3 here. - mWallpaperManager.setWallpaperZoomOut(windowToken, depth / 3); + if (enableScalingRevealHomeAnimation()) { + mWallpaperManager.setWallpaperZoomOut(windowToken, depth); + } else { + // The API's full zoom-out is three times larger than the zoom-out we apply to the + // icons. To keep the two consistent throughout the animation while keeping + // Launcher's concept of full depth unchanged, we divide the depth by 3 here. + mWallpaperManager.setWallpaperZoomOut(windowToken, depth / 3); + } } if (!BlurUtils.supportsBlursOnWindows()) { @@ -150,8 +159,15 @@ public class BaseDepthController { boolean hasOpaqueBg = mLauncher.getScrimView().isFullyOpaque(); boolean isSurfaceOpaque = !mHasContentBehindLauncher && hasOpaqueBg && !mPauseBlurs; + float blurAmount; + if (enableScalingRevealHomeAnimation()) { + blurAmount = mapDepthToBlur(depth); + } else { + blurAmount = depth; + } mCurrentBlur = !mCrossWindowBlursEnabled || hasOpaqueBg || mPauseBlurs - ? 0 : (int) (depth * mMaxBlurRadius); + ? 0 : (int) (blurAmount * mMaxBlurRadius); + SurfaceControl.Transaction transaction = new SurfaceControl.Transaction() .setBackgroundBlurRadius(mSurface, mCurrentBlur) .setOpaque(mSurface, isSurfaceOpaque); @@ -197,4 +213,12 @@ public class BaseDepthController { applyDepthAndBlur(); } } + + /** + * Maps depth values to blur amounts as a percentage of the max blur. + * The blur percentage grows linearly with depth, and maxes out at 30% depth. + */ + private static float mapDepthToBlur(float depth) { + return Math.min(3 * depth, 1f); + } } diff --git a/quickstep/src/com/android/quickstep/util/RectFSpringAnim.java b/quickstep/src/com/android/quickstep/util/RectFSpringAnim.java index 245dde26a3..c39056d208 100644 --- a/quickstep/src/com/android/quickstep/util/RectFSpringAnim.java +++ b/quickstep/src/com/android/quickstep/util/RectFSpringAnim.java @@ -15,6 +15,8 @@ */ package com.android.quickstep.util; +import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation; + import static java.lang.annotation.RetentionPolicy.SOURCE; import android.animation.Animator; @@ -104,6 +106,8 @@ public class RectFSpringAnim extends ReleaseCheck { private float mCurrentScaleProgress; private FlingSpringAnim mRectXAnim; private FlingSpringAnim mRectYAnim; + private SpringAnimation mRectXSpring; + private SpringAnimation mRectYSpring; private SpringAnimation mRectScaleAnim; private boolean mAnimsStarted; private boolean mRectXAnimEnded; @@ -166,27 +170,47 @@ public class RectFSpringAnim extends ReleaseCheck { } public void onTargetPositionChanged() { - if (mRectXAnim != null && mRectXAnim.getTargetPosition() != mTargetRect.centerX()) { - mRectXAnim.updatePosition(mCurrentCenterX, mTargetRect.centerX()); - } + if (enableScalingRevealHomeAnimation()) { + if (mRectXSpring != null) { + mRectXSpring.animateToFinalPosition(mTargetRect.centerX()); + } - if (mRectYAnim != null) { - switch (mTracking) { - case TRACKING_TOP: - if (mRectYAnim.getTargetPosition() != mTargetRect.top) { - mRectYAnim.updatePosition(mCurrentY, mTargetRect.top); - } - break; - case TRACKING_BOTTOM: - if (mRectYAnim.getTargetPosition() != mTargetRect.bottom) { - mRectYAnim.updatePosition(mCurrentY, mTargetRect.bottom); - } - break; - case TRACKING_CENTER: - if (mRectYAnim.getTargetPosition() != mTargetRect.centerY()) { - mRectYAnim.updatePosition(mCurrentY, mTargetRect.centerY()); - } - break; + if (mRectYSpring != null) { + switch (mTracking) { + case TRACKING_TOP: + mRectYSpring.animateToFinalPosition(mTargetRect.top); + break; + case TRACKING_BOTTOM: + mRectYSpring.animateToFinalPosition(mTargetRect.bottom); + break; + case TRACKING_CENTER: + mRectYSpring.animateToFinalPosition(mTargetRect.centerY()); + break; + } + } + } else { + if (mRectXAnim != null && mRectXAnim.getTargetPosition() != mTargetRect.centerX()) { + mRectXAnim.updatePosition(mCurrentCenterX, mTargetRect.centerX()); + } + + if (mRectYAnim != null) { + switch (mTracking) { + case TRACKING_TOP: + if (mRectYAnim.getTargetPosition() != mTargetRect.top) { + mRectYAnim.updatePosition(mCurrentY, mTargetRect.top); + } + break; + case TRACKING_BOTTOM: + if (mRectYAnim.getTargetPosition() != mTargetRect.bottom) { + mRectYAnim.updatePosition(mCurrentY, mTargetRect.bottom); + } + break; + case TRACKING_CENTER: + if (mRectYAnim.getTargetPosition() != mTargetRect.centerY()) { + mRectYAnim.updatePosition(mCurrentY, mTargetRect.centerY()); + } + break; + } } } } @@ -215,59 +239,126 @@ public class RectFSpringAnim extends ReleaseCheck { maybeOnEnd(); }); - // We dampen the user velocity here to keep the natural feeling and to prevent the - // rect from straying too from a linear path. - final float xVelocityPxPerS = velocityPxPerMs.x * 1000; - final float yVelocityPxPerS = velocityPxPerMs.y * 1000; - final float dampedXVelocityPxPerS = OverScroll.dampedScroll( - Math.abs(xVelocityPxPerS), mMaxVelocityPxPerS) * Math.signum(xVelocityPxPerS); - final float dampedYVelocityPxPerS = OverScroll.dampedScroll( - Math.abs(yVelocityPxPerS), mMaxVelocityPxPerS) * Math.signum(yVelocityPxPerS); - + float xVelocityPxPerS = velocityPxPerMs.x * 1000; + float yVelocityPxPerS = velocityPxPerMs.y * 1000; float startX = mCurrentCenterX; float endX = mTargetRect.centerX(); - float minXValue = Math.min(startX, endX); - float maxXValue = Math.max(startX, endX); - - mRectXAnim = new FlingSpringAnim(this, context, RECT_CENTER_X, startX, endX, - dampedXVelocityPxPerS, mMinVisChange, minXValue, maxXValue, mDampingX, mStiffnessX, - onXEndListener); - float startY = mCurrentY; float endY = getTrackedYFromRect(mTargetRect); - float minYValue = Math.min(startY, endY); - float maxYValue = Math.max(startY, endY); - mRectYAnim = new FlingSpringAnim(this, context, RECT_Y, startY, endY, dampedYVelocityPxPerS, - mMinVisChange, minYValue, maxYValue, mDampingY, mStiffnessY, onYEndListener); - float minVisibleChange = Math.abs(1f / mStartRect.height()); - ResourceProvider rp = DynamicResource.provider(context); - float damping = rp.getFloat(R.dimen.swipe_up_rect_scale_damping_ratio); - // Increase the stiffness for devices where we want the window size to transform quicker. - boolean shouldUseHigherStiffness = profile != null - && (profile.isLandscape || profile.isTablet); - float stiffness = shouldUseHigherStiffness - ? rp.getFloat(R.dimen.swipe_up_rect_scale_higher_stiffness) - : rp.getFloat(R.dimen.swipe_up_rect_scale_stiffness); + if (enableScalingRevealHomeAnimation()) { + ResourceProvider rp = DynamicResource.provider(context); + long minVelocityXPxPerS = rp.getInt(R.dimen.swipe_up_min_velocity_x_px_per_s); + long maxVelocityXPxPerS = rp.getInt(R.dimen.swipe_up_max_velocity_x_px_per_s); + long minVelocityYPxPerS = rp.getInt(R.dimen.swipe_up_min_velocity_y_px_per_s); + long maxVelocityYPxPerS = rp.getInt(R.dimen.swipe_up_max_velocity_y_px_per_s); + float fallOffFactor = rp.getFloat(R.dimen.swipe_up_max_velocity_fall_off_factor); - mRectScaleAnim = new SpringAnimation(this, RECT_SCALE_PROGRESS) - .setSpring(new SpringForce(1f) - .setDampingRatio(damping) - .setStiffness(stiffness)) - .setStartVelocity(velocityPxPerMs.y * minVisibleChange) - .setMaxValue(1f) - .setMinimumVisibleChange(minVisibleChange) - .addEndListener((animation, canceled, value, velocity) -> { - mRectScaleAnimEnded = true; - maybeOnEnd(); - }); + // We want the actual initial velocity to never dip below the minimum, and to taper off + // once it's above the soft cap so that we can prevent the window from flying off + // screen, while maintaining a natural feel. + xVelocityPxPerS = adjustVelocity( + xVelocityPxPerS, minVelocityXPxPerS, maxVelocityXPxPerS, fallOffFactor); + yVelocityPxPerS = adjustVelocity( + yVelocityPxPerS, minVelocityYPxPerS, maxVelocityYPxPerS, fallOffFactor); - setCanRelease(false); - mAnimsStarted = true; + float stiffnessX = rp.getFloat(R.dimen.swipe_up_rect_x_stiffness); + float dampingX = rp.getFloat(R.dimen.swipe_up_rect_x_damping_ratio); + mRectXSpring = + new SpringAnimation(this, RECT_CENTER_X) + .setSpring( + new SpringForce(endX) + .setStiffness(stiffnessX) + .setDampingRatio(dampingX) + ).setStartValue(startX) + .setStartVelocity(xVelocityPxPerS) + .addEndListener(onXEndListener); + + float stiffnessY = rp.getFloat(R.dimen.swipe_up_rect_y_stiffness); + float dampingY = rp.getFloat(R.dimen.swipe_up_rect_y_damping_ratio); + mRectYSpring = + new SpringAnimation(this, RECT_Y) + .setSpring( + new SpringForce(endY) + .setStiffness(stiffnessY) + .setDampingRatio(dampingY) + ) + .setStartValue(startY) + .setStartVelocity(yVelocityPxPerS) + .addEndListener(onYEndListener); + + float stiffnessZ = rp.getFloat(R.dimen.swipe_up_rect_scale_stiffness_v2); + float dampingZ = rp.getFloat(R.dimen.swipe_up_rect_scale_damping_ratio_v2); + mRectScaleAnim = + new SpringAnimation(this, RECT_SCALE_PROGRESS) + .setSpring( + new SpringForce(1f) + .setStiffness(stiffnessZ) + .setDampingRatio(dampingZ)) + .setStartVelocity(velocityPxPerMs.y * minVisibleChange) + .setMinimumVisibleChange(minVisibleChange) + .addEndListener((animation, canceled, value, velocity) -> { + mRectScaleAnimEnded = true; + maybeOnEnd(); + }); + + setCanRelease(false); + mAnimsStarted = true; + + mRectXSpring.start(); + mRectYSpring.start(); + } else { + // We dampen the user velocity here to keep the natural feeling and to prevent the + // rect from straying too from a linear path. + final float dampedXVelocityPxPerS = OverScroll.dampedScroll( + Math.abs(xVelocityPxPerS), mMaxVelocityPxPerS) * Math.signum(xVelocityPxPerS); + final float dampedYVelocityPxPerS = OverScroll.dampedScroll( + Math.abs(yVelocityPxPerS), mMaxVelocityPxPerS) * Math.signum(yVelocityPxPerS); + + float minXValue = Math.min(startX, endX); + float maxXValue = Math.max(startX, endX); + + mRectXAnim = new FlingSpringAnim(this, context, RECT_CENTER_X, startX, endX, + dampedXVelocityPxPerS, mMinVisChange, minXValue, maxXValue, mDampingX, + mStiffnessX, onXEndListener); + + float minYValue = Math.min(startY, endY); + float maxYValue = Math.max(startY, endY); + mRectYAnim = new FlingSpringAnim(this, context, RECT_Y, startY, endY, + dampedYVelocityPxPerS, mMinVisChange, minYValue, maxYValue, mDampingY, + mStiffnessY, onYEndListener); + + ResourceProvider rp = DynamicResource.provider(context); + float damping = rp.getFloat(R.dimen.swipe_up_rect_scale_damping_ratio); + + // Increase the stiffness for devices where we want the window size to transform + // quicker. + boolean shouldUseHigherStiffness = profile != null + && (profile.isLandscape || profile.isTablet); + float stiffness = shouldUseHigherStiffness + ? rp.getFloat(R.dimen.swipe_up_rect_scale_higher_stiffness) + : rp.getFloat(R.dimen.swipe_up_rect_scale_stiffness); + + mRectScaleAnim = new SpringAnimation(this, RECT_SCALE_PROGRESS) + .setSpring(new SpringForce(1f) + .setDampingRatio(damping) + .setStiffness(stiffness)) + .setStartVelocity(velocityPxPerMs.y * minVisibleChange) + .setMaxValue(1f) + .setMinimumVisibleChange(minVisibleChange) + .addEndListener((animation, canceled, value, velocity) -> { + mRectScaleAnimEnded = true; + maybeOnEnd(); + }); + + setCanRelease(false); + mAnimsStarted = true; + + mRectXAnim.start(); + mRectYAnim.start(); + } - mRectXAnim.start(); - mRectYAnim.start(); mRectScaleAnim.start(); for (Animator.AnimatorListener animatorListener : mAnimatorListeners) { animatorListener.onAnimationStart(null); @@ -276,8 +367,17 @@ public class RectFSpringAnim extends ReleaseCheck { public void end() { if (mAnimsStarted) { - mRectXAnim.end(); - mRectYAnim.end(); + if (enableScalingRevealHomeAnimation()) { + if (mRectXSpring.canSkipToEnd()) { + mRectXSpring.skipToEnd(); + } + if (mRectYSpring.canSkipToEnd()) { + mRectYSpring.skipToEnd(); + } + } else { + mRectXAnim.end(); + mRectYAnim.end(); + } if (mRectScaleAnim.canSkipToEnd()) { mRectScaleAnim.skipToEnd(); } @@ -357,6 +457,32 @@ public class RectFSpringAnim extends ReleaseCheck { end(); } + /** + * Modify the given velocity so that it's never below the minimum value, and falls off by the + * given factor once it goes above the maximum value. + * In order for the max soft cap to be enforced, the fall-off factor must be >1. + */ + private static float adjustVelocity(float velocity, long min, long max, float factor) { + float sign = Math.signum(velocity); + float magnitude = Math.abs(velocity); + + // If the absolute velocity is less than the min, bump it up. + if (magnitude < min) { + return min * sign; + } + + // If the absolute velocity falls between min and max, or the fall-off factor is invalid, + // do nothing. + if (magnitude <= max || factor <= 1) { + return velocity; + } + + // Scale the excess velocity by the fall-off factor. + float excess = magnitude - max; + float scaled = (float) Math.pow(excess, 1f / factor); + return (max + scaled) * sign; + } + public interface OnUpdateListener { /** * Called when an update is made to the animation. diff --git a/quickstep/src/com/android/quickstep/util/ScalingWorkspaceRevealAnim.kt b/quickstep/src/com/android/quickstep/util/ScalingWorkspaceRevealAnim.kt new file mode 100644 index 0000000000..33736adc82 --- /dev/null +++ b/quickstep/src/com/android/quickstep/util/ScalingWorkspaceRevealAnim.kt @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2024 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.view.View +import com.android.app.animation.Interpolators +import com.android.app.animation.Interpolators.EMPHASIZED +import com.android.app.animation.Interpolators.LINEAR +import com.android.launcher3.Launcher +import com.android.launcher3.LauncherAnimUtils.HOTSEAT_SCALE_PROPERTY_FACTORY +import com.android.launcher3.LauncherAnimUtils.SCALE_INDEX_WORKSPACE_STATE +import com.android.launcher3.LauncherAnimUtils.WORKSPACE_SCALE_PROPERTY_FACTORY +import com.android.launcher3.LauncherState +import com.android.launcher3.anim.AnimatorListeners +import com.android.launcher3.anim.PendingAnimation +import com.android.launcher3.anim.PropertySetter +import com.android.launcher3.states.StateAnimationConfig +import com.android.launcher3.states.StateAnimationConfig.SKIP_DEPTH_CONTROLLER +import com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW +import com.android.launcher3.states.StateAnimationConfig.SKIP_SCRIM +import com.android.launcher3.uioverrides.QuickstepLauncher +import com.android.quickstep.views.RecentsView + +/** + * Creates an animation where the workspace and hotseat fade in while revealing from the center of + * the screen outwards radially. This is used in conjunction with the swipe up to home animation. + */ +class ScalingWorkspaceRevealAnim(launcher: Launcher) { + companion object { + private const val FADE_DURATION_MS = 200L + private const val SCALE_DURATION_MS = 1000L + private const val MAX_ALPHA = 1f + private const val MIN_ALPHA = 0f + private const val MAX_SIZE = 1f + private const val MIN_SIZE = 0.85f + } + + private val animation = PendingAnimation(SCALE_DURATION_MS) + + init { + // Make sure the starting state is right for the animation. + val config = StateAnimationConfig() + config.animFlags = SKIP_OVERVIEW.or(SKIP_DEPTH_CONTROLLER).or(SKIP_SCRIM) + config.duration = 0 + launcher.stateManager + .createAtomicAnimation(LauncherState.BACKGROUND_APP, LauncherState.NORMAL, config) + .start() + launcher + .getOverviewPanel>() + .forceFinishScroller() + launcher.workspace.stateTransitionAnimation.setScrim( + PropertySetter.NO_ANIM_PROPERTY_SETTER, + LauncherState.BACKGROUND_APP, + config + ) + + val workspace = launcher.workspace + val hotseat = launcher.hotseat + + // Scale the Workspace and Hotseat around the same pivot. + animation.addFloat( + workspace, + WORKSPACE_SCALE_PROPERTY_FACTORY[SCALE_INDEX_WORKSPACE_STATE], + MIN_SIZE, + MAX_SIZE, + EMPHASIZED, + ) + workspace.setPivotToScaleWithSelf(hotseat) + animation.addFloat( + hotseat, + HOTSEAT_SCALE_PROPERTY_FACTORY[SCALE_INDEX_WORKSPACE_STATE], + MIN_SIZE, + MAX_SIZE, + EMPHASIZED, + ) + + // Fade in quickly at the beginning of the animation, so the content doesn't look like it's + // popping into existence out of nowhere. + val fadeClamp = FADE_DURATION_MS.toFloat() / SCALE_DURATION_MS + workspace.alpha = MIN_ALPHA + animation.setViewAlpha( + workspace, + MAX_ALPHA, + Interpolators.clampToProgress(LINEAR, 0f, fadeClamp) + ) + hotseat.alpha = MIN_ALPHA + animation.setViewAlpha( + hotseat, + MAX_ALPHA, + Interpolators.clampToProgress(LINEAR, 0f, fadeClamp) + ) + + // Match the Wallpaper animation to the rest of the content. + val depthController = (launcher as? QuickstepLauncher)?.depthController + val depthConfig = StateAnimationConfig() + depthConfig.setInterpolator(StateAnimationConfig.ANIM_DEPTH, EMPHASIZED) + depthController?.setStateWithAnimation(LauncherState.NORMAL, depthConfig, animation) + + // Needed to avoid text artefacts during the scale animation. + workspace.setLayerType(View.LAYER_TYPE_HARDWARE, null) + hotseat.setLayerType(View.LAYER_TYPE_HARDWARE, null) + animation.addListener( + AnimatorListeners.forEndCallback( + Runnable { + workspace.setLayerType(View.LAYER_TYPE_NONE, null) + hotseat.setLayerType(View.LAYER_TYPE_NONE, null) + } + ) + ) + } + + fun start() { + animation.buildAnim().start() + } +} diff --git a/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java b/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java index 6b3199f5b4..d6d6a119bd 100644 --- a/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java +++ b/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java @@ -65,7 +65,7 @@ public class StaggeredWorkspaceAnim { // Should be used for animations running alongside this StaggeredWorkspaceAnim. public static final int DURATION_MS = 250; public static final int DURATION_TASKBAR_MS = - QuickstepTransitionManager.TASKBAR_TO_HOME_DURATION; + QuickstepTransitionManager.getTaskbarToHomeDuration(); private static final float MAX_VELOCITY_PX_PER_S = 22f; diff --git a/res/values/config.xml b/res/values/config.xml index 21d6024583..048d6cc01e 100644 --- a/res/values/config.xml +++ b/res/values/config.xml @@ -159,6 +159,9 @@ 0.75 200 400 + + 0.8 + 650 1.5 @@ -166,6 +169,11 @@ 0.8 200 + + 0.965 + 300 + 0.95 + 190 @@ -190,6 +198,12 @@ 18dp -60dp 7.619dp + + 300 + 500 + 2000 + 5000 + 1.4 @dimen/swipe_up_scale_start diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java index 4b4bdc2ecf..9afbc64141 100644 --- a/src/com/android/launcher3/DeviceProfile.java +++ b/src/com/android/launcher3/DeviceProfile.java @@ -18,6 +18,7 @@ package com.android.launcher3; import static com.android.app.animation.Interpolators.LINEAR; import static com.android.launcher3.Flags.enableOverviewIconMenu; +import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation; import static com.android.launcher3.InvariantDeviceProfile.INDEX_DEFAULT; import static com.android.launcher3.InvariantDeviceProfile.INDEX_LANDSCAPE; import static com.android.launcher3.InvariantDeviceProfile.INDEX_TWO_PANEL_LANDSCAPE; @@ -436,7 +437,11 @@ public class DeviceProfile { if (isMultiDisplay && !ENABLE_MULTI_DISPLAY_PARTIAL_DEPTH.get()) { // TODO(b/259893832): Revert to use maxWallpaperScale to calculate bottomSheetDepth // when screen recorder bug is fixed. - bottomSheetDepth = 1f; + if (enableScalingRevealHomeAnimation()) { + bottomSheetDepth = 0.3f; + } else { + bottomSheetDepth = 1f; + } } else { // The goal is to set wallpaper to zoom at workspaceContentScale when in AllApps. // When depth is 0, wallpaper zoom is set to maxWallpaperScale. diff --git a/src/com/android/launcher3/states/EditModeState.kt b/src/com/android/launcher3/states/EditModeState.kt index aafaaa07fa..6ff47aef43 100644 --- a/src/com/android/launcher3/states/EditModeState.kt +++ b/src/com/android/launcher3/states/EditModeState.kt @@ -16,6 +16,7 @@ package com.android.launcher3.states import android.content.Context +import com.android.launcher3.Flags.enableScalingRevealHomeAnimation import com.android.launcher3.Launcher import com.android.launcher3.LauncherState import com.android.launcher3.logging.StatsLogManager @@ -25,6 +26,8 @@ import com.android.launcher3.views.ActivityContext class EditModeState(id: Int) : LauncherState(id, StatsLogManager.LAUNCHER_STATE_HOME, STATE_FLAGS) { companion object { + const val DEPTH_15_PERCENT = 0.15f + private val STATE_FLAGS = (FLAG_MULTI_PAGE or FLAG_WORKSPACE_INACCESSIBLE or @@ -40,7 +43,11 @@ class EditModeState(id: Int) : LauncherState(id, StatsLogManager.LAUNCHER_STATE_ } override fun getDepthUnchecked(context: T): Float where T : Context?, T : ActivityContext? { - return 0.5f + if (enableScalingRevealHomeAnimation()) { + return DEPTH_15_PERCENT + } else { + return 0.5f + } } override fun getWorkspaceScaleAndTranslation(launcher: Launcher): ScaleAndTranslation { diff --git a/src/com/android/launcher3/states/HintState.java b/src/com/android/launcher3/states/HintState.java index 4cfced8561..bf2fb30841 100644 --- a/src/com/android/launcher3/states/HintState.java +++ b/src/com/android/launcher3/states/HintState.java @@ -15,6 +15,7 @@ */ package com.android.launcher3.states; +import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation; import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_HOME; import android.content.Context; @@ -34,6 +35,8 @@ public class HintState extends LauncherState { private static final int STATE_FLAGS = FLAG_WORKSPACE_INACCESSIBLE | FLAG_DISABLE_RESTORE | FLAG_HAS_SYS_UI_SCRIM; + public static final float DEPTH_5_PERCENT = 0.05f; + public HintState(int id) { this(id, LAUNCHER_STATE_HOME); } @@ -49,7 +52,11 @@ public class HintState extends LauncherState { @Override protected float getDepthUnchecked(Context context) { - return 0.15f; + if (enableScalingRevealHomeAnimation()) { + return DEPTH_5_PERCENT; + } else { + return 0.15f; + } } @Override diff --git a/src/com/android/launcher3/states/SpringLoadedState.java b/src/com/android/launcher3/states/SpringLoadedState.java index 3286afb67c..2e57ed86ab 100644 --- a/src/com/android/launcher3/states/SpringLoadedState.java +++ b/src/com/android/launcher3/states/SpringLoadedState.java @@ -15,6 +15,7 @@ */ package com.android.launcher3.states; +import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation; import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_HOME; import android.content.Context; @@ -33,6 +34,8 @@ public class SpringLoadedState extends LauncherState { | FLAG_WORKSPACE_INACCESSIBLE | FLAG_DISABLE_RESTORE | FLAG_WORKSPACE_ICONS_CAN_BE_DRAGGED | FLAG_WORKSPACE_HAS_BACKGROUNDS; + public static final float DEPTH_15_PERCENT = 0.15f; + public SpringLoadedState(int id) { super(id, LAUNCHER_STATE_HOME, STATE_FLAGS); } @@ -62,7 +65,11 @@ public class SpringLoadedState extends LauncherState { @Override protected float getDepthUnchecked(Context context) { - return 0.5f; + if (enableScalingRevealHomeAnimation()) { + return DEPTH_15_PERCENT; + } else { + return 0.5f; + } } @Override