Use the splash screen background color as the fallback background color

For the widget transition animation fallback background color, use either
the splash screen background color if a task is starting or the task
description-attached background color.

With a background in place, allow the window to fully fade out before
fading in the widget, by moving the window alpha math into an animation
factory-specific implementation.

Bug: 187706750
Test: Manual
Change-Id: I2b5a7783b0585d447ad60534bc48d2e2176877ed
diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index 36322ce..26d407f 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -214,8 +214,9 @@
         }
     };
 
+    // Pairs of window starting type and starting window background color for starting tasks
     // Will never be larger than MAX_NUM_TASKS
-    private LinkedHashMap<Integer, Integer> mTypeForTaskId;
+    private LinkedHashMap<Integer, Pair<Integer, Integer>> mTaskStartParams;
 
     public QuickstepTransitionManager(Context context) {
         mLauncher = Launcher.cast(Launcher.getLauncher(context));
@@ -232,9 +233,9 @@
         mLauncher.addOnDeviceProfileChangeListener(this);
 
         if (supportsSSplashScreen()) {
-            mTypeForTaskId = new LinkedHashMap<Integer, Integer>(MAX_NUM_TASKS) {
+            mTaskStartParams = new LinkedHashMap<Integer, Pair<Integer, Integer>>(MAX_NUM_TASKS) {
                 @Override
-                protected boolean removeEldestEntry(Entry<Integer, Integer> entry) {
+                protected boolean removeEldestEntry(Entry<Integer, Pair<Integer, Integer>> entry) {
                     return size() > MAX_NUM_TASKS;
                 }
             };
@@ -420,15 +421,6 @@
         return bounds;
     }
 
-    private int getOpeningTaskId(RemoteAnimationTargetCompat[] appTargets) {
-        for (RemoteAnimationTargetCompat target : appTargets) {
-            if (target.mode == MODE_OPENING) {
-                return target.taskId;
-            }
-        }
-        return -1;
-    }
-
     public void setRemoteAnimationProvider(final RemoteAnimationProvider animationProvider,
             CancellationSignal cancellationSignal) {
         mRemoteAnimationProvider = animationProvider;
@@ -595,10 +587,12 @@
 
         final boolean hasSplashScreen;
         if (supportsSSplashScreen()) {
-            int taskId = getOpeningTaskId(appTargets);
-            int type = mTypeForTaskId.getOrDefault(taskId, STARTING_WINDOW_TYPE_NONE);
-            mTypeForTaskId.remove(taskId);
-            hasSplashScreen = type == STARTING_WINDOW_TYPE_SPLASH_SCREEN;
+            int taskId = openingTargets.getFirstAppTargetTaskId();
+            Pair<Integer, Integer> defaultParams = Pair.create(STARTING_WINDOW_TYPE_NONE, 0);
+            Pair<Integer, Integer> taskParams =
+                    mTaskStartParams.getOrDefault(taskId, defaultParams);
+            mTaskStartParams.remove(taskId);
+            hasSplashScreen = taskParams.first == STARTING_WINDOW_TYPE_SPLASH_SCREEN;
         } else {
             hasSplashScreen = false;
         }
@@ -799,18 +793,30 @@
         final RectF widgetBackgroundBounds = new RectF();
         final Rect appWindowCrop = new Rect();
         final Matrix matrix = new Matrix();
+        RemoteAnimationTargets openingTargets = new RemoteAnimationTargets(appTargets,
+                wallpaperTargets, nonAppTargets, MODE_OPENING);
+
+        RemoteAnimationTargetCompat openingTarget = openingTargets.getFirstAppTarget();
+        int fallbackBackgroundColor = 0;
+        if (openingTarget != null && supportsSSplashScreen()) {
+            fallbackBackgroundColor = mTaskStartParams.containsKey(openingTarget.taskId)
+                    ? mTaskStartParams.get(openingTarget.taskId).second : 0;
+            mTaskStartParams.remove(openingTarget.taskId);
+        }
+        if (fallbackBackgroundColor == 0) {
+            fallbackBackgroundColor =
+                    FloatingWidgetView.getDefaultBackgroundColor(mLauncher, openingTarget);
+        }
 
         final float finalWindowRadius = mDeviceProfile.isMultiWindowMode
                 ? 0 : getWindowCornerRadius(mLauncher.getResources());
         final FloatingWidgetView floatingView = FloatingWidgetView.getFloatingWidgetView(mLauncher,
                 v, widgetBackgroundBounds,
                 new Size(windowTargetBounds.width(), windowTargetBounds.height()),
-                finalWindowRadius, appTargetsAreTranslucent);
+                finalWindowRadius, appTargetsAreTranslucent, fallbackBackgroundColor);
         final float initialWindowRadius = supportsRoundedCornersOnWindows(mLauncher.getResources())
                 ? floatingView.getInitialCornerRadius() : 0;
 
-        RemoteAnimationTargets openingTargets = new RemoteAnimationTargets(appTargets,
-                wallpaperTargets, nonAppTargets, MODE_OPENING);
         SurfaceTransactionApplier surfaceApplier = new SurfaceTransactionApplier(floatingView);
         openingTargets.addReleaseCheck(surfaceApplier);
 
@@ -1434,8 +1440,8 @@
         }
 
         @Override
-        public void onTaskLaunching(int taskId, int supportedType) {
-            mTransitionManager.mTypeForTaskId.put(taskId, supportedType);
+        public void onTaskLaunching(int taskId, int supportedType, int color) {
+            mTransitionManager.mTaskStartParams.put(taskId, Pair.create(supportedType, color));
         }
     }
 }
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 88db274..52f34d9 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -1071,7 +1071,8 @@
     }
 
     protected abstract HomeAnimationFactory createHomeAnimationFactory(
-            ArrayList<IBinder> launchCookies, long duration, boolean isTargetTranslucent);
+            ArrayList<IBinder> launchCookies, long duration, boolean isTargetTranslucent,
+            RemoteAnimationTargetCompat runningTaskTarget);
 
     private final TaskStackChangeListener mActivityRestartListener = new TaskStackChangeListener() {
         @Override
@@ -1117,7 +1118,7 @@
                     : new ArrayList<>();
             boolean isTranslucent = runningTaskTarget != null && runningTaskTarget.isTranslucent;
             HomeAnimationFactory homeAnimFactory =
-                    createHomeAnimationFactory(cookies, duration, isTranslucent);
+                    createHomeAnimationFactory(cookies, duration, isTranslucent, runningTaskTarget);
             mIsSwipingPipToHome = homeAnimFactory.supportSwipePipToHome()
                     && runningTaskTarget != null
                     && runningTaskTarget.taskInfo.pictureInPictureParams != null
diff --git a/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java b/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
index 2d81429..7290ff6 100644
--- a/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
@@ -129,7 +129,8 @@
 
     @Override
     protected HomeAnimationFactory createHomeAnimationFactory(ArrayList<IBinder> launchCookies,
-            long duration, boolean isTargetTranslucent) {
+            long duration, boolean isTargetTranslucent,
+            RemoteAnimationTargetCompat runningTaskTarget) {
         mActiveAnimationFactory = new FallbackHomeAnimationFactory(duration);
         ActivityOptions options = ActivityOptions.makeCustomAnimation(mContext, 0, 0);
         Intent intent = new Intent(mGestureState.getHomeIntent());
diff --git a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
index 1bae1c5..40741e4 100644
--- a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
+++ b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
@@ -20,6 +20,9 @@
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.Utilities.boundToRange;
 import static com.android.launcher3.Utilities.dpToPx;
+import static com.android.launcher3.Utilities.mapBoundToRange;
+import static com.android.launcher3.anim.Interpolators.EXAGGERATED_EASE;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.config.FeatureFlags.PROTOTYPE_APP_CLOSE;
 import static com.android.launcher3.model.data.ItemInfo.NO_MATCHING_ID;
 import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION;
@@ -65,6 +68,7 @@
 import com.android.quickstep.views.TaskView;
 import com.android.systemui.plugins.ResourceProvider;
 import com.android.systemui.shared.system.InputConsumerController;
+import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 
 import java.util.ArrayList;
 
@@ -84,7 +88,8 @@
 
     @Override
     protected HomeAnimationFactory createHomeAnimationFactory(ArrayList<IBinder> launchCookies,
-            long duration, boolean isTargetTranslucent) {
+            long duration, boolean isTargetTranslucent,
+            RemoteAnimationTargetCompat runningTaskTarget) {
         if (mActivity == null) {
             mStateCallback.addChangeListener(STATE_LAUNCHER_PRESENT | STATE_HANDLER_INVALIDATED,
                     isPresent -> mRecentsView.startHome());
@@ -108,7 +113,7 @@
         }
         if (workspaceView instanceof LauncherAppWidgetHostView) {
             return createWidgetHomeAnimationFactory((LauncherAppWidgetHostView) workspaceView,
-                    isTargetTranslucent);
+                    isTargetTranslucent, runningTaskTarget);
         }
         return createIconHomeAnimationFactory(workspaceView);
     }
@@ -169,15 +174,19 @@
     }
 
     private HomeAnimationFactory createWidgetHomeAnimationFactory(
-            LauncherAppWidgetHostView hostView, boolean isTargetTranslucent) {
-
+            LauncherAppWidgetHostView hostView, boolean isTargetTranslucent,
+            RemoteAnimationTargetCompat runningTaskTarget) {
+        final float floatingWidgetAlpha = isTargetTranslucent ? 0 : 1;
         RectF backgroundLocation = new RectF();
         Rect crop = new Rect();
         mTaskViewSimulator.getCurrentCropRect().roundOut(crop);
         Size windowSize = new Size(crop.width(), crop.height());
+        int fallbackBackgroundColor =
+                FloatingWidgetView.getDefaultBackgroundColor(mContext, runningTaskTarget);
         FloatingWidgetView floatingWidgetView = FloatingWidgetView.getFloatingWidgetView(mActivity,
                 hostView, backgroundLocation, windowSize,
-                mTaskViewSimulator.getCurrentCornerRadius(), isTargetTranslucent);
+                mTaskViewSimulator.getCurrentCornerRadius(), isTargetTranslucent,
+                fallbackBackgroundColor);
 
         return new FloatingViewHomeAnimationFactory(floatingWidgetView) {
 
@@ -207,12 +216,20 @@
             }
 
             @Override
