Optimize hot launching recents

Rearrange how we generate the transition specs, which involves
creating a thumbnail on the mainthread (about 10ms on large
devices): First, we put launching the activity onto a handler
thread (with default priority), to free up the main thread. Then,
we immediately start generating the thumbnail such that when the
future calls us we have the generated spec already handy.

For that we need to be able to supply a specs future into
ActivityOptions, to avoid race conditions. Furthermore we need to
make sure not to call into WM while creating specs, to avoid WM
lock contention.

Test: App -> Recents -> Same app, inspect app transition logs
Test: Double tap recents for quick switching

Bug: 32668632
Change-Id: I6001e29145f8e56deb9c4ead46c53c87c9191436
Merged-In: Ic6ec65c2560f67cade3b5ddde9f79ee13e9ba32c
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 3eec596..53608fb 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -38,6 +38,7 @@
 import android.util.Pair;
 import android.util.Slog;
 import android.view.AppTransitionAnimationSpec;
+import android.view.IAppTransitionAnimationSpecsFuture;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.Window;
@@ -213,6 +214,7 @@
 
     private static final String KEY_INSTANT_APP_VERIFICATION_BUNDLE
             = "android:instantapps.installerbundle";
+    private static final String KEY_SPECS_FUTURE = "android:activity.specsFuture";
 
     /** @hide */
     public static final int ANIM_NONE = 0;
@@ -268,6 +270,7 @@
     private AppTransitionAnimationSpec mAnimSpecs[];
     private int mRotationAnimationHint = -1;
     private Bundle mAppVerificationBundle;
+    private IAppTransitionAnimationSpecsFuture mSpecsFuture;
 
     /**
      * Create an ActivityOptions specifying a custom animation to run when
@@ -492,35 +495,12 @@
      * is not executed, the callback will happen immediately.
      * @return Returns a new ActivityOptions object that you can use to
      * supply these options as the options Bundle when starting an activity.
-     * @hide
      */
