Improve animating from recents to multi window state.

Previously all windows would start animating from a single thumbnail
that was clicked. Now each animates from its thumbnail, including
properly animating the thumbnail header. This involves System UI
providing information about the thumbnail setup and app transition code
using it to setup each individual animation.

Change-Id: I6f45c94af6bcbf6898b8ed757d83505af99ed6b1
diff --git a/core/java/android/view/AppTransitionAnimationSpec.aidl b/core/java/android/view/AppTransitionAnimationSpec.aidl
new file mode 100644
index 0000000..8388347
--- /dev/null
+++ b/core/java/android/view/AppTransitionAnimationSpec.aidl
@@ -0,0 +1,20 @@
+/*
+** Copyright 2015, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.view;
+
+/** @hide */
+parcelable AppTransitionAnimationSpec;
diff --git a/core/java/android/view/AppTransitionAnimationSpec.java b/core/java/android/view/AppTransitionAnimationSpec.java
new file mode 100644
index 0000000..c6e1989
--- /dev/null
+++ b/core/java/android/view/AppTransitionAnimationSpec.java
@@ -0,0 +1,61 @@
+package android.view;
+
+import android.graphics.Bitmap;
+import android.graphics.Rect;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Holds information about how the next app transition animation should be executed.
+ *
+ * This class is intended to be used with IWindowManager.overridePendingAppTransition* methods when
+ * simple arguments are not enough to describe the animation.
+ *
+ * @hide
+ */
+public class AppTransitionAnimationSpec implements Parcelable {
+    public final int taskId;
+    public final Bitmap bitmap;
+    public final Rect rect;
+
+    public AppTransitionAnimationSpec(int taskId, Bitmap bitmap, Rect rect) {
+        this.taskId = taskId;
+        this.bitmap = bitmap;
+        this.rect = rect;
+    }
+
+    public AppTransitionAnimationSpec(Parcel in) {
+        taskId = in.readInt();
+        bitmap = in.readParcelable(null);
+        rect = in.readParcelable(null);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(taskId);
+        dest.writeParcelable(bitmap, 0 /* flags */);
+        dest.writeParcelable(rect, 0 /* flags */);
+
+    }
+
+    public static final Parcelable.Creator<AppTransitionAnimationSpec> CREATOR
+            = new Parcelable.Creator<AppTransitionAnimationSpec>() {
+        public AppTransitionAnimationSpec createFromParcel(Parcel in) {
+            return new AppTransitionAnimationSpec(in);
+        }
+
+        public AppTransitionAnimationSpec[] newArray(int size) {
+            return new AppTransitionAnimationSpec[size];
+        }
+    };
+
+    @Override
+    public String toString() {
+        return "{taskId: " + taskId + ", bitmap: " + bitmap + ", rect: " + rect + "}";
+    }
+}
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 0a4b982..f86adfe 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -39,6 +39,7 @@
 import android.view.InputChannel;
 import android.view.InputDevice;
 import android.view.IInputFilter;
+import android.view.AppTransitionAnimationSpec;
 import android.view.WindowContentFrameStats;
 
 /**
@@ -127,6 +128,16 @@
     void overridePendingAppTransitionAspectScaledThumb(in Bitmap srcThumb, int startX,
             int startY, int targetWidth, int targetHeight, IRemoteCallback startedCallback,
             boolean scaleUp);
+    /**
+     * Overrides animation for app transition that exits from an application to a multi-window
+     * environment and allows specifying transition animation parameters for each window.
+     *
+     * @param specs Array of transition animation descriptions for entering windows.
+     *
+     * @hide
+     */
+    void overridePendingAppTransitionMultiThumb(in AppTransitionAnimationSpec[] specs,
+            IRemoteCallback startedCallback, boolean scaleUp);
     void overridePendingAppTransitionInPlace(String packageName, int anim);
     void executeAppTransition();
     void setAppStartingWindow(IBinder token, String pkg, int theme,
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 947c19c..00ac5f9 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.recents.views;
 
+import android.app.ActivityManager;
 import android.app.ActivityOptions;
 import android.app.TaskStackBuilder;
 import android.content.Context;
@@ -31,6 +32,8 @@
 import android.provider.Settings;
 import android.util.AttributeSet;
 import android.util.Log;
+import android.util.SparseArray;
+import android.view.AppTransitionAnimationSpec;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.WindowInsets;
@@ -60,6 +63,8 @@
 
     private static final String TAG = "RecentsView";
 
+    private static final boolean ADD_HEADER_BITMAP = true;
+
     /** The RecentsView callbacks */
     public interface RecentsViewCallbacks {
         public void onTaskViewClicked();
@@ -443,62 +448,158 @@
         }
     }
 
