From 0750f03c9679746c526cf7117eb6c993644cb0d3 Mon Sep 17 00:00:00 2001 From: Jon Miranda Date: Fri, 11 Jun 2021 16:30:47 -0700 Subject: [PATCH] Address LAUNCHER_APP_LAUNCH_FROM_ICON jank. - Delay app launch animations by a frame, and skip logic to skip the first frame. - Note the icon pressed state animation still occurs, so there is still some visual feedback for the user that something is happening. Bug: 181901105 Test: ensure animation still looks smooth (using window animation scale & record in slow mo) Change-Id: Ia904b8b96301042c900e0589f33fc625c1c1148b Merged-In: Ia904b8b96301042c900e0589f33fc625c1c1148b --- .../launcher3/LauncherAnimationRunner.java | 16 ++++--- .../launcher3/QuickstepTransitionManager.java | 47 ++++++++++++++----- .../android/quickstep/RecentsActivity.java | 6 ++- .../com/android/quickstep/TaskViewUtils.java | 2 +- .../util/MultiValueUpdateListener.java | 8 +++- .../quickstep/util/RectFSpringAnim2.java | 2 +- .../quickstep/views/AllAppsEduView.java | 2 +- .../launcher3/views/FloatingIconView.java | 1 + 8 files changed, 59 insertions(+), 25 deletions(-) diff --git a/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java b/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java index be98157b1b..1090099d2f 100644 --- a/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java +++ b/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java @@ -152,16 +152,18 @@ public abstract class LauncherAnimationRunner implements RemoteAnimationRunnerCo @UiThread public void setAnimation(AnimatorSet animation, Context context) { - setAnimation(animation, context, null); + setAnimation(animation, context, null, true); } /** * Sets the animation to play for this app launch + * @param skipFirstFrame Iff true, we skip the first frame of the animation. + * We set to false when skipping first frame causes jank. */ @UiThread public void setAnimation(AnimatorSet animation, Context context, - @Nullable Runnable onCompleteCallback) { + @Nullable Runnable onCompleteCallback, boolean skipFirstFrame) { if (mInitialized) { throw new IllegalStateException("Animation already initialized"); } @@ -187,10 +189,12 @@ public abstract class LauncherAnimationRunner implements RemoteAnimationRunnerCo }); mAnimator.start(); - // Because t=0 has the app icon in its original spot, we can skip the - // first frame and have the same movement one frame earlier. - mAnimator.setCurrentPlayTime( - Math.min(getSingleFrameMs(context), mAnimator.getTotalDuration())); + if (skipFirstFrame) { + // Because t=0 has the app icon in its original spot, we can skip the + // first frame and have the same movement one frame earlier. + mAnimator.setCurrentPlayTime( + Math.min(getSingleFrameMs(context), mAnimator.getTotalDuration())); + } } } } diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java index 26d407fb96..65df237e5f 100644 --- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java +++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java @@ -38,6 +38,7 @@ import static com.android.launcher3.config.FeatureFlags.KEYGUARD_ANIMATION; import static com.android.launcher3.config.FeatureFlags.SEPARATE_RECENTS_ACTIVITY; import static com.android.launcher3.dragndrop.DragLayer.ALPHA_INDEX_TRANSITIONS; import static com.android.launcher3.statehandlers.DepthController.DEPTH; +import static com.android.launcher3.util.DisplayController.getSingleFrameMs; import static com.android.quickstep.TaskUtils.taskIsATargetWithMode; import static com.android.quickstep.TaskViewUtils.findTaskViewToLaunch; import static com.android.systemui.shared.system.QuickStepContract.getWindowCornerRadius; @@ -340,12 +341,17 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener final int rotationChange = getRotationChange(appTargets); // Note: the targetBounds are relative to the launcher + int startDelay = getSingleFrameMs(mLauncher); Rect windowTargetBounds = getWindowTargetBounds(appTargets, rotationChange); - anim.play(getOpeningWindowAnimators(v, appTargets, wallpaperTargets, nonAppTargets, - windowTargetBounds, areAllTargetsTranslucent(appTargets), rotationChange)); + Animator windowAnimator = getOpeningWindowAnimators(v, appTargets, wallpaperTargets, + nonAppTargets, windowTargetBounds, areAllTargetsTranslucent(appTargets), + rotationChange); + windowAnimator.setStartDelay(startDelay); + anim.play(windowAnimator); if (launcherClosing) { + // Delay animation by a frame to avoid jank. Pair launcherContentAnimator = - getLauncherContentAnimator(true /* isAppOpening */); + getLauncherContentAnimator(true /* isAppOpening */, startDelay); anim.play(launcherContentAnimator.first); anim.addListener(new AnimatorListenerAdapter() { @Override @@ -436,8 +442,10 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener * * @param isAppOpening True when this is called when an app is opening. * False when this is called when an app is closing. + * @param startDelay Start delay duration. */ - private Pair getLauncherContentAnimator(boolean isAppOpening) { + private Pair getLauncherContentAnimator(boolean isAppOpening, + int startDelay) { AnimatorSet launcherAnimator = new AnimatorSet(); Runnable endListener; @@ -528,6 +536,8 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener mLauncher.getWorkspace().getPageIndicator().skipAnimationsToEnd(); }; } + + launcherAnimator.setStartDelay(startDelay); return new Pair<>(launcherAnimator, endListener); } @@ -633,7 +643,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener ? 0 : getWindowCornerRadius(mLauncher.getResources()); final float finalShadowRadius = appTargetsAreTranslucent ? 0 : mMaxShadowRadius; - appAnimator.addUpdateListener(new MultiValueUpdateListener() { + MultiValueUpdateListener listener = new MultiValueUpdateListener() { FloatProp mDx = new FloatProp(0, prop.dX, 0, prop.xDuration, AGGRESSIVE_EASE); FloatProp mDy = new FloatProp(0, prop.dY, 0, prop.yDuration, AGGRESSIVE_EASE); @@ -662,7 +672,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener ANIMATION_NAV_FADE_IN_DURATION, NAV_FADE_IN_INTERPOLATOR); @Override - public void onUpdate(float percent) { + public void onUpdate(float percent, boolean initOnly) { // Calculate the size of the scaled icon. float iconWidth = launcherIconBounds.width() * mIconScaleToFitScreen.value; float iconHeight = launcherIconBounds.height() * mIconScaleToFitScreen.value; @@ -707,6 +717,12 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener floatingIconBounds.right += offsetX; floatingIconBounds.bottom += offsetY; + if (initOnly) { + floatingView.update(mIconAlpha.value, 255, floatingIconBounds, percent, 0f, + mWindowRadius.value * scale, true /* isOpening */); + return; + } + ArrayList params = new ArrayList<>(); for (int i = appTargets.length - 1; i >= 0; i--) { RemoteAnimationTargetCompat target = appTargets[i]; @@ -779,7 +795,10 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener surfaceApplier.scheduleApply(params.toArray(new SurfaceParams[params.size()])); } - }); + }; + appAnimator.addUpdateListener(listener); + // Since we added a start delay, call update here to init the FloatingIconView properly. + listener.onUpdate(0, true /* initOnly */); animatorSet.playTogether(appAnimator, getBackgroundAnimator(appTargets)); return animatorSet; @@ -869,7 +888,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener ANIMATION_NAV_FADE_IN_DURATION, NAV_FADE_IN_INTERPOLATOR); @Override - public void onUpdate(float percent) { + public void onUpdate(float percent, boolean initOnly) { widgetBackgroundBounds.set(mDx.value - mWidth.value / 2f, mDy.value - mHeight.value / 2f, mDx.value + mWidth.value / 2f, mDy.value + mHeight.value / 2f); @@ -1128,7 +1147,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener DEACCEL_1_7); @Override - public void onUpdate(float percent) { + public void onUpdate(float percent, boolean initOnly) { SurfaceParams[] params = new SurfaceParams[appTargets.length]; for (int i = appTargets.length - 1; i >= 0; i--) { RemoteAnimationTargetCompat target = appTargets[i]; @@ -1278,8 +1297,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener if (mLauncher.isInState(LauncherState.ALL_APPS)) { Pair contentAnimator = - getLauncherContentAnimator(false /* isAppOpening */); - contentAnimator.first.setStartDelay(LAUNCHER_RESUME_START_DELAY); + getLauncherContentAnimator(false, LAUNCHER_RESUME_START_DELAY); anim.play(contentAnimator.first); anim.addListener(new AnimatorListenerAdapter() { @Override @@ -1328,27 +1346,32 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener final boolean launchingFromWidget = mV instanceof LauncherAppWidgetHostView; final boolean launchingFromRecents = isLaunchingFromRecents(mV, appTargets); + final boolean skipFirstFrame; if (launchingFromWidget) { composeWidgetLaunchAnimator(anim, (LauncherAppWidgetHostView) mV, appTargets, wallpaperTargets, nonAppTargets); addCujInstrumentation( anim, InteractionJankMonitorWrapper.CUJ_APP_LAUNCH_FROM_WIDGET); + skipFirstFrame = true; } else if (launchingFromRecents) { composeRecentsLaunchAnimator(anim, mV, appTargets, wallpaperTargets, nonAppTargets, launcherClosing); addCujInstrumentation( anim, InteractionJankMonitorWrapper.CUJ_APP_LAUNCH_FROM_RECENTS); + skipFirstFrame = true; } else { composeIconLaunchAnimator(anim, mV, appTargets, wallpaperTargets, nonAppTargets, launcherClosing); addCujInstrumentation(anim, InteractionJankMonitorWrapper.CUJ_APP_LAUNCH_FROM_ICON); + skipFirstFrame = false; } if (launcherClosing) { anim.addListener(mForceInvisibleListener); } - result.setAnimation(anim, mLauncher, mOnEndCallback::executeAllAndDestroy); + result.setAnimation(anim, mLauncher, mOnEndCallback::executeAllAndDestroy, + skipFirstFrame); } } diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java index 68526420ef..e15066f9c1 100644 --- a/quickstep/src/com/android/quickstep/RecentsActivity.java +++ b/quickstep/src/com/android/quickstep/RecentsActivity.java @@ -214,7 +214,8 @@ public final class RecentsActivity extends StatefulActivity { AnimatorSet anim = composeRecentsLaunchAnimator(taskView, appTargets, wallpaperTargets, nonAppTargets); anim.addListener(resetStateListener()); - result.setAnimation(anim, RecentsActivity.this, onEndCallback::executeAllAndDestroy); + result.setAnimation(anim, RecentsActivity.this, onEndCallback::executeAllAndDestroy, + true /* skipFirstFrame */); }; final LauncherAnimationRunner wrapper = new WrappedLauncherAnimationRunner<>( @@ -385,7 +386,8 @@ public final class RecentsActivity extends StatefulActivity { anim.play(controller.getAnimationPlayer()); anim.setDuration(HOME_APPEAR_DURATION); result.setAnimation(anim, this, - () -> getStateManager().goToState(RecentsState.HOME, false)); + () -> getStateManager().goToState(RecentsState.HOME, false), + true /* skipFirstFrame */); } @Override diff --git a/quickstep/src/com/android/quickstep/TaskViewUtils.java b/quickstep/src/com/android/quickstep/TaskViewUtils.java index 59bd1ed9ea..3293810582 100644 --- a/quickstep/src/com/android/quickstep/TaskViewUtils.java +++ b/quickstep/src/com/android/quickstep/TaskViewUtils.java @@ -249,7 +249,7 @@ public final class TaskViewUtils { ANIMATION_NAV_FADE_IN_DURATION, NAV_FADE_IN_INTERPOLATOR); @Override - public void onUpdate(float percent) { + public void onUpdate(float percent, boolean initOnly) { final SurfaceParams.Builder navBuilder = new SurfaceParams.Builder(navBarTarget.leash); if (mNavFadeIn.value > mNavFadeIn.getStartValue()) { diff --git a/quickstep/src/com/android/quickstep/util/MultiValueUpdateListener.java b/quickstep/src/com/android/quickstep/util/MultiValueUpdateListener.java index b4ae1ca7fb..1c3c9c2fae 100644 --- a/quickstep/src/com/android/quickstep/util/MultiValueUpdateListener.java +++ b/quickstep/src/com/android/quickstep/util/MultiValueUpdateListener.java @@ -40,10 +40,14 @@ public abstract class MultiValueUpdateListener implements ValueAnimator.Animator newPercent = prop.mInterpolator.getInterpolation(newPercent); prop.value = prop.mEnd * newPercent + prop.mStart * (1 - newPercent); } - onUpdate(percent); + onUpdate(percent, false /* initOnly */); } - public abstract void onUpdate(float percent); + /** + * @param percent The total animation progress. + * @param initOnly When true, only does enough work to initialize the animation. + */ + public abstract void onUpdate(float percent, boolean initOnly); public final class FloatProp { diff --git a/quickstep/src/com/android/quickstep/util/RectFSpringAnim2.java b/quickstep/src/com/android/quickstep/util/RectFSpringAnim2.java index 93b34822ad..c331a136d9 100644 --- a/quickstep/src/com/android/quickstep/util/RectFSpringAnim2.java +++ b/quickstep/src/com/android/quickstep/util/RectFSpringAnim2.java @@ -280,7 +280,7 @@ public class RectFSpringAnim2 extends RectFSpringAnim { } @Override - public void onUpdate(float percent) {} + public void onUpdate(float percent, boolean initOnly) {} }; } diff --git a/quickstep/src/com/android/quickstep/views/AllAppsEduView.java b/quickstep/src/com/android/quickstep/views/AllAppsEduView.java index 00993e3658..f67940ab56 100644 --- a/quickstep/src/com/android/quickstep/views/AllAppsEduView.java +++ b/quickstep/src/com/android/quickstep/views/AllAppsEduView.java @@ -173,7 +173,7 @@ public class AllAppsEduView extends AbstractFloatingView { FloatProp mGradientAlpha = new FloatProp(0, 255, firstPart, secondPart * 0.3f, LINEAR); @Override - public void onUpdate(float progress) { + public void onUpdate(float progress, boolean initOnly) { temp.set(circleBoundsOg); temp.offset(0, (int) -mDeltaY.value); Utilities.scaleRectAboutCenter(temp, mCircleScale.value); diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java index 25cce69063..3027db6c16 100644 --- a/src/com/android/launcher3/views/FloatingIconView.java +++ b/src/com/android/launcher3/views/FloatingIconView.java @@ -376,6 +376,7 @@ public class FloatingIconView extends FrameLayout implements if (mIconLoadResult.isIconLoaded) { setIcon(mIconLoadResult.drawable, mIconLoadResult.badge, mIconLoadResult.btvDrawable, mIconLoadResult.iconOffset); + setVisibility(VISIBLE); setIconAndDotVisible(originalView, false); } else { mIconLoadResult.onIconLoaded = () -> {