-    public static ActivityOptions makeThumbnailScaleUpAnimation(View source,
+    private static ActivityOptions makeThumbnailScaleUpAnimation(View source,
             Bitmap thumbnail, int startX, int startY, OnAnimationStartedListener listener) {
         return makeThumbnailAnimation(source, thumbnail, startX, startY, listener, true);
     }
 
-    /**
-     * Create an ActivityOptions specifying an animation where an activity window
-     * is scaled from a given position to a thumbnail at a specified location.
-     *
-     * @param source The View that this thumbnail is animating to.  This
-     * defines the coordinate space for <var>startX</var> and <var>startY</var>.
-     * @param thumbnail The bitmap that will be shown as the final thumbnail
-     * of the animation.
-     * @param startX The x end location of the bitmap, relative to <var>source</var>.
-     * @param startY The y end location of the bitmap, relative to <var>source</var>.
-     * @param listener Optional OnAnimationStartedListener to find out when the
-     * requested animation has started running.  If for some reason the animation
-     * is not executed, the callback will happen immediately.
-     * @return Returns a new ActivityOptions object that you can use to
-     * supply these options as the options Bundle when starting an activity.
-     * @hide
-     */
-    public static ActivityOptions makeThumbnailScaleDownAnimation(View source,
-            Bitmap thumbnail, int startX, int startY, OnAnimationStartedListener listener) {
-        return makeThumbnailAnimation(source, thumbnail, startX, startY, listener, false);
-    }
-
     private static ActivityOptions makeThumbnailAnimation(View source,
             Bitmap thumbnail, int startX, int startY, OnAnimationStartedListener listener,
             boolean scaleUp) {
@@ -537,29 +517,21 @@
     }
 
     /**
-     * Create an ActivityOptions specifying an animation where the new activity
-     * window and a thumbnail is aspect-scaled to a new location.
-     *
-     * @param source The View that this thumbnail is animating from.  This
-     * defines the coordinate space for <var>startX</var> and <var>startY</var>.
-     * @param thumbnail The bitmap that will be shown as the initial thumbnail
-     * of the animation.
-     * @param startX The x starting location of the bitmap, relative to <var>source</var>.
-     * @param startY The y starting location of the bitmap, relative to <var>source</var>.
-     * @param handler If <var>listener</var> is non-null this must be a valid
-     * Handler on which to dispatch the callback; otherwise it should be null.
-     * @param listener Optional OnAnimationStartedListener to find out when the
-     * requested animation has started running.  If for some reason the animation
-     * is not executed, the callback will happen immediately.
-     * @return Returns a new ActivityOptions object that you can use to
-     * supply these options as the options Bundle when starting an activity.
+     * Create an ActivityOptions specifying an animation where a list of activity windows and
+     * thumbnails are aspect scaled to/from a new location.
      * @hide
      */
-    public static ActivityOptions makeThumbnailAspectScaleUpAnimation(View source,
-            Bitmap thumbnail, int startX, int startY, int targetWidth, int targetHeight,
-            Handler handler, OnAnimationStartedListener listener) {
-        return makeAspectScaledThumbnailAnimation(source, thumbnail, startX, startY,
-                targetWidth, targetHeight, handler, listener, true);
+    public static ActivityOptions makeMultiThumbFutureAspectScaleAnimation(Context context,
+            Handler handler, IAppTransitionAnimationSpecsFuture specsFuture,
+            OnAnimationStartedListener listener, boolean scaleUp) {
+        ActivityOptions opts = new ActivityOptions();
+        opts.mPackageName = context.getPackageName();
+        opts.mAnimationType = scaleUp
+                ? ANIM_THUMBNAIL_ASPECT_SCALE_UP
+                : ANIM_THUMBNAIL_ASPECT_SCALE_DOWN;
+        opts.mSpecsFuture = specsFuture;
+        opts.setOnAnimationStartedListener(handler, listener);
+        return opts;
     }
 
     /**
@@ -891,6 +863,10 @@
         }
         mRotationAnimationHint = opts.getInt(KEY_ROTATION_ANIMATION_HINT);
         mAppVerificationBundle = opts.getBundle(KEY_INSTANT_APP_VERIFICATION_BUNDLE);
+        if (opts.containsKey(KEY_SPECS_FUTURE)) {
+            mSpecsFuture = IAppTransitionAnimationSpecsFuture.Stub.asInterface(opts.getBinder(
+                    KEY_SPECS_FUTURE));
+        }
     }
 
     /**
@@ -1029,6 +1005,11 @@
     public AppTransitionAnimationSpec[] getAnimSpecs() { return mAnimSpecs; }
 
     /** @hide */
+    public IAppTransitionAnimationSpecsFuture getSpecsFuture() {
+        return mSpecsFuture;
+    }
+
+    /** @hide */
     public static ActivityOptions fromBundle(Bundle bOptions) {
         return bOptions != null ? new ActivityOptions(bOptions) : null;
     }
@@ -1205,6 +1186,7 @@
         }
         mAnimSpecs = otherOptions.mAnimSpecs;
         mAnimationFinishedListener = otherOptions.mAnimationFinishedListener;
+        mSpecsFuture = otherOptions.mSpecsFuture;
     }
 
     /**
@@ -1279,6 +1261,9 @@
         if (mAnimationFinishedListener != null) {
             b.putBinder(KEY_ANIMATION_FINISHED_LISTENER, mAnimationFinishedListener.asBinder());
         }
+        if (mSpecsFuture != null) {
+            b.putBinder(KEY_SPECS_FUTURE, mSpecsFuture.asBinder());
+        }
         b.putInt(KEY_ROTATION_ANIMATION_HINT, mRotationAnimationHint);
         if (mAppVerificationBundle != null) {
             b.putBundle(KEY_INSTANT_APP_VERIFICATION_BUNDLE, mAppVerificationBundle);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
index 2b812a5..2522d0f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
@@ -477,7 +477,8 @@
 
         // Launch the task
         ssp.startActivityFromRecents(
-                mContext, toTask.key, toTask.title, launchOpts, INVALID_STACK_ID);
+                mContext, toTask.key, toTask.title, launchOpts, INVALID_STACK_ID,
+                null /* resultListener */);
     }
 
     /**
@@ -550,7 +551,8 @@
 
         // Launch the task
         ssp.startActivityFromRecents(
-                mContext, toTask.key, toTask.title, launchOpts, INVALID_STACK_ID);
+                mContext, toTask.key, toTask.title, launchOpts, INVALID_STACK_ID,
+                null /* resultListener */);
     }
 
     public void showNextAffiliatedTask() {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
index 8594ec62..1dc3c82 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -26,6 +26,7 @@
 import static android.provider.Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.ActivityManager.StackInfo;
 import android.app.ActivityManager.TaskSnapshot;
@@ -70,6 +71,7 @@
 import android.util.IconDrawableFactory;
 import android.util.Log;
 import android.util.MutableBoolean;
+import android.view.AppTransitionAnimationSpec;
 import android.view.Display;
 import android.view.IAppTransitionAnimationSpecsFuture;
 import android.view.IDockedStackListener;
@@ -89,6 +91,7 @@
 import com.android.systemui.recents.RecentsImpl;
 import com.android.systemui.recents.model.Task;
 import com.android.systemui.recents.model.ThumbnailData;
+import com.android.systemui.recents.views.RecentsTransitionHelper.AnimationSpecComposer;
 
 import java.io.IOException;
 import java.util.ArrayList;
@@ -1116,31 +1119,44 @@
     }
 
     /** Starts an activity from recents. */