-    private void postDrawHeaderThumbnailTransitionRunnable(final TaskView tv, final int offsetX,
-            final int offsetY, final TaskViewTransform transform,
+    private void postDrawHeaderThumbnailTransitionRunnable(final TaskStackView view,
+            final TaskView clickedView, final int offsetX, final int offsetY,
+            final float stackScroll,
             final ActivityOptions.OnAnimationStartedListener animStartedListener) {
         Runnable r = new Runnable() {
             @Override
             public void run() {
-                // Disable any focused state before we draw the header
-                if (tv.isFocusedTask()) {
-                    tv.unsetFocusedTask();
-                }
+                overrideDrawHeaderThumbnailTransition(view, clickedView, offsetX, offsetY,
+                        stackScroll, animStartedListener);
 
-                float scale = tv.getScaleX();
-                int fromHeaderWidth = (int) (tv.mHeaderView.getMeasuredWidth() * scale);
-                int fromHeaderHeight = (int) (tv.mHeaderView.getMeasuredHeight() * scale);
-
-                Bitmap b = Bitmap.createBitmap(fromHeaderWidth, fromHeaderHeight,
-                        Bitmap.Config.ARGB_8888);
-                if (Constants.DebugFlags.App.EnableTransitionThumbnailDebugMode) {
-                    b.eraseColor(0xFFff0000);
-                } else {
-                    Canvas c = new Canvas(b);
-                    c.scale(tv.getScaleX(), tv.getScaleY());
-                    tv.mHeaderView.draw(c);
-                    c.setBitmap(null);
-                }
-                b = b.createAshmemBitmap();
-                int[] pts = new int[2];
-                tv.getLocationOnScreen(pts);
-                try {
-                    WindowManagerGlobal.getWindowManagerService()
-                            .overridePendingAppTransitionAspectScaledThumb(b,
-                                    pts[0] + offsetX,
-                                    pts[1] + offsetY,
-                                    transform.rect.width(),
-                                    transform.rect.height(),
-                                    new IRemoteCallback.Stub() {
-                                        @Override
-                                        public void sendResult(Bundle data)
-                                                throws RemoteException {
-                                            post(new Runnable() {
-                                                @Override
-                                                public void run() {
-                                                    if (animStartedListener != null) {
-                                                        animStartedListener.onAnimationStarted();
-                                                    }
-                                                }
-                                            });
-                                        }
-                                    }, true);
-                } catch (RemoteException e) {
-                    Log.w(TAG, "Error overriding app transition", e);
-                }
             }
         };
+
         mCb.runAfterPause(r);
     }
+
+    private void overrideDrawHeaderThumbnailTransition(TaskStackView stackView,
+            TaskView clickedTask, int offsetX, int offsetY, float stackScroll,
+            final ActivityOptions.OnAnimationStartedListener animStartedListener) {
+        List<AppTransitionAnimationSpec> specs = getAppTransitionAnimationSpecs(stackView,
+                clickedTask, offsetX, offsetY, stackScroll);
+        if (specs == null) {
+            return;
+        }
+
+        IRemoteCallback.Stub callback = new IRemoteCallback.Stub() {
+            @Override
+            public void sendResult(Bundle data) throws RemoteException {
+                post(new Runnable() {
+                    @Override
+                    public void run() {
+                        if (animStartedListener != null) {
+                            animStartedListener.onAnimationStarted();
+                        }
+                    }
+                });
+            }
+        };
+
+        AppTransitionAnimationSpec[] specsArray =
+                new AppTransitionAnimationSpec[specs.size()];
+        try {
+            WindowManagerGlobal.getWindowManagerService().overridePendingAppTransitionMultiThumb(
+                    specs.toArray(specsArray), callback, true /* scaleUp */);
+
+        } catch (RemoteException e) {
+            Log.w(TAG, "Error overriding app transition", e);
+        }
+    }
+
+    private List<AppTransitionAnimationSpec> getAppTransitionAnimationSpecs(TaskStackView stackView,
+            TaskView clickedTask, int offsetX, int offsetY, float stackScroll) {
+        final int targetStackId = clickedTask.getTask().key.stackId;
+        if (targetStackId != ActivityManager.FREEFORM_WORKSPACE_STACK_ID
+                && targetStackId != ActivityManager.FULLSCREEN_WORKSPACE_STACK_ID) {
+            return null;
+        }
+        // If this is a full screen stack, the transition will be towards the single, full screen
+        // task. We only need the transition spec for this task.
+        List<AppTransitionAnimationSpec> specs = new ArrayList<>();
+        if (targetStackId == ActivityManager.FULLSCREEN_WORKSPACE_STACK_ID) {
+            specs.add(createThumbnailHeaderAnimationSpec(
+                    stackView, offsetX, offsetY, stackScroll, clickedTask,
+                    clickedTask.getTask().key.id, ADD_HEADER_BITMAP));
+            return specs;
+        }
+        // This is a free form stack or full screen stack, so there will be multiple windows
+        // animating from thumbnails. We need transition animation specs for all of them.
+
+        // We will use top and bottom task views as a base for tasks, that aren't visible on the
+        // screen. This is necessary for cascade recents list, where some of the tasks might be
+        // hidden.
+        List<TaskView> taskViews = stackView.getTaskViews();
+        int childCount = taskViews.size();
+        TaskView topChild = taskViews.get(0);
+        TaskView bottomChild = taskViews.get(childCount - 1);
+        SparseArray<TaskView> taskViewsByTaskId = new SparseArray<>();
+        for (int i = 0; i < childCount; i++) {
+            TaskView taskView = taskViews.get(i);
+            taskViewsByTaskId.put(taskView.getTask().key.id, taskView);
+        }
+
+        TaskStack stack = stackView.getStack();
+        // We go through all tasks now and for each generate transition animation spec. If there is
+        // a view associated with a task, we use that view as a base for the animation. If there
+        // isn't, we use bottom or top view, depending on which one would be closer to the task
+        // view if it existed.
+        ArrayList<Task> tasks = stack.getTasks();
+        boolean passedClickedTask = false;
+        for (int i = 0, n = tasks.size(); i < n; i++) {
+            Task task = tasks.get(i);
+            TaskView taskView = taskViewsByTaskId.get(task.key.id);
+            if (taskView != null) {
+                specs.add(createThumbnailHeaderAnimationSpec(stackView, offsetX, offsetY,
+                        stackScroll, taskView, taskView.getTask().key.id, ADD_HEADER_BITMAP));
+                if (taskView == clickedTask) {
+                    passedClickedTask = true;
+                }
+            } else {
+                taskView = passedClickedTask ? bottomChild : topChild;
+                specs.add(createThumbnailHeaderAnimationSpec(stackView, offsetX, offsetY,
+                        stackScroll, taskView, task.key.id, !ADD_HEADER_BITMAP));
+            }
+        }
+
+        return specs;
+    }
+
+    private AppTransitionAnimationSpec createThumbnailHeaderAnimationSpec(TaskStackView stackView,
+            int offsetX, int offsetY, float stackScroll, TaskView tv, int taskId,
+            boolean addHeaderBitmap) {
+        // Disable any focused state before we draw the header
+        // Upfront the processing of the thumbnail
+        if (tv.isFocusedTask()) {
+            tv.unsetFocusedTask();
+        }
+        TaskViewTransform transform = new TaskViewTransform();
+        transform = stackView.getStackAlgorithm().getStackTransform(tv.mTask, stackScroll,
+                transform, null);
+
+        float scale = tv.getScaleX();
+        int fromHeaderWidth = (int) (tv.mHeaderView.getMeasuredWidth() * scale);
+        int fromHeaderHeight = (int) (tv.mHeaderView.getMeasuredHeight() * scale);
+
+        Bitmap b = null;
+        if (addHeaderBitmap) {
+            b = Bitmap.createBitmap(fromHeaderWidth, fromHeaderHeight,
+                    Bitmap.Config.ARGB_8888);
+
+            if (Constants.DebugFlags.App.EnableTransitionThumbnailDebugMode) {
+                b.eraseColor(0xFFff0000);
+            } else {
+                Canvas c = new Canvas(b);
+                c.scale(tv.getScaleX(), tv.getScaleY());
+                tv.mHeaderView.draw(c);
+                c.setBitmap(null);
+
+            }
+            b = b.createAshmemBitmap();
+        }
+
+        int[] pts = new int[2];
+        tv.getLocationOnScreen(pts);
+
+        final int left = pts[0] + offsetX;
+        final int top = pts[1] + offsetY;
+        final Rect rect = new Rect(left, top, left + transform.rect.width(),
+                top + transform.rect.height());
+
+        return new AppTransitionAnimationSpec(taskId, b, rect);
+    }
+
     /**** TaskStackView.TaskStackCallbacks Implementation ****/
 
     @Override
@@ -521,12 +622,10 @@
             // and then offset to the expected transform rect, but bound this to just
             // outside the display rect (to ensure we don't animate from too far away)
             sourceView = stackView;
-            transform = stackView.getStackAlgorithm().getStackTransform(task, stackScroll, transform, null);
             offsetX = transform.rect.left;
             offsetY = mConfig.displayRect.height();
         } else {
             sourceView = tv.mThumbnailView;
-            transform = stackView.getStackAlgorithm().getStackTransform(task, stackScroll, transform, null);
         }
 
         // Compute the thumbnail to scale up from
@@ -553,10 +652,8 @@
                     }
                 };
             }