-            public void update(@Nullable AppCloseConfig config, RectF currentRect,
-                    float progress, float radius) {
+            public void update(@Nullable AppCloseConfig config, RectF currentRect, float progress,
+                    float radius) {
                 super.update(config, currentRect, progress, radius);
-                floatingWidgetView.update(currentRect, 1 /* floatingWidgetAlpha */,
-                        config != null ? config.getFgAlpha() : 1f /* foregroundAlpha */,
-                        0 /* fallbackBackgroundAlpha */, 1 - progress);
+                final float fallbackBackgroundAlpha =
+                        1 - mapBoundToRange(progress, 0.8f, 1, 0, 1, EXAGGERATED_EASE);
+                final float foregroundAlpha =
+                        mapBoundToRange(progress, 0.5f, 1, 0, 1, EXAGGERATED_EASE);
+                floatingWidgetView.update(currentRect, floatingWidgetAlpha, foregroundAlpha,
+                        fallbackBackgroundAlpha, 1 - progress);
+            }
+
+            @Override
+            protected float getWindowAlpha(float progress) {
+                return 1 - mapBoundToRange(progress, 0, 0.5f, 0, 1, LINEAR);
             }
         };
     }
diff --git a/quickstep/src/com/android/quickstep/RemoteAnimationTargets.java b/quickstep/src/com/android/quickstep/RemoteAnimationTargets.java
index edc3ab2..c032889 100644
--- a/quickstep/src/com/android/quickstep/RemoteAnimationTargets.java
+++ b/quickstep/src/com/android/quickstep/RemoteAnimationTargets.java
@@ -85,6 +85,17 @@
         return null;
     }
 
