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
diff --git a/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java b/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java
index be98157..1090099 100644
--- a/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java
+++ b/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java
@@ -152,16 +152,18 @@
@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 @@
});
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 26d407f..65df237 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.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 @@
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<AnimatorSet, Runnable> launcherContentAnimator =
- getLauncherContentAnimator(true /* isAppOpening */);
+ getLauncherContentAnimator(true /* isAppOpening */, startDelay);
anim.play(launcherContentAnimator.first);
anim.addListener(new AnimatorListenerAdapter() {
@Override
@@ -436,8 +442,10 @@
*
* @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<AnimatorSet, Runnable> getLauncherContentAnimator(boolean isAppOpening) {
+ private Pair<AnimatorSet, Runnable> getLauncherContentAnimator(boolean isAppOpening,
+ int startDelay) {
AnimatorSet launcherAnimator = new AnimatorSet();
Runnable endListener;
@@ -528,6 +536,8 @@
mLauncher.getWorkspace().getPageIndicator().skipAnimationsToEnd();
};
}
+
+ launcherAnimator.setStartDelay(startDelay);
return new Pair<>(launcherAnimator, endListener);
}
@@ -633,7 +643,7 @@
? 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 @@
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 @@
floatingIconBounds.right += offsetX;
floatingIconBounds.bottom += offsetY;
+ if (initOnly) {
+ floatingView.update(mIconAlpha.value, 255, floatingIconBounds, percent, 0f,
+ mWindowRadius.value * scale, true /* isOpening */);
+ return;
+ }
+
ArrayList<SurfaceParams> params = new ArrayList<>();
for (int i = appTargets.length - 1; i >= 0; i--) {
RemoteAnimationTargetCompat target = appTargets[i];
@@ -779,7 +795,10 @@
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 @@
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 @@
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 @@
if (mLauncher.isInState(LauncherState.ALL_APPS)) {
Pair<AnimatorSet, Runnable> 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 @@
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 6852642..e15066f 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -214,7 +214,8 @@
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 @@
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 59bd1ed..3293810 100644
--- a/quickstep/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskViewUtils.java
@@ -249,7 +249,7 @@
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 b4ae1ca..1c3c9c2 100644
--- a/quickstep/src/com/android/quickstep/util/MultiValueUpdateListener.java
+++ b/quickstep/src/com/android/quickstep/util/MultiValueUpdateListener.java
@@ -40,10 +40,14 @@
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 93b3482..c331a13 100644
--- a/quickstep/src/com/android/quickstep/util/RectFSpringAnim2.java
+++ b/quickstep/src/com/android/quickstep/util/RectFSpringAnim2.java
@@ -280,7 +280,7 @@
}
@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 00993e3..f67940a 100644
--- a/quickstep/src/com/android/quickstep/views/AllAppsEduView.java
+++ b/quickstep/src/com/android/quickstep/views/AllAppsEduView.java
@@ -173,7 +173,7 @@
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 25cce69..3027db6 100644
--- a/src/com/android/launcher3/views/FloatingIconView.java
+++ b/src/com/android/launcher3/views/FloatingIconView.java
@@ -376,6 +376,7 @@
if (mIconLoadResult.isIconLoaded) {
setIcon(mIconLoadResult.drawable, mIconLoadResult.badge,
mIconLoadResult.btvDrawable, mIconLoadResult.iconOffset);
+ setVisibility(VISIBLE);
setIconAndDotVisible(originalView, false);
} else {
mIconLoadResult.onIconLoaded = () -> {