From 2098381808b82173d3ea4eab9032e0bdaa0b2898 Mon Sep 17 00:00:00 2001 From: Luca Zuccarini Date: Tue, 3 Jan 2023 14:19:48 +0000 Subject: [PATCH] [Toast] Introduce SysUI's animation library to the transition manager. Bug: 250588519 Test: manual, see videos in bug Change-Id: Iccc440c95ecc14d39e35d911798e239b698b950a --- .../launcher3/QuickstepTransitionManager.java | 103 +++++++++++++++++- .../android/launcher3/LauncherSettings.java | 14 +++ .../launcher3/config/FeatureFlags.java | 2 +- .../launcher3/model/data/ItemInfo.java | 18 +++ .../launcher3/touch/ItemClickHandler.java | 3 +- .../launcher3/views/ActivityContext.java | 25 +++++ .../android/launcher3/views/ScrimView.java | 4 + 7 files changed, 162 insertions(+), 7 deletions(-) diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java index 7948ca499d..b880a7e3f3 100644 --- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java +++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java @@ -89,6 +89,7 @@ import android.provider.Settings; import android.util.Pair; import android.util.Size; import android.view.CrossWindowBlurListeners; +import android.view.IRemoteAnimationFinishedCallback; import android.view.RemoteAnimationAdapter; import android.view.RemoteAnimationDefinition; import android.view.RemoteAnimationTarget; @@ -114,6 +115,7 @@ import com.android.launcher3.anim.AnimatorListeners; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.dragndrop.DragLayer; import com.android.launcher3.icons.FastBitmapDrawable; +import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.shortcuts.DeepShortcutView; import com.android.launcher3.statehandlers.DepthController; import com.android.launcher3.taskbar.LauncherTaskbarUIController; @@ -143,6 +145,9 @@ import com.android.quickstep.util.SurfaceTransactionApplier; import com.android.quickstep.util.WorkspaceRevealAnim; import com.android.quickstep.views.FloatingWidgetView; import com.android.quickstep.views.RecentsView; +import com.android.systemui.animation.ActivityLaunchAnimator; +import com.android.systemui.animation.DelegateLaunchAnimatorController; +import com.android.systemui.animation.RemoteAnimationDelegate; import com.android.systemui.shared.system.BlurUtils; import com.android.systemui.shared.system.InteractionJankMonitorWrapper; import com.android.systemui.shared.system.QuickStepContract; @@ -222,7 +227,6 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener private RemoteAnimationProvider mRemoteAnimationProvider; // Strong refs to runners which are cleared when the launcher activity is destroyed private RemoteAnimationFactory mWallpaperOpenRunner; - private RemoteAnimationFactory mAppLaunchRunner; private RemoteAnimationFactory mKeyguardGoingAwayRunner; private RemoteAnimationFactory mWallpaperOpenTransitionRunner; @@ -291,9 +295,18 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener public ActivityOptionsWrapper getActivityLaunchOptions(View v) { boolean fromRecents = isLaunchingFromRecents(v, null /* targets */); RunnableList onEndCallback = new RunnableList(); - mAppLaunchRunner = new AppLaunchAnimationRunner(v, onEndCallback); + + RemoteAnimationFactory delegateRunner = new AppLaunchAnimationRunner(v, onEndCallback); + ItemInfo tag = (ItemInfo) v.getTag(); + if (tag != null && tag.shouldUseBackgroundAnimation()) { + ContainerAnimationRunner containerAnimationRunner = + ContainerAnimationRunner.from(v, mStartingWindowListener); + if (containerAnimationRunner != null) { + delegateRunner = containerAnimationRunner; + } + } RemoteAnimationRunnerCompat runner = new LauncherAnimationRunner( - mHandler, mAppLaunchRunner, true /* startAtFrontOfQueue */); + mHandler, delegateRunner, true /* startAtFrontOfQueue */); // Note that this duration is a guess as we do not know if the animation will be a // recents launch or not for sure until we know the opening app targets. @@ -1160,7 +1173,6 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener // Also clear strong references to the runners registered with the remote animation // definition so we don't have to wait for the system gc mWallpaperOpenRunner = null; - mAppLaunchRunner = null; mKeyguardGoingAwayRunner = null; } } @@ -1754,6 +1766,79 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener } } + /** Remote animation runner to launch an app using System UI's animation library. */ + private static class ContainerAnimationRunner implements RemoteAnimationFactory { + + /** The delegate runner that handles the actual animation. */ + private final RemoteAnimationDelegate mDelegate; + + private ContainerAnimationRunner( + RemoteAnimationDelegate delegate) { + mDelegate = delegate; + } + + @Nullable + private static ContainerAnimationRunner from( + View v, StartingWindowListener startingWindowListener) { + View viewToUse = findViewWithBackground(v); + if (viewToUse == null) { + viewToUse = v; + } + + // TODO(b/265134143): create a CUJ type for interaction jank monitoring. + ActivityLaunchAnimator.Controller controllerDelegate = + ActivityLaunchAnimator.Controller.fromView(viewToUse, null /* cujType */); + + if (controllerDelegate == null) { + return null; + } + + // This wrapper allows us to override the default value, telling the controller that the + // current window is below the animating window. + ActivityLaunchAnimator.Controller controller = + new DelegateLaunchAnimatorController(controllerDelegate) { + @Override + public boolean isBelowAnimatingWindow() { + return true; + } + }; + + ActivityLaunchAnimator.Callback callback = task -> ColorUtils.setAlphaComponent( + startingWindowListener.getBackgroundColor(), 255); + + return new ContainerAnimationRunner( + new ActivityLaunchAnimator.AnimationDelegate(controller, callback)); + } + + /** Finds the closest parent of [view] (inclusive) with a background drawable. */ + @Nullable + private static View findViewWithBackground(View view) { + View current = view; + while (current.getBackground() == null) { + if (!(current.getParent() instanceof View)) { + return null; + } + + current = (View) view.getParent(); + } + + return current; + } + + @Override + public void onAnimationStart(int transit, RemoteAnimationTarget[] appTargets, + RemoteAnimationTarget[] wallpaperTargets, RemoteAnimationTarget[] nonAppTargets, + LauncherAnimationRunner.AnimationResult result) { + mDelegate.onAnimationStart( + transit, appTargets, wallpaperTargets, nonAppTargets, result); + } + + @Override + public void onAnimationCancelled(boolean isKeyguardOccluded) { + mDelegate.onAnimationCancelled(isKeyguardOccluded); + } + } + /** * Class that holds all the variables for the app open animation. */ @@ -1822,8 +1907,9 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener } } - private static class StartingWindowListener extends IStartingWindowListener.Stub { + private class StartingWindowListener extends IStartingWindowListener.Stub { private QuickstepTransitionManager mTransitionManager; + private int mBackgroundColor; public void setTransitionManager(QuickstepTransitionManager transitionManager) { mTransitionManager = transitionManager; @@ -1832,6 +1918,13 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener @Override public void onTaskLaunching(int taskId, int supportedType, int color) { mTransitionManager.mTaskStartParams.put(taskId, Pair.create(supportedType, color)); + mBackgroundColor = color; + } + + public int getBackgroundColor() { + return mBackgroundColor == Color.TRANSPARENT + ? mLauncher.getScrimView().getBackgroundColor() + : mBackgroundColor; } } diff --git a/src/com/android/launcher3/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java index 6ea331d485..66ea616ffe 100644 --- a/src/com/android/launcher3/LauncherSettings.java +++ b/src/com/android/launcher3/LauncherSettings.java @@ -29,6 +29,20 @@ import com.android.launcher3.model.data.ItemInfo; */ public class LauncherSettings { + /** + * Types of animations. + */ + public static final class Animation { + /** + * The default animation for a given view/item info type. + */ + public static final int DEFAULT = 0; + /** + * An animation using the view's background. + */ + public static final int VIEW_BACKGROUND = 1; + } + /** * Favorites. */ diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java index c90c6bfaec..2841129673 100644 --- a/src/com/android/launcher3/config/FeatureFlags.java +++ b/src/com/android/launcher3/config/FeatureFlags.java @@ -272,7 +272,7 @@ public final class FeatureFlags { public static final BooleanFlag ENABLE_SEARCH_RESULT_LAUNCH_TRANSITION = new DeviceFlag( "ENABLE_SEARCH_RESULT_LAUNCH_TRANSITION", false, - "Enable option to launch search results using the new standardized transitions"); + "Enable option to launch search results using the new view container transitions"); public static final BooleanFlag TWO_PREDICTED_ROWS_ALL_APPS_SEARCH = new DeviceFlag( "TWO_PREDICTED_ROWS_ALL_APPS_SEARCH", false, diff --git a/src/com/android/launcher3/model/data/ItemInfo.java b/src/com/android/launcher3/model/data/ItemInfo.java index 2dd44a4000..4eb2e9edc2 100644 --- a/src/com/android/launcher3/model/data/ItemInfo.java +++ b/src/com/android/launcher3/model/data/ItemInfo.java @@ -47,8 +47,10 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.launcher3.LauncherSettings; +import com.android.launcher3.LauncherSettings.Animation; import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.Workspace; +import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.logger.LauncherAtom; import com.android.launcher3.logger.LauncherAtom.AllAppsContainer; import com.android.launcher3.logger.LauncherAtom.ContainerInfo; @@ -93,6 +95,12 @@ public class ItemInfo { */ public int itemType; + /** + * One of {@link Animation#DEFAULT}, + * {@link Animation#VIEW_BACKGROUND}. + */ + public int animationType = Animation.DEFAULT; + /** * The id of the container that holds this item. For the desktop, this will be * {@link Favorites#CONTAINER_DESKTOP}. For the all applications folder it @@ -185,6 +193,7 @@ public class ItemInfo { rank = info.rank; screenId = info.screenId; itemType = info.itemType; + animationType = info.animationType; container = info.container; user = info.user; contentDescription = info.contentDescription; @@ -297,6 +306,15 @@ public class ItemInfo { return container == CONTAINER_HOTSEAT_PREDICTION || container == CONTAINER_PREDICTION; } + /** + * Returns whether this item should use the background animation. + */ + public boolean shouldUseBackgroundAnimation() { + return animationType == LauncherSettings.Animation.VIEW_BACKGROUND + && FeatureFlags.ENABLE_SEARCH_RESULT_BACKGROUND_DRAWABLES.get() + && FeatureFlags.ENABLE_SEARCH_RESULT_LAUNCH_TRANSITION.get(); + } + /** * Creates {@link LauncherAtom.ItemInfo} with important fields and parent container info. */ diff --git a/src/com/android/launcher3/touch/ItemClickHandler.java b/src/com/android/launcher3/touch/ItemClickHandler.java index 64951ca4d3..790c226a11 100644 --- a/src/com/android/launcher3/touch/ItemClickHandler.java +++ b/src/com/android/launcher3/touch/ItemClickHandler.java @@ -343,7 +343,8 @@ public class ItemClickHandler { return; } } - if (v != null && launcher.supportsAdaptiveIconAnimation(v)) { + if (v != null && launcher.supportsAdaptiveIconAnimation(v) + && !item.shouldUseBackgroundAnimation()) { // Preload the icon to reduce latency b/w swapping the floating view with the original. FloatingIconView.fetchIcon(launcher, v, item, true /* isOpening */); } diff --git a/src/com/android/launcher3/views/ActivityContext.java b/src/com/android/launcher3/views/ActivityContext.java index 79b4cb4da4..36f5f4d217 100644 --- a/src/com/android/launcher3/views/ActivityContext.java +++ b/src/com/android/launcher3/views/ActivityContext.java @@ -23,6 +23,7 @@ import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; import android.app.ActivityOptions; +import android.app.PendingIntent; import android.content.ActivityNotFoundException; import android.content.Context; import android.content.ContextWrapper; @@ -45,6 +46,7 @@ import android.view.WindowInsetsController; import android.view.inputmethod.InputMethodManager; import android.widget.Toast; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.launcher3.BubbleTextView; @@ -270,6 +272,29 @@ public interface ActivityContext { } } + + /** + * Sends a pending intent animating from a view. + * + * @param v View to animate. + * @param intent The pending intent being launched. + * @param item Item associated with the view. + * @return {@code true} if the intent is sent successfully. + */ + default boolean sendPendingIntentWithAnimation( + @NonNull View v, PendingIntent intent, @Nullable ItemInfo item) { + Bundle optsBundle = getActivityLaunchOptions(v, item).toBundle(); + try { + intent.send(null, 0, null, null, null, null, optsBundle); + return true; + } catch (PendingIntent.CanceledException e) { + Toast.makeText(v.getContext(), + v.getContext().getResources().getText(R.string.shortcut_not_available), + Toast.LENGTH_SHORT).show(); + } + return false; + } + /** * Safely starts an activity. * diff --git a/src/com/android/launcher3/views/ScrimView.java b/src/com/android/launcher3/views/ScrimView.java index 870ff122e1..ca80c516f0 100644 --- a/src/com/android/launcher3/views/ScrimView.java +++ b/src/com/android/launcher3/views/ScrimView.java @@ -77,6 +77,10 @@ public class ScrimView extends View implements Insettable { super.setBackgroundColor(color); } + public int getBackgroundColor() { + return mBackgroundColor; + } + @Override public void onVisibilityAggregated(boolean isVisible) { super.onVisibilityAggregated(isVisible);