+    /** Returns the first opening app target. */
+    public RemoteAnimationTargetCompat getFirstAppTarget() {
+        return apps.length > 0 ? apps[0] : null;
+    }
+
+    /** Returns the task id of the first opening app target, or -1 if none is found. */
+    public int getFirstAppTargetTaskId() {
+        RemoteAnimationTargetCompat target = getFirstAppTarget();
+        return target == null ? -1 : target.taskId;
+    }
+
     public boolean isAnimatingHome() {
         for (RemoteAnimationTargetCompat target : unfilteredApps) {
             if (target.activityType == RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME) {
diff --git a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
index b79e934..4495455 100644
--- a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
+++ b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
@@ -181,6 +181,24 @@
         public boolean supportSwipePipToHome() {
             return false;
         }
+
+        /**
+         * @param progress The progress of the animation to the home screen.
+         * @return The current alpha to set on the animating app window.
+         */
+        protected float getWindowAlpha(float progress) {
+            // Alpha interpolates between [1, 0] between progress values [start, end]
+            final float start = 0f;
+            final float end = 0.85f;
+
+            if (progress <= start) {
+                return 1f;
+            }
+            if (progress >= end) {
+                return 0f;
+            }
+            return Utilities.mapToRange(progress, start, end, 1, 0, ACCEL_1_5);
+        }
     }
 
     /**
@@ -236,24 +254,6 @@
         return anim;
     }
 
-    /**
-     * @param progress The progress of the animation to the home screen.
-     * @return The current alpha to set on the animating app window.
-     */
-    protected float getWindowAlpha(float progress) {
-        // Alpha interpolates between [1, 0] between progress values [start, end]
-        final float start = 0f;
-        final float end = 0.85f;
-
-        if (progress <= start) {
-            return 1f;
-        }
-        if (progress >= end) {
-            return 0f;
-        }
-        return Utilities.mapToRange(progress, start, end, 1, 0, ACCEL_1_5);
-    }
-
     protected class SpringAnimationRunner extends AnimationSuccessListener
             implements RectFSpringAnim.OnUpdateListener, BuilderProxy {
 
@@ -292,7 +292,7 @@
 
             mMatrix.setRectToRect(mCropRectF, mWindowCurrentRect, ScaleToFit.FILL);
             float cornerRadius = Utilities.mapRange(progress, mStartRadius, mEndRadius);
-            float alpha = getWindowAlpha(progress);
+            float alpha = mAnimationFactory.getWindowAlpha(progress);
             if (config != null && PROTOTYPE_APP_CLOSE.get()) {
                 alpha = config.getWindowAlpha();
                 cornerRadius = config.getCornerRadius();
diff --git a/quickstep/src/com/android/quickstep/views/FloatingWidgetBackgroundView.java b/quickstep/src/com/android/quickstep/views/FloatingWidgetBackgroundView.java
index 9ea2369..65dba33 100644
--- a/quickstep/src/com/android/quickstep/views/FloatingWidgetBackgroundView.java
+++ b/quickstep/src/com/android/quickstep/views/FloatingWidgetBackgroundView.java
@@ -27,7 +27,6 @@
 import android.view.ViewOutlineProvider;
 import android.widget.RemoteViews.RemoteViewOutlineProvider;
 
-import com.android.launcher3.util.Themes;
 import com.android.launcher3.widget.LauncherAppWidgetHostView;
 import com.android.launcher3.widget.RoundedCornerEnforcement;
 
@@ -62,7 +61,8 @@
         setClipToOutline(true);
     }
 
-    void init(LauncherAppWidgetHostView hostView, View backgroundView, float finalRadius) {
+    void init(LauncherAppWidgetHostView hostView, View backgroundView, float finalRadius,
+            int fallbackBackgroundColor) {
         mFinalRadius = finalRadius;
         mSourceView = backgroundView;
         mInitialOutlineRadius = getOutlineRadius(hostView, backgroundView);
@@ -81,7 +81,7 @@
             setBackground(mBackgroundProperties.mDrawable);
             mSourceView.setBackground(null);
         } else if (mOriginalForeground == null) {
-            mFallbackDrawable.setColor(Themes.getColorBackground(backgroundView.getContext()));
+            mFallbackDrawable.setColor(fallbackBackgroundColor);
             setBackground(mFallbackDrawable);
             mIsUsingFallback = true;
         }
diff --git a/quickstep/src/com/android/quickstep/views/FloatingWidgetView.java b/quickstep/src/com/android/quickstep/views/FloatingWidgetView.java
index 0012dd8..22ce942 100644
--- a/quickstep/src/com/android/quickstep/views/FloatingWidgetView.java
+++ b/quickstep/src/com/android/quickstep/views/FloatingWidgetView.java
@@ -34,10 +34,12 @@
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.dragndrop.DragLayer;
+import com.android.launcher3.util.Themes;
 import com.android.launcher3.views.FloatingView;
 import com.android.launcher3.views.ListenerView;
 import com.android.launcher3.widget.LauncherAppWidgetHostView;
 import com.android.launcher3.widget.RoundedCornerEnforcement;
+import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 
 /** A view that mimics an App Widget through a launch animation. */
 @TargetApi(Build.VERSION_CODES.S)
@@ -148,7 +150,7 @@
 
     private void init(DragLayer dragLayer, LauncherAppWidgetHostView originalView,
             RectF widgetBackgroundPosition, Size windowSize, float windowCornerRadius,
-            boolean appTargetIsTranslucent) {
+            boolean appTargetIsTranslucent, int fallbackBackgroundColor) {
         mAppWidgetView = originalView;
         mAppWidgetView.beginDeferringUpdates();
         mBackgroundPosition = widgetBackgroundPosition;
@@ -163,7 +165,8 @@
         getRelativePosition(mAppWidgetBackgroundView, dragLayer, mBackgroundPosition);
         getRelativePosition(mAppWidgetBackgroundView, mAppWidgetView, mBackgroundOffset);
         if (!mAppTargetIsTranslucent) {
-            mBackgroundView.init(mAppWidgetView, mAppWidgetBackgroundView, windowCornerRadius);
+            mBackgroundView.init(mAppWidgetView, mAppWidgetBackgroundView, windowCornerRadius,
+                    fallbackBackgroundColor);
             // Layout call before GhostView creation so that the overlaid view isn't clipped
             layout(0, 0, windowSize.getWidth(), windowSize.getHeight());
             mForegroundOverlayView = GhostView.addGhost(mAppWidgetView, this);
@@ -274,7 +277,8 @@
      */
     public static FloatingWidgetView getFloatingWidgetView(Launcher launcher,
             LauncherAppWidgetHostView originalView, RectF widgetBackgroundPosition,
-            Size windowSize, float windowCornerRadius, boolean appTargetsAreTranslucent) {
+            Size windowSize, float windowCornerRadius, boolean appTargetsAreTranslucent,
+            int fallbackBackgroundColor) {
         final DragLayer dragLayer = launcher.getDragLayer();
         ViewGroup parent = (ViewGroup) dragLayer.getParent();
         FloatingWidgetView floatingView =
@@ -282,11 +286,22 @@
         floatingView.recycle();
 
         floatingView.init(dragLayer, originalView, widgetBackgroundPosition, windowSize,
-                windowCornerRadius, appTargetsAreTranslucent);
+                windowCornerRadius, appTargetsAreTranslucent, fallbackBackgroundColor);
         parent.addView(floatingView);
         return floatingView;
     }
 
+    /**
+     * Extract a background color from a target's task description, or fall back to the given
+     * context's theme background color.
+     */
+    public static int getDefaultBackgroundColor(
+            Context context, RemoteAnimationTargetCompat target) {
+        return (target != null && target.taskInfo.taskDescription != null)
+                ? target.taskInfo.taskDescription.getBackgroundColor()
+                : Themes.getColorBackground(context);
+    }
+
     private static void getRelativePosition(View descendant, View ancestor, RectF position) {
         float[] points = new float[]{0, 0, descendant.getWidth(), descendant.getHeight()};
         Utilities.getDescendantCoordRelativeToAncestor(descendant, ancestor, points,
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index cb9e1f3..a2d86bc 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -397,6 +397,13 @@
         return mapRange(interpolator.getInterpolation(progress), toMin, toMax);
     }
 
+    /** Bounds t between a lower and upper bound and maps the result to a range. */
+    public static float mapBoundToRange(float t, float lowerBound, float upperBound,
+            float toMin, float toMax, Interpolator interpolator) {
+        return mapToRange(boundToRange(t, lowerBound, upperBound), lowerBound, upperBound,
+                toMin, toMax, interpolator);
+    }
+
     public static float getProgress(float current, float min, float max) {
         return Math.abs(current - min) / Math.abs(max - min);
     }