-    public boolean startActivityFromRecents(Context context, Task.TaskKey taskKey, String taskName,
-            ActivityOptions options, int stackId) {
-        if (mIam != null) {
-            try {
-                if (taskKey.stackId == DOCKED_STACK_ID) {
-                    // We show non-visible docked tasks in Recents, but we always want to launch
-                    // them in the fullscreen stack.
-                    if (options == null) {
-                        options = ActivityOptions.makeBasic();
-                    }
-                    options.setLaunchStackId(FULLSCREEN_WORKSPACE_STACK_ID);
-                } else if (stackId != INVALID_STACK_ID){
-                    if (options == null) {
-                        options = ActivityOptions.makeBasic();
-                    }
-                    options.setLaunchStackId(stackId);
-                }
-                mIam.startActivityFromRecents(
-                        taskKey.id, options == null ? null : options.toBundle());
-                return true;
-            } catch (Exception e) {
-                Log.e(TAG, context.getString(R.string.recents_launch_error_message, taskName), e);
-            }
+    public void startActivityFromRecents(Context context, Task.TaskKey taskKey, String taskName,
+            ActivityOptions options, int stackId,
+            @Nullable final StartActivityFromRecentsResultListener resultListener) {
+        if (mIam == null) {
+            return;
         }
-        return false;
+        if (taskKey.stackId == DOCKED_STACK_ID) {
+            // We show non-visible docked tasks in Recents, but we always want to launch
+            // them in the fullscreen stack.
+            if (options == null) {
+                options = ActivityOptions.makeBasic();
+            }
+            options.setLaunchStackId(FULLSCREEN_WORKSPACE_STACK_ID);
+        } else if (stackId != INVALID_STACK_ID) {
+            if (options == null) {
+                options = ActivityOptions.makeBasic();
+            }
+            options.setLaunchStackId(stackId);
+        }
+        final ActivityOptions finalOptions = options;
+
+        // Execute this from another thread such that we can do other things (like caching the
+        // bitmap for the thumbnail) while AM is busy starting our activity.
+        mOnewayExecutor.submit(() -> {
+            try {
+                mIam.startActivityFromRecents(
+                        taskKey.id, finalOptions == null ? null : finalOptions.toBundle());
+                if (resultListener != null) {
+                    mHandler.post(() -> resultListener.onStartActivityResult(true));
+                }
+            } catch (Exception e) {
+                Log.e(TAG, context.getString(
+                        R.string.recents_launch_error_message, taskName), e);
+                if (resultListener != null) {
+                    mHandler.post(() -> resultListener.onStartActivityResult(false));
+                }
+            }
+        });
     }
 
     /** Starts an in-place animation on the front most application windows. */
@@ -1258,6 +1274,10 @@
         }
     }
 