-            if (tv != null) {
-                postDrawHeaderThumbnailTransitionRunnable(tv, offsetX, offsetY, transform,
-                        animStartedListener);
-            }
+            postDrawHeaderThumbnailTransitionRunnable(stackView, tv, offsetX, offsetY, stackScroll,
+                    animStartedListener);
             if (mConfig.multiStackEnabled) {
                 opts = ActivityOptions.makeCustomAnimation(sourceView.getContext(),
                         R.anim.recents_from_unknown_enter,
diff --git a/services/core/java/com/android/server/policy/StatusBarController.java b/services/core/java/com/android/server/policy/StatusBarController.java
index d1b50da..da23f45 100644
--- a/services/core/java/com/android/server/policy/StatusBarController.java
+++ b/services/core/java/com/android/server/policy/StatusBarController.java
@@ -122,7 +122,7 @@
      *
      * @return the desired start time of the status bar transition, in uptime millis
      */
-    private long calculateStatusBarTransitionStartTime(Animation openAnimation,
+    private static long calculateStatusBarTransitionStartTime(Animation openAnimation,
             Animation closeAnimation) {
         if (openAnimation != null && closeAnimation != null) {
             TranslateAnimation openTranslateAnimation = findTranslateAnimation(openAnimation);
@@ -151,7 +151,7 @@
      *
      * @return the found animation, {@code null} otherwise
      */
-    private TranslateAnimation findTranslateAnimation(Animation animation) {
+    private static TranslateAnimation findTranslateAnimation(Animation animation) {
         if (animation instanceof TranslateAnimation) {
             return (TranslateAnimation) animation;
         } else if (animation instanceof AnimationSet) {
@@ -170,7 +170,7 @@
      * Binary searches for a {@code t} such that there exists a {@code -0.01 < eps < 0.01} for which
      * {@code interpolator(t + eps) > 0.99}.
      */
-    private float findAlmostThereFraction(Interpolator interpolator) {
+    private static float findAlmostThereFraction(Interpolator interpolator) {
         float val = 0.5f;
         float adj = 0.25f;
         while (adj >= 0.01f) {
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index dfdb29c..d1145d0 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -26,6 +26,8 @@
 import android.os.IBinder;
 import android.os.IRemoteCallback;
 import android.util.Slog;
+import android.util.SparseArray;
+import android.view.AppTransitionAnimationSpec;
 import android.view.WindowManager;
 import android.view.animation.AlphaAnimation;
 import android.view.animation.Animation;
@@ -153,22 +155,25 @@
     private static final int THUMBNAIL_TRANSITION_EXIT_SCALE_DOWN = 3;
 
     private String mNextAppTransitionPackage;
-    private Bitmap mNextAppTransitionThumbnail;
     // Used for thumbnail transitions. True if we're scaling up, false if scaling down
     private boolean mNextAppTransitionScaleUp;
     private IRemoteCallback mNextAppTransitionCallback;
     private int mNextAppTransitionEnter;
     private int mNextAppTransitionExit;
     private int mNextAppTransitionInPlace;
-    private int mNextAppTransitionStartX;
-    private int mNextAppTransitionStartY;
-    private int mNextAppTransitionStartWidth;
-    private int mNextAppTransitionStartHeight;
+
+    // Keyed by task id.
+    private final SparseArray<AppTransitionAnimationSpec> mNextAppTransitionAnimationsSpecs
+            = new SparseArray<>();
+    private AppTransitionAnimationSpec mDefaultNextAppTransitionAnimationSpec;
+
     private Rect mNextAppTransitionInsets = new Rect();
 
     private Rect mTmpFromClipRect = new Rect();
     private Rect mTmpToClipRect = new Rect();
 
+    private final Rect mTmpStartRect = new Rect();
+
     private final static int APP_STATE_IDLE = 0;
     private final static int APP_STATE_READY = 1;
     private final static int APP_STATE_RUNNING = 2;
@@ -276,8 +281,9 @@
         mAppTransitionState = APP_STATE_TIMEOUT;
     }
 
-    Bitmap getNextAppTransitionThumbnail() {
-        return mNextAppTransitionThumbnail;
+    Bitmap getAppTransitionThumbnailHeader(int taskId) {
+        AppTransitionAnimationSpec spec = mNextAppTransitionAnimationsSpecs.get(taskId);
+        return spec != null ? spec.bitmap : null;
     }
 
     /** Returns whether the next thumbnail transition is aspect scaled up. */
@@ -291,14 +297,6 @@
         return mNextAppTransitionScaleUp;
     }
 
-    int getStartingX() {
-        return mNextAppTransitionStartX;
-    }
-
-    int getStartingY() {
-        return mNextAppTransitionStartY;
-    }
-
     boolean prepare() {
         if (!isRunning()) {
             mAppTransitionState = APP_STATE_IDLE;
@@ -321,7 +319,7 @@
     void clear() {
         mNextAppTransitionType = NEXT_TRANSIT_TYPE_NONE;
         mNextAppTransitionPackage = null;
-        mNextAppTransitionThumbnail = null;
+        mNextAppTransitionAnimationsSpecs.clear();
     }
 
     void freeze() {
@@ -459,16 +457,17 @@
         return -startPos / denom;
     }
 
-    private Animation createScaleUpAnimationLocked(int transit, boolean enter,
-                                                   int appWidth, int appHeight) {
+    private Animation createScaleUpAnimationLocked(
+            int transit, boolean enter, int appWidth, int appHeight) {
         Animation a = null;
+        getDefaultNextAppTransitionStartRect(mTmpStartRect);
         if (enter) {
             // Entering app zooms out from the center of the initial rect.
-            float scaleW = mNextAppTransitionStartWidth / (float) appWidth;
-            float scaleH = mNextAppTransitionStartHeight / (float) appHeight;
+            float scaleW = mTmpStartRect.width() / (float) appWidth;
+            float scaleH = mTmpStartRect.height() / (float) appHeight;
             Animation scale = new ScaleAnimation(scaleW, 1, scaleH, 1,
-                    computePivot(mNextAppTransitionStartX, scaleW),
-                    computePivot(mNextAppTransitionStartY, scaleH));
+                    computePivot(mTmpStartRect.left, scaleW),
+                    computePivot(mTmpStartRect.right, scaleH));
             scale.setInterpolator(mDecelerateInterpolator);
 
             Animation alpha = new AlphaAnimation(0, 1);
@@ -512,6 +511,32 @@
         return a;
     }
 
+    private void getDefaultNextAppTransitionStartRect(Rect rect) {
+        if (mDefaultNextAppTransitionAnimationSpec == null ||
+                mDefaultNextAppTransitionAnimationSpec.rect == null) {
+            Slog.wtf(TAG, "Starting rect for app requested, but none available", new Throwable());
+            rect.setEmpty();
+        } else {
+            rect.set(mDefaultNextAppTransitionAnimationSpec.rect);
+        }
+    }
+
+    void getNextAppTransitionStartRect(int taskId, Rect rect) {
+        AppTransitionAnimationSpec spec = mNextAppTransitionAnimationsSpecs.get(taskId);
+        if (spec == null || spec.rect == null) {
+            Slog.wtf(TAG, "Starting rect for task: " + taskId + " requested, but not available",
+                    new Throwable());
+            rect.setEmpty();
+        } else {
+            rect.set(spec.rect);
+        }
+    }
+
+    private void putDefaultNextAppTransitionCoordinates(int left, int top, int width, int height) {
+        mDefaultNextAppTransitionAnimationSpec = new AppTransitionAnimationSpec(-1 /* taskId */,
+                null /* bitmap */, new Rect(left, top, left + width, top + height));
+    }
+
     private Animation createClipRevealAnimationLocked(int transit, boolean enter, Rect appFrame) {
         final Animation anim;
         if (enter) {
@@ -519,28 +544,27 @@
 
             final int appWidth = appFrame.width();
             final int appHeight = appFrame.height();
+            getDefaultNextAppTransitionStartRect(mTmpStartRect);
 
             float t = 0f;
             if (appHeight > 0) {
-                t = (float) mNextAppTransitionStartY / appHeight;
+                t = (float) mTmpStartRect.left / appHeight;
             }
             int translationY = mClipRevealTranslationY
                     + (int)(appHeight / 7f * t);
 
-            int centerX = mNextAppTransitionStartX + mNextAppTransitionStartWidth / 2;
-            int centerY = mNextAppTransitionStartY + mNextAppTransitionStartHeight / 2;
+            int centerX = mTmpStartRect.centerX();
+            int centerY = mTmpStartRect.centerY();
+            int halfWidth = mTmpStartRect.width() / 2;
+            int halfHeight = mTmpStartRect.height() / 2;
 
             // Clip third of the from size of launch icon, expand to full width/height
             Animation clipAnimLR = new ClipRectLRAnimation(
-                    centerX - mNextAppTransitionStartWidth / 2,
-                    centerX + mNextAppTransitionStartWidth / 2,
-                    0, appWidth);
+                    centerX - halfWidth, centerX + halfWidth, 0, appWidth);
             clipAnimLR.setInterpolator(mClipHorizontalInterpolator);
             clipAnimLR.setDuration((long) (DEFAULT_APP_TRANSITION_DURATION / 2.5f));
-            Animation clipAnimTB = new ClipRectTBAnimation(
-                    centerY - mNextAppTransitionStartHeight / 2 - translationY,
-                    centerY + mNextAppTransitionStartHeight / 2 - translationY,
-                    0, appHeight);
+            Animation clipAnimTB = new ClipRectTBAnimation(centerY - halfHeight - translationY,
+                    centerY + halfHeight/ 2 - translationY, 0, appHeight);
             clipAnimTB.setInterpolator(mTouchResponseInterpolator);
             clipAnimTB.setDuration(DEFAULT_APP_TRANSITION_DURATION);
 
@@ -649,31 +673,32 @@
 
     /**
      * This animation runs for the thumbnail that gets cross faded with the enter/exit activity
-     * when a thumbnail is specified with the activity options.
+     * when a thumbnail is specified with the pending animation override.
      */
-    Animation createThumbnailAspectScaleAnimationLocked(Rect appRect) {
+    Animation createThumbnailAspectScaleAnimationLocked(Rect appRect, Bitmap thumbnailHeader,
+            final int taskId) {
         Animation a;
-        final int thumbWidthI = mNextAppTransitionThumbnail.getWidth();
+        final int thumbWidthI = thumbnailHeader.getWidth();
         final float thumbWidth = thumbWidthI > 0 ? thumbWidthI : 1;
-        final int thumbHeightI = mNextAppTransitionThumbnail.getHeight();
+        final int thumbHeightI = thumbnailHeader.getHeight();
         final float thumbHeight = thumbHeightI > 0 ? thumbHeightI : 1;
         final int appWidth = appRect.width();
 
         float scaleW = appWidth / thumbWidth;
         float unscaledHeight = thumbHeight * scaleW;
-        float unscaledStartY = mNextAppTransitionStartY - (unscaledHeight - thumbHeight) / 2f;
+        getNextAppTransitionStartRect(taskId, mTmpStartRect);
+        float unscaledStartY = mTmpStartRect.top - (unscaledHeight - thumbHeight) / 2f;
         if (mNextAppTransitionScaleUp) {
             // Animation up from the thumbnail to the full screen
             Animation scale = new ScaleAnimation(1f, scaleW, 1f, scaleW,
-                    mNextAppTransitionStartX + (thumbWidth / 2f),
-                    mNextAppTransitionStartY + (thumbHeight / 2f));
+                    mTmpStartRect.left + (thumbWidth / 2f), mTmpStartRect.top + (thumbHeight / 2f));
             scale.setInterpolator(mTouchResponseInterpolator);
             scale.setDuration(THUMBNAIL_APP_TRANSITION_DURATION);
             Animation alpha = new AlphaAnimation(1, 0);
             alpha.setInterpolator(mThumbnailFadeOutInterpolator);
             alpha.setDuration(THUMBNAIL_APP_TRANSITION_ALPHA_DURATION);
             final float toX = appRect.left + appRect.width() / 2 -
-                    (mNextAppTransitionStartX + thumbWidth / 2);
+                    (mTmpStartRect.left + thumbWidth / 2);
             final float toY = appRect.top + mNextAppTransitionInsets.top + -unscaledStartY;
             Animation translate = new TranslateAnimation(0, toX, 0, toY);
             translate.setInterpolator(mTouchResponseInterpolator);
@@ -688,8 +713,7 @@
         } else {
             // Animation down from the full screen to the thumbnail
             Animation scale = new ScaleAnimation(scaleW, 1f, scaleW, 1f,
-                    mNextAppTransitionStartX + (thumbWidth / 2f),
-                    mNextAppTransitionStartY + (thumbHeight / 2f));
+                    mTmpStartRect.left + (thumbWidth / 2f), mTmpStartRect.top + (thumbHeight / 2f));
             scale.setInterpolator(mTouchResponseInterpolator);
             scale.setDuration(THUMBNAIL_APP_TRANSITION_DURATION);
             Animation alpha = new AlphaAnimation(0f, 1f);
@@ -718,11 +742,13 @@
      */
     Animation createAspectScaledThumbnailEnterExitAnimationLocked(int thumbTransitState,
             int appWidth, int appHeight, int orientation, int transit, Rect containingFrame,
-            Rect contentInsets, @Nullable Rect surfaceInsets, boolean resizedWindow) {
+            Rect contentInsets, @Nullable Rect surfaceInsets, boolean resizedWindow,
+            int taskId) {
         Animation a;
-        final int thumbWidthI = mNextAppTransitionStartWidth;
+        getDefaultNextAppTransitionStartRect(mTmpStartRect);
+        final int thumbWidthI = mTmpStartRect.width();
         final float thumbWidth = thumbWidthI > 0 ? thumbWidthI : 1;
-        final int thumbHeightI = mNextAppTransitionStartHeight;
+        final int thumbHeightI = mTmpStartRect.height();
         final float thumbHeight = thumbHeightI > 0 ? thumbHeightI : 1;
 
         // Used for the ENTER_SCALE_UP and EXIT_SCALE_DOWN transitions
@@ -733,7 +759,7 @@
             case THUMBNAIL_TRANSITION_ENTER_SCALE_UP: {
                 if (resizedWindow) {
                     a = createAspectScaledThumbnailEnterNonFullscreenAnimationLocked(
-                            containingFrame, surfaceInsets);
+                            containingFrame, surfaceInsets, taskId);
                 } else {
                     // App window scaling up to become full screen
                     if (orientation == Configuration.ORIENTATION_PORTRAIT) {
@@ -759,8 +785,8 @@
                     mNextAppTransitionInsets.set(contentInsets);
 
                     Animation scaleAnim = new ScaleAnimation(scale, 1, scale, 1,
-                            computePivot(mNextAppTransitionStartX, scale),
-                            computePivot(mNextAppTransitionStartY, scale));
+                            computePivot(mTmpStartRect.left, scale),
+                            computePivot(mTmpStartRect.top, scale));
                     Animation clipAnim = new ClipRectAnimation(mTmpFromClipRect, mTmpToClipRect);
                     Animation translateAnim = new TranslateAnimation(0, 0, -scaledTopDecor, 0);
 
@@ -819,8 +845,8 @@
                 mNextAppTransitionInsets.set(contentInsets);
 
                 Animation scaleAnim = new ScaleAnimation(1, scale, 1, scale,
-                        computePivot(mNextAppTransitionStartX, scale),
-                        computePivot(mNextAppTransitionStartY, scale));
+                        computePivot(mTmpStartRect.left, scale),
+                        computePivot(mTmpStartRect.top, scale));
                 Animation clipAnim = new ClipRectAnimation(mTmpFromClipRect, mTmpToClipRect);
                 Animation translateAnim = new TranslateAnimation(0, 0, 0, -scaledTopDecor);
 
@@ -844,11 +870,12 @@
     }
 
     private Animation createAspectScaledThumbnailEnterNonFullscreenAnimationLocked(
-            Rect containingFrame, @Nullable Rect surfaceInsets) {
+            Rect containingFrame, @Nullable Rect surfaceInsets, int taskId) {
+        getNextAppTransitionStartRect(taskId, mTmpStartRect);
         float width = containingFrame.width();
         float height = containingFrame.height();
-        float scaleWidth = mNextAppTransitionStartWidth / width;
-        float scaleHeight = mNextAppTransitionStartHeight / height;
+        float scaleWidth = mTmpStartRect.width() / width;
+        float scaleHeight = mTmpStartRect.height() / height;
         AnimationSet set = new AnimationSet(true);
         int surfaceInsetsHorizontal = surfaceInsets == null
                 ? 0 : surfaceInsets.left + surfaceInsets.right;
@@ -858,9 +885,9 @@
         // we need to account for surface insets that will be used to enlarge the surface.
         ScaleAnimation scale = new ScaleAnimation(scaleWidth, 1, scaleHeight, 1,
                 (width + surfaceInsetsHorizontal) / 2, (height + surfaceInsetsVertical) / 2);
-        int fromX = mNextAppTransitionStartX + mNextAppTransitionStartWidth / 2
+        int fromX = mTmpStartRect.left + mTmpStartRect.width() / 2
                 - (containingFrame.left + containingFrame.width() / 2);
-        int fromY = mNextAppTransitionStartY + mNextAppTransitionStartHeight / 2
+        int fromY = mTmpStartRect.top + mTmpStartRect.height() / 2
                 - (containingFrame.top + containingFrame.height() / 2);
         TranslateAnimation translation = new TranslateAnimation(fromX, 0, fromY, 0);
         set.addAnimation(scale);
@@ -870,13 +897,15 @@
 
     /**
      * This animation runs for the thumbnail that gets cross faded with the enter/exit activity
-     * when a thumbnail is specified with the activity options.
+     * when a thumbnail is specified with the pending animation override.
      */
-    Animation createThumbnailScaleAnimationLocked(int appWidth, int appHeight, int transit) {
+    Animation createThumbnailScaleAnimationLocked(int appWidth, int appHeight, int transit,
+            Bitmap thumbnailHeader) {
         Animation a;
-        final int thumbWidthI = mNextAppTransitionThumbnail.getWidth();
+        getDefaultNextAppTransitionStartRect(mTmpStartRect);
+        final int thumbWidthI = thumbnailHeader.getWidth();
         final float thumbWidth = thumbWidthI > 0 ? thumbWidthI : 1;
-        final int thumbHeightI = mNextAppTransitionThumbnail.getHeight();
+        final int thumbHeightI = thumbnailHeader.getHeight();
         final float thumbHeight = thumbHeightI > 0 ? thumbHeightI : 1;
 
         if (mNextAppTransitionScaleUp) {
@@ -884,8 +913,8 @@
             float scaleW = appWidth / thumbWidth;
             float scaleH = appHeight / thumbHeight;
             Animation scale = new ScaleAnimation(1, scaleW, 1, scaleH,
-                    computePivot(mNextAppTransitionStartX, 1 / scaleW),
-                    computePivot(mNextAppTransitionStartY, 1 / scaleH));
+                    computePivot(mTmpStartRect.left, 1 / scaleW),
+                    computePivot(mTmpStartRect.top, 1 / scaleH));
             scale.setInterpolator(mDecelerateInterpolator);
 
             Animation alpha = new AlphaAnimation(1, 0);
@@ -901,8 +930,8 @@
             float scaleW = appWidth / thumbWidth;
             float scaleH = appHeight / thumbHeight;
             a = new ScaleAnimation(scaleW, 1, scaleH, 1,
-                    computePivot(mNextAppTransitionStartX, 1 / scaleW),
-                    computePivot(mNextAppTransitionStartY, 1 / scaleH));
+                    computePivot(mTmpStartRect.left, 1 / scaleW),
+                    computePivot(mTmpStartRect.top, 1 / scaleH));
         }
 
         return prepareThumbnailAnimation(a, appWidth, appHeight, transit);
@@ -913,11 +942,13 @@
      * leaving, and the activity that is entering.
      */
     Animation createThumbnailEnterExitAnimationLocked(int thumbTransitState, int appWidth,
-            int appHeight, int transit) {
+            int appHeight, int transit, int taskId) {
+        Bitmap thumbnailHeader = getAppTransitionThumbnailHeader(taskId);
         Animation a;
-        final int thumbWidthI = mNextAppTransitionThumbnail.getWidth();
+        getDefaultNextAppTransitionStartRect(mTmpStartRect);
+        final int thumbWidthI = thumbnailHeader != null ? thumbnailHeader.getWidth() : appWidth;
         final float thumbWidth = thumbWidthI > 0 ? thumbWidthI : 1;
-        final int thumbHeightI = mNextAppTransitionThumbnail.getHeight();
+        final int thumbHeightI = thumbnailHeader != null ? thumbnailHeader.getHeight() : appHeight;
         final float thumbHeight = thumbHeightI > 0 ? thumbHeightI : 1;
 
         switch (thumbTransitState) {
@@ -926,8 +957,8 @@
                 float scaleW = thumbWidth / appWidth;
                 float scaleH = thumbHeight / appHeight;
                 a = new ScaleAnimation(scaleW, 1, scaleH, 1,
-                        computePivot(mNextAppTransitionStartX, scaleW),
-                        computePivot(mNextAppTransitionStartY, scaleH));
+                        computePivot(mTmpStartRect.left, scaleW),
+                        computePivot(mTmpStartRect.top, scaleH));
                 break;
             }
             case THUMBNAIL_TRANSITION_EXIT_SCALE_UP: {
@@ -954,8 +985,8 @@
                 float scaleW = thumbWidth / appWidth;
                 float scaleH = thumbHeight / appHeight;
                 Animation scale = new ScaleAnimation(1, scaleW, 1, scaleH,
-                        computePivot(mNextAppTransitionStartX, scaleW),
-                        computePivot(mNextAppTransitionStartY, scaleH));
+                        computePivot(mTmpStartRect.left, scaleW),
+                        computePivot(mTmpStartRect.top, scaleH));
 
                 Animation alpha = new AlphaAnimation(1, 0);
 
@@ -987,7 +1018,7 @@
     Animation loadAnimation(WindowManager.LayoutParams lp, int transit, boolean enter,
             int appWidth, int appHeight, int orientation, Rect containingFrame, Rect contentInsets,
             @Nullable Rect surfaceInsets, Rect appFrame, boolean isVoiceInteraction,
-            boolean resizedWindow) {
+            boolean resizedWindow, int taskId) {
         Animation a;
         if (isVoiceInteraction && (transit == TRANSIT_ACTIVITY_OPEN
                 || transit == TRANSIT_TASK_OPEN
@@ -1042,7 +1073,7 @@
             mNextAppTransitionScaleUp =
                     (mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_UP);
             a = createThumbnailEnterExitAnimationLocked(getThumbnailTransitionState(enter),
-                    appWidth, appHeight, transit);
+                    appWidth, appHeight, transit, taskId);
             if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) {
                 String animName = mNextAppTransitionScaleUp ?
                         "ANIM_THUMBNAIL_SCALE_UP" : "ANIM_THUMBNAIL_SCALE_DOWN";
@@ -1057,7 +1088,7 @@
                     (mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP);
             a = createAspectScaledThumbnailEnterExitAnimationLocked(
                     getThumbnailTransitionState(enter), appWidth, appHeight, orientation, transit,
-                    containingFrame, contentInsets, surfaceInsets, resizedWindow);
+                    containingFrame, contentInsets, surfaceInsets, resizedWindow, taskId);
             if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) {
                 String animName = mNextAppTransitionScaleUp ?
                         "ANIM_THUMBNAIL_ASPECT_SCALE_UP" : "ANIM_THUMBNAIL_ASPECT_SCALE_DOWN";
@@ -1147,7 +1178,7 @@
         if (isTransitionSet()) {
             mNextAppTransitionType = NEXT_TRANSIT_TYPE_CUSTOM;
             mNextAppTransitionPackage = packageName;
-            mNextAppTransitionThumbnail = null;
+            mNextAppTransitionAnimationsSpecs.clear();
             mNextAppTransitionEnter = enterAnim;
             mNextAppTransitionExit = exitAnim;
             postAnimationCallback();
@@ -1158,15 +1189,13 @@
     }
 
     void overridePendingAppTransitionScaleUp(int startX, int startY, int startWidth,
-                                                    int startHeight) {
+            int startHeight) {
         if (isTransitionSet()) {
             mNextAppTransitionType = NEXT_TRANSIT_TYPE_SCALE_UP;
             mNextAppTransitionPackage = null;
-            mNextAppTransitionThumbnail = null;
-            mNextAppTransitionStartX = startX;
-            mNextAppTransitionStartY = startY;
-            mNextAppTransitionStartWidth = startWidth;
-            mNextAppTransitionStartHeight = startHeight;
+            mNextAppTransitionAnimationsSpecs.clear();
+            putDefaultNextAppTransitionCoordinates(startX, startY, startX + startWidth,
+                    startY + startHeight);
             postAnimationCallback();
             mNextAppTransitionCallback = null;
         }
@@ -1176,10 +1205,7 @@
                                                 int startWidth, int startHeight) {
         if (isTransitionSet()) {
             mNextAppTransitionType = NEXT_TRANSIT_TYPE_CLIP_REVEAL;
-            mNextAppTransitionStartX = startX;
-            mNextAppTransitionStartY = startY;
-            mNextAppTransitionStartWidth = startWidth;
-            mNextAppTransitionStartHeight = startHeight;
+            putDefaultNextAppTransitionCoordinates(startX, startY, startWidth, startHeight);
             postAnimationCallback();
             mNextAppTransitionCallback = null;
         }
@@ -1191,10 +1217,9 @@
             mNextAppTransitionType = scaleUp ? NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_UP
                     : NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_DOWN;
             mNextAppTransitionPackage = null;
-            mNextAppTransitionThumbnail = srcThumb;
+            mNextAppTransitionAnimationsSpecs.clear();
             mNextAppTransitionScaleUp = scaleUp;
-            mNextAppTransitionStartX = startX;
-            mNextAppTransitionStartY = startY;
+            putDefaultNextAppTransitionCoordinates(startX, startY, 0 ,0);
             postAnimationCallback();
             mNextAppTransitionCallback = startedCallback;
         } else {
@@ -1208,12 +1233,9 @@
             mNextAppTransitionType = scaleUp ? NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP
                     : NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_DOWN;
             mNextAppTransitionPackage = null;
-            mNextAppTransitionThumbnail = srcThumb;
+            mNextAppTransitionAnimationsSpecs.clear();
             mNextAppTransitionScaleUp = scaleUp;
-            mNextAppTransitionStartX = startX;
-            mNextAppTransitionStartY = startY;
-            mNextAppTransitionStartWidth = targetWidth;
-            mNextAppTransitionStartHeight = targetHeight;
+            putDefaultNextAppTransitionCoordinates(startX, startY, targetWidth, targetHeight);
             postAnimationCallback();
             mNextAppTransitionCallback = startedCallback;
         } else {
@@ -1221,6 +1243,27 @@
         }
     }
 
+    public void overridePendingAppTransitionMultiThumb(AppTransitionAnimationSpec[] specs,
+            IRemoteCallback callback, boolean scaleUp) {
+        if (isTransitionSet()) {
+            mNextAppTransitionType = scaleUp ? NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP
+                    : NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_DOWN;
+            mNextAppTransitionPackage = null;
+            mNextAppTransitionAnimationsSpecs.clear();
+            mNextAppTransitionScaleUp = scaleUp;
+            for (int i = 0; i < specs.length; i++) {
+                AppTransitionAnimationSpec spec = specs[i];
+                if (spec != null) {
+                    mNextAppTransitionAnimationsSpecs.put(spec.taskId, spec);
+                }
+            }
+            postAnimationCallback();
+            mNextAppTransitionCallback = callback;
+        } else {
+            postAnimationCallback();
+        }
+    }
+
     void overrideInPlaceAppTransition(String packageName, int anim) {
         if (isTransitionSet()) {
             mNextAppTransitionType = NEXT_TRANSIT_TYPE_CUSTOM_IN_PLACE;
@@ -1350,33 +1393,30 @@
                 pw.print(prefix); pw.print("mNextAppTransitionInPlace=0x");
                         pw.print(Integer.toHexString(mNextAppTransitionInPlace));
                 break;
-            case NEXT_TRANSIT_TYPE_SCALE_UP:
+            case NEXT_TRANSIT_TYPE_SCALE_UP: {
+                getDefaultNextAppTransitionStartRect(mTmpStartRect);
                 pw.print(prefix); pw.print("mNextAppTransitionStartX=");
-                        pw.print(mNextAppTransitionStartX);
+                        pw.print(mTmpStartRect.left);
                         pw.print(" mNextAppTransitionStartY=");
-                        pw.println(mNextAppTransitionStartY);
+                        pw.println(mTmpStartRect.top);
                 pw.print(prefix); pw.print("mNextAppTransitionStartWidth=");
-                        pw.print(mNextAppTransitionStartWidth);
+                        pw.print(mTmpStartRect.width());
                         pw.print(" mNextAppTransitionStartHeight=");
-                        pw.println(mNextAppTransitionStartHeight);
+                        pw.println(mTmpStartRect.height());
                 break;
+            }
             case NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_UP:
             case NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_DOWN:
             case NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP:
-            case NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_DOWN:
-                pw.print(prefix); pw.print("mNextAppTransitionThumbnail=");
-                        pw.print(mNextAppTransitionThumbnail);
-                        pw.print(" mNextAppTransitionStartX=");
-                        pw.print(mNextAppTransitionStartX);
-                        pw.print(" mNextAppTransitionStartY=");
-                        pw.println(mNextAppTransitionStartY);
-                pw.print(prefix); pw.print("mNextAppTransitionStartWidth=");
-                        pw.print(mNextAppTransitionStartWidth);
-                        pw.print(" mNextAppTransitionStartHeight=");
-                        pw.println(mNextAppTransitionStartHeight);
+            case NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_DOWN: {
+                pw.print(prefix); pw.print("mDefaultNextAppTransitionAnimationSpec=");
+                        pw.println(mDefaultNextAppTransitionAnimationSpec);
+                pw.print(prefix); pw.print("mNextAppTransitionAnimationsSpecs=");
+                        pw.println(mNextAppTransitionAnimationsSpecs);
                 pw.print(prefix); pw.print("mNextAppTransitionScaleUp=");
                         pw.println(mNextAppTransitionScaleUp);
                 break;
+            }
         }
         if (mNextAppTransitionCallback != null) {
             pw.print(prefix); pw.print("mNextAppTransitionCallback=");
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 355b09b..946043a 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -78,6 +78,7 @@
 import android.util.SparseIntArray;
 import android.util.TimeUtils;
 import android.util.TypedValue;
+import android.view.AppTransitionAnimationSpec;
 import android.view.Choreographer;
 import android.view.Display;
 import android.view.DisplayInfo;
@@ -443,6 +444,7 @@
 
     final float[] mTmpFloats = new float[9];
     final Rect mTmpContentRect = new Rect();
+    private final Rect mTmpStartRect = new Rect();
 
     boolean mDisplayReady;
     boolean mSafeMode;
@@ -3495,7 +3497,8 @@
             final boolean resizedWindow = !fullscreen && !dialogWindow;
             Animation a = mAppTransition.loadAnimation(lp, transit, enter, containingWidth,
                     containingHeight, mCurConfiguration.orientation, containingFrame, contentInsets,
-                    surfaceInsets, appFrame, isVoiceInteraction, resizedWindow);
+                    surfaceInsets, appFrame, isVoiceInteraction, resizedWindow,
+                    atoken.mTask.mTaskId);
             if (a != null) {
                 if (DEBUG_ANIM) {
                     RuntimeException e = null;
@@ -4219,6 +4222,14 @@
     }
 
     @Override
+    public void overridePendingAppTransitionMultiThumb(AppTransitionAnimationSpec[] specs,
+            IRemoteCallback callback, boolean scaleUp) {
+        synchronized (mWindowMap) {
+            mAppTransition.overridePendingAppTransitionMultiThumb(specs, callback, scaleUp);
+        }
+    }
+
+    @Override
     public void overridePendingAppTransitionInPlace(String packageName, int anim) {
         synchronized(mWindowMap) {
             mAppTransition.overrideInPlaceAppTransition(packageName, anim);
@@ -9402,11 +9413,6 @@
                 animLp = null;
             }
 
-            AppWindowToken topOpeningApp = null;
-            AppWindowToken topClosingApp = null;
-            int topOpeningLayer = 0;
-            int topClosingLayer = 0;
-
             // Process all applications animating in place
             if (transit == AppTransition.TRANSIT_TASK_IN_PLACE) {
                 // Find the focused window
@@ -9431,49 +9437,8 @@
                 }
             }
 
-            appsCount = mOpeningApps.size();
-            for (i = 0; i < appsCount; i++) {
-                AppWindowToken wtoken = mOpeningApps.valueAt(i);
-                final AppWindowAnimator appAnimator = wtoken.mAppAnimator;
-                if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "Now opening app" + wtoken);
-
-                if (!appAnimator.usingTransferredAnimation) {
-                    appAnimator.clearThumbnail();
-                    appAnimator.animation = null;
-                }
-                wtoken.inPendingTransaction = false;
-                if (!setTokenVisibilityLocked(
-                        wtoken, animLp, true, transit, false, voiceInteraction)){
-                    // This token isn't going to be animating. Add it to the list of tokens to
-                    // be notified of app transition complete since the notification will not be
-                    // sent be the app window animator.
-                    mNoAnimationNotifyOnTransitionFinished.add(wtoken.token);
-                }
-                wtoken.updateReportedVisibilityLocked();
-                wtoken.waitingToShow = false;
-
-                appAnimator.mAllAppWinAnimators.clear();
-                final int windowsCount = wtoken.allAppWindows.size();
-                for (int j = 0; j < windowsCount; j++) {
-                    appAnimator.mAllAppWinAnimators.add(wtoken.allAppWindows.get(j).mWinAnimator);
-                }
-                mAnimator.mAnimating |= appAnimator.showAllWindowsLocked();
-                mAnimator.mAppWindowAnimating |= appAnimator.isAnimating();
-
-                if (animLp != null) {
-                    int layer = -1;
-                    for (int j = 0; j < wtoken.windows.size(); j++) {
-                        WindowState win = wtoken.windows.get(j);
-                        if (win.mWinAnimator.mAnimLayer > layer) {
-                            layer = win.mWinAnimator.mAnimLayer;
-                        }
-                    }
-                    if (topOpeningApp == null || layer > topOpeningLayer) {
-                        topOpeningApp = wtoken;
-                        topOpeningLayer = layer;
-                    }
-                }
-            }
+            AppWindowToken topClosingApp = null;
+            int topClosingLayer = 0;
             appsCount = mClosingApps.size();
             for (i = 0; i < appsCount; i++) {
                 AppWindowToken wtoken = mClosingApps.valueAt(i);
@@ -9511,77 +9476,57 @@
                 }
             }
 
-            AppWindowAnimator openingAppAnimator = (topOpeningApp == null) ? null :
+            AppWindowToken topOpeningApp = null;
+            appsCount = mOpeningApps.size();
+            for (i = 0; i < appsCount; i++) {
+                AppWindowToken wtoken = mOpeningApps.valueAt(i);
+                final AppWindowAnimator appAnimator = wtoken.mAppAnimator;
+                if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "Now opening app" + wtoken);
+
+                if (!appAnimator.usingTransferredAnimation) {
+                    appAnimator.clearThumbnail();
+                    appAnimator.animation = null;
+                }
+                wtoken.inPendingTransaction = false;
+                if (!setTokenVisibilityLocked(
+                        wtoken, animLp, true, transit, false, voiceInteraction)){
+                    // This token isn't going to be animating. Add it to the list of tokens to
+                    // be notified of app transition complete since the notification will not be
+                    // sent be the app window animator.
+                    mNoAnimationNotifyOnTransitionFinished.add(wtoken.token);
+                }
+                wtoken.updateReportedVisibilityLocked();
+                wtoken.waitingToShow = false;
+
+                appAnimator.mAllAppWinAnimators.clear();
+                final int windowsCount = wtoken.allAppWindows.size();
+                for (int j = 0; j < windowsCount; j++) {
+                    appAnimator.mAllAppWinAnimators.add(wtoken.allAppWindows.get(j).mWinAnimator);
+                }
+                mAnimator.mAnimating |= appAnimator.showAllWindowsLocked();
+                mAnimator.mAppWindowAnimating |= appAnimator.isAnimating();
+
+                int topOpeningLayer = 0;
+                if (animLp != null) {
+                    int layer = -1;
+                    for (int j = 0; j < wtoken.windows.size(); j++) {
+                        WindowState win = wtoken.windows.get(j);
+                        if (win.mWinAnimator.mAnimLayer > layer) {
+                            layer = win.mWinAnimator.mAnimLayer;
+                        }
+                    }
+                    if (topOpeningApp == null || layer > topOpeningLayer) {
+                        topOpeningApp = wtoken;
+                        topOpeningLayer = layer;
+                    }
+                }
+                createThumbnailAppAnimator(transit, wtoken, topOpeningLayer, topClosingLayer);
+            }
+
+            AppWindowAnimator openingAppAnimator = (topOpeningApp == null) ?  null :
                     topOpeningApp.mAppAnimator;
             AppWindowAnimator closingAppAnimator = (topClosingApp == null) ? null :
                     topClosingApp.mAppAnimator;
-            Bitmap nextAppTransitionThumbnail = mAppTransition.getNextAppTransitionThumbnail();
-            if (nextAppTransitionThumbnail != null
-                    && openingAppAnimator != null && openingAppAnimator.animation != null &&
-                    nextAppTransitionThumbnail.getConfig() != Config.ALPHA_8) {
-                // This thumbnail animation is very special, we need to have
-                // an extra surface with the thumbnail included with the animation.
-                Rect dirty = new Rect(0, 0, nextAppTransitionThumbnail.getWidth(),
-                        nextAppTransitionThumbnail.getHeight());
-                try {
-                    // TODO(multi-display): support other displays
-                    final DisplayContent displayContent = getDefaultDisplayContentLocked();
-                    final Display display = displayContent.getDisplay();
-                    final DisplayInfo displayInfo = displayContent.getDisplayInfo();
-
-                    // Create a new surface for the thumbnail
-                    SurfaceControl surfaceControl = new SurfaceControl(mFxSession,
-                            "thumbnail anim", dirty.width(), dirty.height(),
-                            PixelFormat.TRANSLUCENT, SurfaceControl.HIDDEN);
-                    surfaceControl.setLayerStack(display.getLayerStack());
-                    if (SHOW_TRANSACTIONS) {
-                        Slog.i(TAG, "  THUMBNAIL " + surfaceControl + ": CREATE");
-                    }
-
-                    // Draw the thumbnail onto the surface
-                    Surface drawSurface = new Surface();
-                    drawSurface.copyFrom(surfaceControl);
-                    Canvas c = drawSurface.lockCanvas(dirty);
-                    c.drawBitmap(nextAppTransitionThumbnail, 0, 0, null);
-                    drawSurface.unlockCanvasAndPost(c);
-                    drawSurface.release();
-
-                    // Get the thumbnail animation
-                    Animation anim;
-                    if (mAppTransition.isNextThumbnailTransitionAspectScaled()) {
-                        // If this is a multi-window scenario, we use the windows frame as
-                        // destination of the thumbnail header animation. If this is a full screen
-                        // window scenario, we use the whole display as the target.
-                        WindowState win = topOpeningApp.findMainWindow();
-                        Rect appRect = win != null ? win.getContentFrameLw() :
-                                new Rect(0, 0, displayInfo.appWidth, displayInfo.appHeight);
-                        // For the new aspect-scaled transition, we want it to always show
-                        // above the animating opening/closing window, and we want to
-                        // synchronize its thumbnail surface with the surface for the
-                        // open/close animation (only on the way down)
-                        anim = mAppTransition.createThumbnailAspectScaleAnimationLocked(appRect);
-                        openingAppAnimator.thumbnailForceAboveLayer = Math.max(topOpeningLayer,
-                                topClosingLayer);
-                        openingAppAnimator.deferThumbnailDestruction =
-                                !mAppTransition.isNextThumbnailTransitionScaleUp();
-                    } else {
-                        anim = mAppTransition.createThumbnailScaleAnimationLocked(
-                                displayInfo.appWidth, displayInfo.appHeight, transit);
-                    }
-                    anim.restrictDuration(MAX_ANIMATION_DURATION);
-                    anim.scaleCurrentDuration(getTransitionAnimationScaleLocked());
-
-                    openingAppAnimator.thumbnail = surfaceControl;
-                    openingAppAnimator.thumbnailLayer = topOpeningLayer;
-                    openingAppAnimator.thumbnailAnimation = anim;
-                    openingAppAnimator.thumbnailX = mAppTransition.getStartingX();
-                    openingAppAnimator.thumbnailY = mAppTransition.getStartingY();
-                } catch (OutOfResourcesException e) {
-                    Slog.e(TAG, "Can't allocate thumbnail/Canvas surface w=" + dirty.width()
-                            + " h=" + dirty.height(), e);
-                    openingAppAnimator.clearThumbnail();
-                }
-            }
 
             mAppTransition.goodToGo(openingAppAnimator, closingAppAnimator);
             mAppTransition.postAnimationCallback();
@@ -9609,6 +9554,83 @@
         return changes;
     }
 
+    private void createThumbnailAppAnimator(int transit, AppWindowToken appToken,
+            int openingLayer, int closingLayer) {
+        AppWindowAnimator openingAppAnimator = (appToken == null) ? null : appToken.mAppAnimator;
+        if (openingAppAnimator == null || openingAppAnimator.animation == null) {
+            return;
+        }
+        final int taskId = appToken.mTask.mTaskId;
+        Bitmap thumbnailHeader = mAppTransition.getAppTransitionThumbnailHeader(taskId);
+        if (thumbnailHeader == null || thumbnailHeader.getConfig() == Config.ALPHA_8) {
+            return;
+        }
+        // This thumbnail animation is very special, we need to have
+        // an extra surface with the thumbnail included with the animation.
+        Rect dirty = new Rect(0, 0, thumbnailHeader.getWidth(), thumbnailHeader.getHeight());
+        try {
+            // TODO(multi-display): support other displays
+            final DisplayContent displayContent = getDefaultDisplayContentLocked();
+            final Display display = displayContent.getDisplay();
+            final DisplayInfo displayInfo = displayContent.getDisplayInfo();
+
+            // Create a new surface for the thumbnail
+            SurfaceControl surfaceControl = new SurfaceControl(mFxSession,
+                    "thumbnail anim", dirty.width(), dirty.height(),
+                    PixelFormat.TRANSLUCENT, SurfaceControl.HIDDEN);
+            surfaceControl.setLayerStack(display.getLayerStack());
+            if (SHOW_TRANSACTIONS) {
+                Slog.i(TAG, "  THUMBNAIL " + surfaceControl + ": CREATE");
+            }
+
+            // Draw the thumbnail onto the surface
+            Surface drawSurface = new Surface();
+            drawSurface.copyFrom(surfaceControl);
+            Canvas c = drawSurface.lockCanvas(dirty);
+            c.drawBitmap(thumbnailHeader, 0, 0, null);
+            drawSurface.unlockCanvasAndPost(c);
+            drawSurface.release();
+
+            // Get the thumbnail animation
+            Animation anim;
+            if (mAppTransition.isNextThumbnailTransitionAspectScaled()) {
+                // If this is a multi-window scenario, we use the windows frame as
+                // destination of the thumbnail header animation. If this is a full screen
+                // window scenario, we use the whole display as the target.
+                WindowState win = appToken.findMainWindow();
+                Rect appRect = win != null ? win.getContentFrameLw() :
+                        new Rect(0, 0, displayInfo.appWidth, displayInfo.appHeight);
+                // For the new aspect-scaled transition, we want it to always show
+                // above the animating opening/closing window, and we want to
+                // synchronize its thumbnail surface with the surface for the
+                // open/close animation (only on the way down)
+                anim = mAppTransition.createThumbnailAspectScaleAnimationLocked(appRect,
+                        thumbnailHeader, taskId);
+                Log.d(TAG, "assigning thumbnail force above layer: " + openingLayer + " " +
+                        closingLayer);
+                openingAppAnimator.thumbnailForceAboveLayer = Math.max(openingLayer, closingLayer);
+                openingAppAnimator.deferThumbnailDestruction =
+                        !mAppTransition.isNextThumbnailTransitionScaleUp();
+            } else {
+                anim = mAppTransition.createThumbnailScaleAnimationLocked(
+                        displayInfo.appWidth, displayInfo.appHeight, transit, thumbnailHeader);
+            }
+            anim.restrictDuration(MAX_ANIMATION_DURATION);
+            anim.scaleCurrentDuration(getTransitionAnimationScaleLocked());
+
+            openingAppAnimator.thumbnail = surfaceControl;
+            openingAppAnimator.thumbnailLayer = openingLayer;
+            openingAppAnimator.thumbnailAnimation = anim;
+            mAppTransition.getNextAppTransitionStartRect(taskId, mTmpStartRect);
+            openingAppAnimator.thumbnailX = mTmpStartRect.left;
+            openingAppAnimator.thumbnailY = mTmpStartRect.top;
+        } catch (OutOfResourcesException e) {
+            Slog.e(TAG, "Can't allocate thumbnail/Canvas surface w=" + dirty.width()
+                    + " h=" + dirty.height(), e);
+            openingAppAnimator.clearThumbnail();
+        }
+    }
+
     /**
      * Extracted from {@link #performLayoutAndPlaceSurfacesLockedInner} to reduce size of method.
      * @return bitmap indicating if another pass through layout must be made.