From 2831d1eaf7dfaeb3ed1901480a9e36579e6718e1 Mon Sep 17 00:00:00 2001 From: Luca Zuccarini Date: Wed, 18 Sep 2024 11:08:56 +0000 Subject: [PATCH] Make workspace and hotseat scale animations interruptible. This affects app launches and returns to home. When launching immediately after returning to home (before the return animation is complete), the two animations could overlap, causing the two property setters to conflict. Bug: 367591368 Flag: com.android.launcher3.coordinate_workspace_scale Test: tested with flag off and on, see before and after in the bug Change-Id: Ie1483fa0bf589caee315ae298e9e68ca361709d8 --- aconfig/launcher.aconfig | 11 ++++ .../launcher3/QuickstepTransitionManager.java | 38 ++++++++++++-- .../util/ScalingWorkspaceRevealAnim.kt | 51 +++++++++++++++---- 3 files changed, 85 insertions(+), 15 deletions(-) diff --git a/aconfig/launcher.aconfig b/aconfig/launcher.aconfig index 38af57260f..9fb5b7b061 100644 --- a/aconfig/launcher.aconfig +++ b/aconfig/launcher.aconfig @@ -433,3 +433,14 @@ flag { description: "Enables tracking active gesture logs in ProtoLog" bug: "293182501" } + + +flag { + name: "coordinate_workspace_scale" + namespace: "launcher" + description: "Ensure that the workspace and hotseat scale doesn't conflict and transitions smoothly between launching and closing apps" + bug: "366403487" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java index a64936d287..acf74cdede 100644 --- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java +++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java @@ -116,6 +116,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.graphics.ColorUtils; +import com.android.app.animation.Animations; import com.android.internal.jank.Cuj; import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener; import com.android.launcher3.LauncherAnimationRunner.RemoteAnimationFactory; @@ -574,23 +575,45 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener } else { List viewsToAnimate = new ArrayList<>(); Workspace workspace = mLauncher.getWorkspace(); - workspace.forEachVisiblePage( - view -> viewsToAnimate.add(((CellLayout) view).getShortcutsAndWidgets())); + if (Flags.coordinateWorkspaceScale()) { + viewsToAnimate.add(workspace); + } else { + workspace.forEachVisiblePage( + view -> viewsToAnimate.add(((CellLayout) view).getShortcutsAndWidgets())); + } + Hotseat hotseat = mLauncher.getHotseat(); // Do not scale hotseat as a whole when taskbar is present, and scale QSB only if it's // not inline. if (mDeviceProfile.isTaskbarPresent) { if (!mDeviceProfile.isQsbInline) { - viewsToAnimate.add(mLauncher.getHotseat().getQsb()); + viewsToAnimate.add(hotseat.getQsb()); } } else { - viewsToAnimate.add(mLauncher.getHotseat()); + viewsToAnimate.add(hotseat); } viewsToAnimate.forEach(view -> { view.setLayerType(View.LAYER_TYPE_HARDWARE, null); - ObjectAnimator scaleAnim = ObjectAnimator.ofFloat(view, SCALE_PROPERTY, scales) + float[] scale = scales; + if (Flags.coordinateWorkspaceScale()) { + // Start the animation from the current value, instead of assuming the views are + // in their resting state, so interrupted animations merge seamlessly. + // TODO(b/367591368): ideally these animations would be refactored to be + // controlled centrally so each instances doesn't need to care about this + // coordination. + scale = new float[]{view.getScaleX(), scales[1]}; + + // Cancel any ongoing animations. This is necessary to avoid a conflict between + // e.g. the unfinished animation triggered when closing an app back to Home and + // this animation caused by a launch. + Animations.Companion.cancelOngoingAnimation(view); + // Make sure to cache the current animation, so it can be properly interrupted. + Animations.Companion.setOngoingAnimation(view, launcherAnimator); + } + + ObjectAnimator scaleAnim = ObjectAnimator.ofFloat(view, SCALE_PROPERTY, scale) .setDuration(CONTENT_SCALE_DURATION); scaleAnim.setInterpolator(DECELERATE_1_5); launcherAnimator.play(scaleAnim); @@ -600,6 +623,11 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener viewsToAnimate.forEach(view -> { SCALE_PROPERTY.set(view, 1f); view.setLayerType(View.LAYER_TYPE_NONE, null); + + if (Flags.coordinateWorkspaceScale()) { + // Reset the cached animation. + Animations.Companion.setOngoingAnimation(view, null /* animation */); + } }); mLauncher.resumeExpensiveViewUpdates(); }; diff --git a/quickstep/src/com/android/quickstep/util/ScalingWorkspaceRevealAnim.kt b/quickstep/src/com/android/quickstep/util/ScalingWorkspaceRevealAnim.kt index f547a7fba1..a94d023076 100644 --- a/quickstep/src/com/android/quickstep/util/ScalingWorkspaceRevealAnim.kt +++ b/quickstep/src/com/android/quickstep/util/ScalingWorkspaceRevealAnim.kt @@ -23,8 +23,10 @@ import android.graphics.RectF import android.view.View import android.view.animation.PathInterpolator import androidx.core.graphics.transform +import com.android.app.animation.Animations import com.android.app.animation.Interpolators import com.android.app.animation.Interpolators.LINEAR +import com.android.launcher3.Flags 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 @@ -44,9 +46,9 @@ import com.android.quickstep.views.RecentsView * the screen outwards radially. This is used in conjunction with the swipe up to home animation. */ class ScalingWorkspaceRevealAnim( - launcher: QuickstepLauncher, + private val launcher: QuickstepLauncher, siblingAnimation: RectFSpringAnim?, - windowTargetRect: RectF? + windowTargetRect: RectF?, ) { companion object { private const val FADE_DURATION_MS = 200L @@ -86,25 +88,40 @@ class ScalingWorkspaceRevealAnim( launcher.workspace.stateTransitionAnimation.setScrim( PropertySetter.NO_ANIM_PROPERTY_SETTER, LauncherState.BACKGROUND_APP, - setupConfig + setupConfig, ) val workspace = launcher.workspace val hotseat = launcher.hotseat + var fromSize = + if (Flags.coordinateWorkspaceScale()) { + // Interrupt the current animation, if any. + Animations.cancelOngoingAnimation(workspace) + Animations.cancelOngoingAnimation(hotseat) + + if (workspace.scaleX != MAX_SIZE) { + workspace.scaleX + } else { + MIN_SIZE + } + } else { + MIN_SIZE + } + // Scale the Workspace and Hotseat around the same pivot. workspace.setPivotToScaleWithSelf(hotseat) animation.addFloat( workspace, WORKSPACE_SCALE_PROPERTY_FACTORY[SCALE_INDEX_WORKSPACE_STATE], - MIN_SIZE, + fromSize, MAX_SIZE, SCALE_INTERPOLATOR, ) animation.addFloat( hotseat, HOTSEAT_SCALE_PROPERTY_FACTORY[SCALE_INDEX_WORKSPACE_STATE], - MIN_SIZE, + fromSize, MAX_SIZE, SCALE_INTERPOLATOR, ) @@ -116,13 +133,13 @@ class ScalingWorkspaceRevealAnim( animation.setViewAlpha( workspace, MAX_ALPHA, - Interpolators.clampToProgress(LINEAR, 0f, fadeClamp) + Interpolators.clampToProgress(LINEAR, 0f, fadeClamp), ) hotseat.alpha = MIN_ALPHA animation.setViewAlpha( hotseat, MAX_ALPHA, - Interpolators.clampToProgress(LINEAR, 0f, fadeClamp) + Interpolators.clampToProgress(LINEAR, 0f, fadeClamp), ) val transitionConfig = StateAnimationConfig() @@ -137,7 +154,7 @@ class ScalingWorkspaceRevealAnim( launcher.workspace.stateTransitionAnimation.setScrim( animation, LauncherState.NORMAL, - transitionConfig + transitionConfig, ) // To avoid awkward jumps in icon position, we want the sibling animation to always be @@ -164,7 +181,7 @@ class ScalingWorkspaceRevealAnim( 1 / workspace.scaleX, 1 / workspace.scaleY, transformed.centerX(), - transformed.centerY() + transformed.centerY(), ) } ) @@ -183,6 +200,12 @@ class ScalingWorkspaceRevealAnim( Runnable { workspace.setLayerType(View.LAYER_TYPE_NONE, null) hotseat.setLayerType(View.LAYER_TYPE_NONE, null) + + if (Flags.coordinateWorkspaceScale()) { + // Reset the cached animations. + Animations.setOngoingAnimation(workspace, animation = null) + Animations.setOngoingAnimation(hotseat, animation = null) + } } ) ) @@ -193,6 +216,14 @@ class ScalingWorkspaceRevealAnim( } fun start() { - getAnimators().start() + val animators = getAnimators() + if (Flags.coordinateWorkspaceScale()) { + // Make sure to cache the current animation, so it can be properly interrupted. + // TODO(b/367591368): ideally these animations would be refactored to be controlled + // centrally so each instances doesn't need to care about this coordination. + Animations.setOngoingAnimation(launcher.workspace, animators) + Animations.setOngoingAnimation(launcher.hotseat, animators) + } + animators.start() } }