+    public interface StartActivityFromRecentsResultListener {
+        void onStartActivityResult(boolean succeeded);
+    }
+
     private final class H extends Handler {
         private static final int ON_TASK_STACK_CHANGED = 1;
         private static final int ON_TASK_SNAPSHOT_CHANGED = 2;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
index 8882cab..d7d264e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
@@ -101,57 +101,49 @@
      */
     public void launchTaskFromRecents(final TaskStack stack, @Nullable final Task task,
             final TaskStackView stackView, final TaskView taskView,
-            final boolean screenPinningRequested, final Rect bounds, final int destinationStack) {
-        final ActivityOptions opts = ActivityOptions.makeBasic();
-        if (bounds != null) {
-            opts.setLaunchBounds(bounds.isEmpty() ? null : bounds);
-        }
+            final boolean screenPinningRequested, final int destinationStack) {
 
         final ActivityOptions.OnAnimationStartedListener animStartedListener;
-        final IAppTransitionAnimationSpecsFuture transitionFuture;
+        final AppTransitionAnimationSpecsFuture transitionFuture;
         if (taskView != null) {
-            transitionFuture = getAppTransitionFuture(new AnimationSpecComposer() {
-                @Override
-                public List<AppTransitionAnimationSpec> composeSpecs() {
-                    return composeAnimationSpecs(task, stackView, destinationStack);
-                }
-            });
-            animStartedListener = new ActivityOptions.OnAnimationStartedListener() {
-                @Override
-                public void onAnimationStarted() {
-                    // If we are launching into another task, cancel the previous task's
-                    // window transition
-                    EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(task));
-                    EventBus.getDefault().send(new ExitRecentsWindowFirstAnimationFrameEvent());
-                    stackView.cancelAllTaskViewAnimations();
 
-                    if (screenPinningRequested) {
-                        // Request screen pinning after the animation runs
-                        mStartScreenPinningRunnable.taskId = task.key.id;
-                        mHandler.postDelayed(mStartScreenPinningRunnable, 350);
-                    }
+            // Fetch window rect here already in order not to be blocked on lock contention in WM
+            // when the future calls it.
+            final Rect windowRect = Recents.getSystemServices().getWindowRect();
+            transitionFuture = getAppTransitionFuture(
+                    () -> composeAnimationSpecs(task, stackView, destinationStack, windowRect));
+            animStartedListener = () -> {
+                // If we are launching into another task, cancel the previous task's
+                // window transition
+                EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(task));
+                EventBus.getDefault().send(new ExitRecentsWindowFirstAnimationFrameEvent());
+                stackView.cancelAllTaskViewAnimations();
+
+                if (screenPinningRequested) {
+                    // Request screen pinning after the animation runs
+                    mStartScreenPinningRunnable.taskId = task.key.id;
+                    mHandler.postDelayed(mStartScreenPinningRunnable, 350);
                 }
             };
         } else {
             // This is only the case if the task is not on screen (scrolled offscreen for example)
             transitionFuture = null;
-            animStartedListener = new ActivityOptions.OnAnimationStartedListener() {
-                @Override
-                public void onAnimationStarted() {
-                    // If we are launching into another task, cancel the previous task's
-                    // window transition
-                    EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(task));
-                    EventBus.getDefault().send(new ExitRecentsWindowFirstAnimationFrameEvent());
-                    stackView.cancelAllTaskViewAnimations();
-                }
+            animStartedListener = () -> {
+                // If we are launching into another task, cancel the previous task's
+                // window transition
+                EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(task));
+                EventBus.getDefault().send(new ExitRecentsWindowFirstAnimationFrameEvent());
+                stackView.cancelAllTaskViewAnimations();
             };
         }
 
+        final ActivityOptions opts = ActivityOptions.makeMultiThumbFutureAspectScaleAnimation(mContext,
+                mHandler, transitionFuture != null ? transitionFuture.future : null,
+                animStartedListener, true /* scaleUp */);
         if (taskView == null) {
             // If there is no task view, then we do not need to worry about animating out occluding
             // task views, and we can launch immediately
-            startTaskActivity(stack, task, taskView, opts, transitionFuture, animStartedListener,
-                    destinationStack);
+            startTaskActivity(stack, task, taskView, opts, transitionFuture, destinationStack);
         } else {
             LaunchTaskStartedEvent launchStartedEvent = new LaunchTaskStartedEvent(taskView,
                     screenPinningRequested);
@@ -160,14 +152,13 @@
                     @Override
                     public void run() {
                         startTaskActivity(stack, task, taskView, opts, transitionFuture,
-                                animStartedListener, destinationStack);
+                                destinationStack);
                     }
                 });
                 EventBus.getDefault().send(launchStartedEvent);
             } else {
                 EventBus.getDefault().send(launchStartedEvent);
-                startTaskActivity(stack, task, taskView, opts, transitionFuture,
-                        animStartedListener, destinationStack);
+                startTaskActivity(stack, task, taskView, opts, transitionFuture, destinationStack);
             }
         }
         Recents.getSystemServices().sendCloseSystemWindows(
@@ -199,30 +190,31 @@
      * @param destinationStack id of the stack to put the task into.
      */
     private void startTaskActivity(TaskStack stack, Task task, @Nullable TaskView taskView,
-            ActivityOptions opts, IAppTransitionAnimationSpecsFuture transitionFuture,
-            final OnAnimationStartedListener animStartedListener, int destinationStack) {
+            ActivityOptions opts, AppTransitionAnimationSpecsFuture transitionFuture,
+            int destinationStack) {
         SystemServicesProxy ssp = Recents.getSystemServices();
-        if (ssp.startActivityFromRecents(mContext, task.key, task.title, opts, destinationStack)) {
-            // Keep track of the index of the task launch
-            int taskIndexFromFront = 0;
-            int taskIndex = stack.indexOfStackTask(task);
-            if (taskIndex > -1) {
-                taskIndexFromFront = stack.getTaskCount() - taskIndex - 1;
-            }
-            EventBus.getDefault().send(new LaunchTaskSucceededEvent(taskIndexFromFront));
-        } else {
-            // Dismiss the task if we fail to launch it
-            if (taskView != null) {
-                taskView.dismissTask();
-            }
+        ssp.startActivityFromRecents(mContext, task.key, task.title, opts, destinationStack,
+                succeeded -> {
+            if (succeeded) {
+                // Keep track of the index of the task launch
+                int taskIndexFromFront = 0;
+                int taskIndex = stack.indexOfStackTask(task);
+                if (taskIndex > -1) {
+                    taskIndexFromFront = stack.getTaskCount() - taskIndex - 1;
+                }
+                EventBus.getDefault().send(new LaunchTaskSucceededEvent(taskIndexFromFront));
+            } else {
+                // Dismiss the task if we fail to launch it
+                if (taskView != null) {
+                    taskView.dismissTask();
+                }
 
-            // Keep track of failed launches
-            EventBus.getDefault().send(new LaunchTaskFailedEvent());
-        }
-
+                // Keep track of failed launches
+                EventBus.getDefault().send(new LaunchTaskFailedEvent());
+            }
+        });
         if (transitionFuture != null) {
-            ssp.overridePendingAppTransitionMultiThumbFuture(transitionFuture,
-                    wrapStartedListener(animStartedListener), true /* scaleUp */);
+            mHandler.post(transitionFuture::precacheSpecs);
         }
     }
 
@@ -231,21 +223,18 @@
      *
      * @param composer The implementation that composes the specs on the UI thread.
      */
-    public IAppTransitionAnimationSpecsFuture getAppTransitionFuture(
+    public AppTransitionAnimationSpecsFuture getAppTransitionFuture(
             final AnimationSpecComposer composer) {
         synchronized (this) {
             mAppTransitionAnimationSpecs = SPECS_WAITING;
         }
-        return new IAppTransitionAnimationSpecsFuture.Stub() {
+        IAppTransitionAnimationSpecsFuture future = new IAppTransitionAnimationSpecsFuture.Stub() {
             @Override
             public AppTransitionAnimationSpec[] get() throws RemoteException {
-                mHandler.post(new Runnable() {
-                    @Override
-                    public void run() {
-                        synchronized (RecentsTransitionHelper.this) {
-                            mAppTransitionAnimationSpecs = composer.composeSpecs();
-                            RecentsTransitionHelper.this.notifyAll();
-                        }
+                mHandler.post(() -> {
+                    synchronized (RecentsTransitionHelper.this) {
+                        mAppTransitionAnimationSpecs = composer.composeSpecs();
+                        RecentsTransitionHelper.this.notifyAll();
                     }
                 });
                 synchronized (RecentsTransitionHelper.this) {
@@ -265,6 +254,7 @@
                 }
             }
         };
+        return new AppTransitionAnimationSpecsFuture(composer, future);
     }
 
     /**
@@ -283,7 +273,7 @@
      * Composes the animation specs for all the tasks in the target stack.
      */
     private List<AppTransitionAnimationSpec> composeAnimationSpecs(final Task task,
-            final TaskStackView stackView, final int destinationStack) {
+            final TaskStackView stackView, final int destinationStack, Rect windowRect) {
         // Ensure we have a valid target stack id
         final int targetStackId = destinationStack != INVALID_STACK_ID ?
                 destinationStack : task.key.stackId;
@@ -309,8 +299,7 @@
                 specs.add(composeOffscreenAnimationSpec(task, offscreenTaskRect));
             } else {
                 mTmpTransform.fillIn(taskView);
-                stackLayout.transformToScreenCoordinates(mTmpTransform,
-                        null /* windowOverrideRect */);
+                stackLayout.transformToScreenCoordinates(mTmpTransform, windowRect);
                 AppTransitionAnimationSpec spec = composeAnimationSpec(stackView, taskView,
                         mTmpTransform, true /* addHeaderBitmap */);
                 if (spec != null) {
@@ -430,4 +419,34 @@
     public interface AnimationSpecComposer {
         List<AppTransitionAnimationSpec> composeSpecs();
     }
+
+    /**
+     * Class to be returned from {@link #composeAnimationSpec} that gives access to both the future
+     * and the anonymous class used for composing.
+     */
+    public class AppTransitionAnimationSpecsFuture {
+
+        private final AnimationSpecComposer composer;
+        private final IAppTransitionAnimationSpecsFuture future;
+
+        private AppTransitionAnimationSpecsFuture(AnimationSpecComposer composer,
+                IAppTransitionAnimationSpecsFuture future) {
+            this.composer = composer;
+            this.future = future;
+        }
+
+        public IAppTransitionAnimationSpecsFuture getFuture() {
+            return future;
+        }
+
+        /**
+         * Manually generates and caches the spec such that they are already available when the
+         * future needs.
+         */
+        public void precacheSpecs() {
+            synchronized (RecentsTransitionHelper.this) {
+                mAppTransitionAnimationSpecs = composer.composeSpecs();
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
index 8f9c457..e06c40c 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -24,19 +24,16 @@
 import android.content.Context;
 import android.graphics.Canvas;
 import android.graphics.Color;
-import android.graphics.Outline;
 import android.graphics.Rect;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.util.ArraySet;
 import android.util.AttributeSet;
 import android.view.AppTransitionAnimationSpec;
-import android.view.IAppTransitionAnimationSpecsFuture;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewDebug;
-import android.view.ViewOutlineProvider;
 import android.view.ViewPropertyAnimator;
 import android.view.WindowInsets;
 import android.widget.FrameLayout;
@@ -73,10 +70,10 @@
 import com.android.systemui.recents.model.Task;
 import com.android.systemui.recents.model.TaskStack;
 import com.android.systemui.recents.views.RecentsTransitionHelper.AnimationSpecComposer;
+import com.android.systemui.recents.views.RecentsTransitionHelper.AppTransitionAnimationSpecsFuture;
 import com.android.systemui.stackdivider.WindowManagerProxy;
 import com.android.systemui.statusbar.FlingAnimationUtils;
 
-import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
@@ -440,8 +437,7 @@
     public final void onBusEvent(LaunchTaskEvent event) {
         mLastTaskLaunchedWasFreeform = event.task.isFreeformTask();
         mTransitionHelper.launchTaskFromRecents(getStack(), event.task, mTaskStackView,
-                event.taskView, event.screenPinningRequested, event.targetTaskBounds,
-                event.targetTaskStack);
+                event.taskView, event.screenPinningRequested, event.targetTaskStack);
     }
 
     public final void onBusEvent(DismissRecentsToHomeAnimationStarted event) {
@@ -523,7 +519,7 @@
                 };
 
                 final Rect taskRect = getTaskRect(event.taskView);
-                IAppTransitionAnimationSpecsFuture future =
+                AppTransitionAnimationSpecsFuture future =
                         mTransitionHelper.getAppTransitionFuture(
                                 new AnimationSpecComposer() {
                                     @Override
@@ -532,7 +528,7 @@
                                                 event.taskView, taskRect);
                                     }
                                 });
-                ssp.overridePendingAppTransitionMultiThumbFuture(future,
+                ssp.overridePendingAppTransitionMultiThumbFuture(future.getFuture(),
                         mTransitionHelper.wrapStartedListener(startedListener),
                         true /* scaleUp */);
 
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index 4e00f2d..bd8e7f7 100644
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -149,6 +149,7 @@
 import android.util.Slog;
 import android.util.TimeUtils;
 import android.view.AppTransitionAnimationSpec;
+import android.view.IAppTransitionAnimationSpecsFuture;
 import android.view.IApplicationToken;
 import android.view.WindowManager.LayoutParams;
 
@@ -1437,7 +1438,13 @@
                 case ANIM_THUMBNAIL_ASPECT_SCALE_UP:
                 case ANIM_THUMBNAIL_ASPECT_SCALE_DOWN:
                     final AppTransitionAnimationSpec[] specs = pendingOptions.getAnimSpecs();
-                    if (animationType == ANIM_THUMBNAIL_ASPECT_SCALE_DOWN
+                    final IAppTransitionAnimationSpecsFuture specsFuture =
+                            pendingOptions.getSpecsFuture();
+                    if (specsFuture != null) {
+                        service.mWindowManager.overridePendingAppTransitionMultiThumbFuture(
+                                specsFuture, pendingOptions.getOnAnimationStartListener(),
+                                animationType == ANIM_THUMBNAIL_ASPECT_SCALE_UP);
+                    } else if (animationType == ANIM_THUMBNAIL_ASPECT_SCALE_DOWN
                             && specs != null) {
                         service.mWindowManager.overridePendingAppTransitionMultiThumb(
                                 specs, pendingOptions.getOnAnimationStartListener(),