Merge "Add new thumbnail animation."
diff --git a/api/current.txt b/api/current.txt
index 4398ea3..5719793 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -2744,6 +2744,7 @@
     method public static boolean isUserAMonkey();
     method public void killBackgroundProcesses(java.lang.String);
     method public void moveTaskToFront(int, int);
+    method public void moveTaskToFront(int, int, android.os.Bundle);
     method public deprecated void restartPackage(java.lang.String);
     field public static final int MOVE_TASK_NO_USER_ACTION = 2; // 0x2
     field public static final int MOVE_TASK_WITH_HOME = 1; // 0x1
@@ -2867,9 +2868,14 @@
   public class ActivityOptions {
     method public void join(android.app.ActivityOptions);
     method public static android.app.ActivityOptions makeCustomAnimation(android.content.Context, int, int);
+    method public static android.app.ActivityOptions makeThumbnailScaleUpAnimation(android.view.View, android.graphics.Bitmap, int, int, android.app.ActivityOptions.OnAnimationStartedListener);
     method public android.os.Bundle toBundle();
   }
 
+  public static abstract interface ActivityOptions.OnAnimationStartedListener {
+    method public abstract void onAnimationStarted();
+  }
+
   public class AlarmManager {
     method public void cancel(android.app.PendingIntent);
     method public void set(int, long, android.app.PendingIntent);
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index ea32745..b277efb 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -3639,7 +3639,7 @@
      */
     public void startActivityFromChild(Activity child, Intent intent,
             int requestCode) {
-        startActivityFromChild(child, intent, requestCode);
+        startActivityFromChild(child, intent, requestCode, null);
     }
 
     /**
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index d056b17..531a695 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -31,6 +31,7 @@
 import android.graphics.Bitmap;
 import android.graphics.Point;
 import android.os.Binder;
+import android.os.Bundle;
 import android.os.Debug;
 import android.os.Handler;
 import android.os.Parcel;
@@ -816,6 +817,19 @@
     public static final int MOVE_TASK_NO_USER_ACTION = 0x00000002;
 
     /**
+     * Equivalent to calling {@link #moveTaskToFront(int, int, Bundle)}
+     * with a null options argument.
+     *
+     * @param taskId The identifier of the task to be moved, as found in
+     * {@link RunningTaskInfo} or {@link RecentTaskInfo}.
+     * @param flags Additional operational flags, 0 or more of
+     * {@link #MOVE_TASK_WITH_HOME}.
+     */
+    public void moveTaskToFront(int taskId, int flags) {
+        moveTaskToFront(taskId, flags, null);
+    }
+
+    /**
      * Ask that the task associated with a given task ID be moved to the
      * front of the stack, so it is now visible to the user.  Requires that
      * the caller hold permission {@link android.Manifest.permission#REORDER_TASKS}
@@ -825,10 +839,13 @@
      * {@link RunningTaskInfo} or {@link RecentTaskInfo}.
      * @param flags Additional operational flags, 0 or more of
      * {@link #MOVE_TASK_WITH_HOME}.
+     * @param options Additional options for the operation, either null or
+     * as per {@link Context#startActivity(Intent, android.os.Bundle)
+     * Context.startActivity(Intent, Bundle)}.
      */
-    public void moveTaskToFront(int taskId, int flags) {
+    public void moveTaskToFront(int taskId, int flags, Bundle options) {
         try {
-            ActivityManagerNative.getDefault().moveTaskToFront(taskId, flags);
+            ActivityManagerNative.getDefault().moveTaskToFront(taskId, flags, options);
         } catch (RemoteException e) {
             // System dead, we will be dead too soon!
         }
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index a3cc352..c000bdf 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -510,7 +510,9 @@
             data.enforceInterface(IActivityManager.descriptor);
             int task = data.readInt();
             int fl = data.readInt();
-            moveTaskToFront(task, fl);
+            Bundle options = data.readInt() != 0
+                    ? Bundle.CREATOR.createFromParcel(data) : null;
+            moveTaskToFront(task, fl, options);
             reply.writeNoException();
             return true;
         }
@@ -2134,13 +2136,19 @@
         reply.recycle();
         return list;
     }
-    public void moveTaskToFront(int task, int flags) throws RemoteException
+    public void moveTaskToFront(int task, int flags, Bundle options) throws RemoteException
     {
         Parcel data = Parcel.obtain();
         Parcel reply = Parcel.obtain();
         data.writeInterfaceToken(IActivityManager.descriptor);
         data.writeInt(task);
         data.writeInt(flags);
+        if (options != null) {
+            data.writeInt(1);
+            options.writeToParcel(data, 0);
+        } else {
+            data.writeInt(0);
+        }
         mRemote.transact(MOVE_TASK_TO_FRONT_TRANSACTION, data, reply, 0);
         reply.readException();
         data.recycle();
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 03bc338..c637df0 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -17,7 +17,13 @@
 package android.app;
 
 import android.content.Context;
+import android.graphics.Bitmap;
 import android.os.Bundle;
+import android.os.Handler;
+import android.os.IRemoteCallback;
+import android.os.Message;
+import android.os.RemoteException;
+import android.view.View;
 
 /**
  * Helper class for building an options Bundle that can be used with
@@ -32,6 +38,12 @@
     public static final String KEY_PACKAGE_NAME = "android:packageName";
 
     /**
+     * Type of animation that arguments specify.
+     * @hide
+     */
+    public static final String KEY_ANIM_TYPE = "android:animType";
+
+    /**
      * Custom enter animation resource ID.
      * @hide
      */
@@ -43,10 +55,45 @@
      */
     public static final String KEY_ANIM_EXIT_RES_ID = "android:animExitRes";
 
+    /**
+     * Bitmap for thumbnail animation.
+     * @hide
+     */
+    public static final String KEY_ANIM_THUMBNAIL = "android:animThumbnail";
+
+    /**
+     * Start X position of thumbnail animation.
+     * @hide
+     */
+    public static final String KEY_ANIM_START_X = "android:animStartX";
+
+    /**
+     * Start Y position of thumbnail animation.
+     * @hide
+     */
+    public static final String KEY_ANIM_START_Y = "android:animStartY";
+
+    /**
+     * Callback for when animation is started.
+     * @hide
+     */
+    public static final String KEY_ANIM_START_LISTENER = "android:animStartListener";
+
+    /** @hide */
+    public static final int ANIM_NONE = 0;
+    /** @hide */
+    public static final int ANIM_CUSTOM = 1;
+    /** @hide */
+    public static final int ANIM_THUMBNAIL = 2;
+
     private String mPackageName;
-    private boolean mIsCustomAnimation;
+    private int mAnimationType = ANIM_NONE;
     private int mCustomEnterResId;
     private int mCustomExitResId;
+    private Bitmap mThumbnail;
+    private int mStartX;
+    private int mStartY;
+    private IRemoteCallback mAnimationStartedListener;
 
     /**
      * Create an ActivityOptions specifying a custom animation to run when
@@ -65,22 +112,79 @@
             int enterResId, int exitResId) {
         ActivityOptions opts = new ActivityOptions();
         opts.mPackageName = context.getPackageName();
-        opts.mIsCustomAnimation = true;
+        opts.mAnimationType = ANIM_CUSTOM;
         opts.mCustomEnterResId = enterResId;
         opts.mCustomExitResId = exitResId;
         return opts;
     }
 
+    /**
+     * Callback for use with {@link ActivityOptions#makeThumbnailScaleUpAnimation}
+     * to find out when the given animation has started running.
+     */
+    public interface OnAnimationStartedListener {
+        void onAnimationStarted();
+    }
+
+    /**
+     * Create an ActivityOptions specifying an animation where a thumbnail
+     * is scaled from a given position to the new activity window that is
+     * being started.
+     *
+     * @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, in screen coordiantes.
+     * @param startY The y starting location of the bitmap, in screen coordinates.
+     * @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.
+     */
+    public static ActivityOptions makeThumbnailScaleUpAnimation(View source,
+            Bitmap thumbnail, int startX, int startY, OnAnimationStartedListener listener) {
+        ActivityOptions opts = new ActivityOptions();
+        opts.mPackageName = source.getContext().getPackageName();
+        opts.mAnimationType = ANIM_THUMBNAIL;
+        opts.mThumbnail = thumbnail;
+        int[] pts = new int[2];
+        source.getLocationOnScreen(pts);
+        opts.mStartX = pts[0] + startX;
+        opts.mStartY = pts[1] + startY;
+        if (listener != null) {
+            final Handler h = source.getHandler();
+            final OnAnimationStartedListener finalListener = listener;
+            opts.mAnimationStartedListener = new IRemoteCallback.Stub() {
+                @Override public void sendResult(Bundle data) throws RemoteException {
+                    h.post(new Runnable() {
+                        @Override public void run() {
+                            finalListener.onAnimationStarted();
+                        }
+                    });
+                }
+            };
+        }
+        return opts;
+    }
+
     private ActivityOptions() {
     }
 
     /** @hide */
     public ActivityOptions(Bundle opts) {
         mPackageName = opts.getString(KEY_PACKAGE_NAME);
-        if (opts.containsKey(KEY_ANIM_ENTER_RES_ID)) {
-            mIsCustomAnimation = true;
+        mAnimationType = opts.getInt(KEY_ANIM_TYPE);
+        if (mAnimationType == ANIM_CUSTOM) {
             mCustomEnterResId = opts.getInt(KEY_ANIM_ENTER_RES_ID, 0);
             mCustomExitResId = opts.getInt(KEY_ANIM_EXIT_RES_ID, 0);
+        } else if (mAnimationType == ANIM_THUMBNAIL) {
+            mThumbnail = (Bitmap)opts.getParcelable(KEY_ANIM_THUMBNAIL);
+            mStartX = opts.getInt(KEY_ANIM_START_X, 0);
+            mStartY = opts.getInt(KEY_ANIM_START_Y, 0);
+            mAnimationStartedListener = IRemoteCallback.Stub.asInterface(
+                    opts.getIBinder(KEY_ANIM_START_LISTENER));
         }
     }
 
@@ -90,8 +194,8 @@
     }
 
     /** @hide */
-    public boolean isCustomAnimation() {
-        return mIsCustomAnimation;
+    public int getAnimationType() {
+        return mAnimationType;
     }
 
     /** @hide */
@@ -104,6 +208,43 @@
         return mCustomExitResId;
     }
 
+    /** @hide */
+    public Bitmap getThumbnail() {
+        return mThumbnail;
+    }
+
+    /** @hide */
+    public int getStartX() {
+        return mStartX;
+    }
+
+    /** @hide */
+    public int getStartY() {
+        return mStartY;
+    }
+
+    /** @hide */
+    public IRemoteCallback getOnAnimationStartListener() {
+        return mAnimationStartedListener;
+    }
+
+    /** @hide */
+    public void abort() {
+        if (mAnimationStartedListener != null) {
+            try {
+                mAnimationStartedListener.sendResult(null);
+            } catch (RemoteException e) {
+            }
+        }
+    }
+
+    /** @hide */
+    public static void abort(Bundle options) {
+        if (options != null) {
+            (new ActivityOptions(options)).abort();
+        }
+    }
+
     /**
      * Join the values in <var>otherOptions</var> in to this one.  Any values
      * defined in <var>otherOptions</var> replace those in the base options.
@@ -112,10 +253,27 @@
         if (otherOptions.mPackageName != null) {
             mPackageName = otherOptions.mPackageName;
         }
-        if (otherOptions.mIsCustomAnimation) {
-            mIsCustomAnimation = true;
-            mCustomEnterResId = otherOptions.mCustomEnterResId;
-            mCustomExitResId = otherOptions.mCustomExitResId;
+        switch (otherOptions.mAnimationType) {
+            case ANIM_CUSTOM:
+                mAnimationType = otherOptions.mAnimationType;
+                mCustomEnterResId = otherOptions.mCustomEnterResId;
+                mCustomExitResId = otherOptions.mCustomExitResId;
+                mThumbnail = null;
+                mAnimationStartedListener = null;
+                break;
+            case ANIM_THUMBNAIL:
+                mAnimationType = otherOptions.mAnimationType;
+                mThumbnail = otherOptions.mThumbnail;
+                mStartX = otherOptions.mStartX;
+                mStartY = otherOptions.mStartY;
+                if (otherOptions.mAnimationStartedListener != null) {
+                    try {
+                        otherOptions.mAnimationStartedListener.sendResult(null);
+                    } catch (RemoteException e) {
+                    }
+                }
+                mAnimationStartedListener = otherOptions.mAnimationStartedListener;
+                break;
         }
     }
 
@@ -132,9 +290,19 @@
         if (mPackageName != null) {
             b.putString(KEY_PACKAGE_NAME, mPackageName);
         }
-        if (mIsCustomAnimation) {
-            b.putInt(KEY_ANIM_ENTER_RES_ID, mCustomEnterResId);
-            b.putInt(KEY_ANIM_EXIT_RES_ID, mCustomExitResId);
+        switch (mAnimationType) {
+            case ANIM_CUSTOM:
+                b.putInt(KEY_ANIM_TYPE, mAnimationType);
+                b.putInt(KEY_ANIM_ENTER_RES_ID, mCustomEnterResId);
+                b.putInt(KEY_ANIM_EXIT_RES_ID, mCustomExitResId);
+                break;
+            case ANIM_THUMBNAIL:
+                b.putInt(KEY_ANIM_TYPE, mAnimationType);
+                b.putParcelable(KEY_ANIM_THUMBNAIL, mThumbnail);
+                b.putInt(KEY_ANIM_START_X, mStartX);
+                b.putInt(KEY_ANIM_START_Y, mStartY);
+                b.putIBinder(KEY_ANIM_START_LISTENER, mAnimationStartedListener
+                        != null ? mAnimationStartedListener.asBinder() : null);
         }
         return b;
     }
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 31066b5..a809cc1 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -105,7 +105,7 @@
     public List getServices(int maxNum, int flags) throws RemoteException;
     public List<ActivityManager.ProcessErrorStateInfo> getProcessesInErrorState()
             throws RemoteException;
-    public void moveTaskToFront(int task, int flags) throws RemoteException;
+    public void moveTaskToFront(int task, int flags, Bundle options) throws RemoteException;
     public void moveTaskToBack(int task) throws RemoteException;
     public boolean moveActivityTaskToBack(IBinder token, boolean nonRoot) throws RemoteException;
     public void moveTaskBackwards(int task) throws RemoteException;
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index c54d09e..14cd48f 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -89,6 +89,8 @@
     void prepareAppTransition(int transit, boolean alwaysKeepCurrent);
     int getPendingAppTransition();
     void overridePendingAppTransition(String packageName, int enterAnim, int exitAnim);
+    void overridePendingAppTransitionThumb(in Bitmap srcThumb, int startX, int startY,
+            IRemoteCallback startedCallback);
     void executeAppTransition();
     void setAppStartingWindow(IBinder token, String pkg, int theme,
             in CompatibilityInfo compatInfo, CharSequence nonLocalizedLabel, int labelRes,
diff --git a/core/java/android/view/animation/Animation.java b/core/java/android/view/animation/Animation.java
index dc8c71b7..d92ebcd 100644
--- a/core/java/android/view/animation/Animation.java
+++ b/core/java/android/view/animation/Animation.java
@@ -875,7 +875,7 @@
      * otherwise.
      *
      * @param currentTime Where we are in the animation. This is wall clock time.
-     * @param outTransformation A tranformation object that is provided by the
+     * @param outTransformation A transformation object that is provided by the
      *        caller and will be filled in by the animation.
      * @param scale Scaling factor to apply to any inputs to the transform operation, such
      *        pivot points being rotated or scaled around.
diff --git a/core/java/android/view/animation/Transformation.java b/core/java/android/view/animation/Transformation.java
index cf210c8..e8c1d23 100644
--- a/core/java/android/view/animation/Transformation.java
+++ b/core/java/android/view/animation/Transformation.java
@@ -112,6 +112,16 @@
     }
     
     /**
+     * Like {@link #compose(Transformation)} but does this.postConcat(t) of
+     * the transformation matrix.
+     * @hide
+     */
+    public void postCompose(Transformation t) {
+        mAlpha *= t.getAlpha();
+        mMatrix.postConcat(t.getMatrix());
+    }
+
+    /**
      * @return The 3x3 Matrix representing the trnasformation to apply to the
      * coordinates of the object being animated
      */
diff --git a/core/java/com/android/internal/app/HeavyWeightSwitcherActivity.java b/core/java/com/android/internal/app/HeavyWeightSwitcherActivity.java
index ef7e651..3d46cdd 100644
--- a/core/java/com/android/internal/app/HeavyWeightSwitcherActivity.java
+++ b/core/java/com/android/internal/app/HeavyWeightSwitcherActivity.java
@@ -123,7 +123,7 @@
     private OnClickListener mSwitchOldListener = new OnClickListener() {
         public void onClick(View v) {
             try {
-                ActivityManagerNative.getDefault().moveTaskToFront(mCurTask, 0);
+                ActivityManagerNative.getDefault().moveTaskToFront(mCurTask, 0, null);
             } catch (RemoteException e) {
             }
             finish();
diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java b/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java
index 66cb32c..5f314d6 100644
--- a/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java
@@ -19,6 +19,7 @@
 import android.animation.Animator;
 import android.animation.LayoutTransition;
 import android.app.ActivityManager;
+import android.app.ActivityOptions;
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.Configuration;
@@ -656,22 +657,33 @@
     }
 
     public void handleOnClick(View view) {
-        TaskDescription ad = ((ViewHolder) view.getTag()).taskDescription;
+        ViewHolder holder = (ViewHolder)view.getTag();
+        TaskDescription ad = holder.taskDescription;
         final Context context = view.getContext();
         final ActivityManager am = (ActivityManager)
                 context.getSystemService(Context.ACTIVITY_SERVICE);
+        holder.thumbnailViewImage.setDrawingCacheEnabled(true);
+        Bitmap bm = holder.thumbnailViewImage.getDrawingCache();
+        ActivityOptions opts = ActivityOptions.makeThumbnailScaleUpAnimation(
+                holder.thumbnailViewImage, bm, 0, 0,
+                new ActivityOptions.OnAnimationStartedListener() {
+                    @Override public void onAnimationStarted() {
+                        hide(true);
+                    }
+                });
         if (ad.taskId >= 0) {
             // This is an active task; it should just go to the foreground.
-            am.moveTaskToFront(ad.taskId, ActivityManager.MOVE_TASK_WITH_HOME);
+            am.moveTaskToFront(ad.taskId, ActivityManager.MOVE_TASK_WITH_HOME,
+                    opts.toBundle());
         } else {
             Intent intent = ad.intent;
             intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY
                     | Intent.FLAG_ACTIVITY_TASK_ON_HOME
                     | Intent.FLAG_ACTIVITY_NEW_TASK);
             if (DEBUG) Log.v(TAG, "Starting activity " + intent);
-            context.startActivity(intent);
+            context.startActivity(intent, opts.toBundle());
         }
-        hide(true);
+        holder.thumbnailViewImage.setDrawingCacheEnabled(false);
     }
 
     public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index b422678..84fcd5e 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -34,6 +34,7 @@
 import android.app.Activity;
 import android.app.ActivityManager;
 import android.app.ActivityManagerNative;
+import android.app.ActivityOptions;
 import android.app.ActivityThread;
 import android.app.AlertDialog;
 import android.app.AppGlobals;
@@ -2353,10 +2354,12 @@
         synchronized (this) {
             ActivityRecord r = mMainStack.isInStackLocked(callingActivity);
             if (r == null) {
+                ActivityOptions.abort(options);
                 return false;
             }
             if (r.app == null || r.app.thread == null) {
                 // The caller is not running...  d'oh!
+                ActivityOptions.abort(options);
                 return false;
             }
             intent = new Intent(intent);
@@ -2393,6 +2396,7 @@
 
             if (aInfo == null) {
                 // Nobody who is next!
+                ActivityOptions.abort(options);
                 return false;
             }
 
@@ -2422,8 +2426,6 @@
             }
 
             final long origId = Binder.clearCallingIdentity();
-            // XXX we are not dealing with propagating grantedUriPermissions...
-            // those are not yet exposed to user code, so there is no need.
             int res = mMainStack.startActivityLocked(r.app.thread, intent,
                     r.resolvedType, aInfo, resultTo != null ? resultTo.appToken : null,
                     resultWho, requestCode, -1, r.launchedFromUid, 0,
@@ -3653,7 +3655,7 @@
                 }
                 lastTask = r.task;
                 if (r.stack.finishActivityLocked(r, i, Activity.RESULT_CANCELED,
-                        null, "force-stop")) {
+                        null, "force-stop", true)) {
                     i--;
                 }
             }
@@ -5686,13 +5688,14 @@
     /**
      * TODO: Add mController hook
      */
-    public void moveTaskToFront(int task, int flags) {
+    public void moveTaskToFront(int task, int flags, Bundle options) {
         enforceCallingPermission(android.Manifest.permission.REORDER_TASKS,
                 "moveTaskToFront()");
 
         synchronized(this) {
             if (!checkAppSwitchAllowedLocked(Binder.getCallingPid(),
                     Binder.getCallingUid(), "Task to front")) {
+                ActivityOptions.abort(options);
                 return;
             }
             final long origId = Binder.clearCallingIdentity();
@@ -5707,7 +5710,7 @@
                         // we'll just move the home task to the top first.
                         mMainStack.moveHomeToFrontLocked();
                     }
-                    mMainStack.moveTaskToFrontLocked(tr, null);
+                    mMainStack.moveTaskToFrontLocked(tr, null, options);
                     return;
                 }
                 for (int i=mMainStack.mHistory.size()-1; i>=0; i--) {
@@ -5721,13 +5724,14 @@
                             // we'll just move the home task to the top first.
                             mMainStack.moveHomeToFrontLocked();
                         }
-                        mMainStack.moveTaskToFrontLocked(hr.task, null);
+                        mMainStack.moveTaskToFrontLocked(hr.task, null, options);
                         return;
                     }
                 }
             } finally {
                 Binder.restoreCallingIdentity(origId);
             }
+            ActivityOptions.abort(options);
         }
     }
 
diff --git a/services/java/com/android/server/am/ActivityRecord.java b/services/java/com/android/server/am/ActivityRecord.java
index 53cb2b0..d60ff2b 100644
--- a/services/java/com/android/server/am/ActivityRecord.java
+++ b/services/java/com/android/server/am/ActivityRecord.java
@@ -26,7 +26,6 @@
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.res.CompatibilityInfo;
-import android.content.pm.PackageManager;
 import android.content.res.Configuration;
 import android.graphics.Bitmap;
 import android.os.Build;
@@ -542,24 +541,38 @@
 
     void updateOptionsLocked(Bundle options) {
         if (options != null) {
+            if (pendingOptions != null) {
+                pendingOptions.abort();
+            }
             pendingOptions = new ActivityOptions(options);
         }
     }
 
     void applyOptionsLocked() {
         if (pendingOptions != null) {
-            if (pendingOptions.isCustomAnimation()) {
-                service.mWindowManager.overridePendingAppTransition(
-                        pendingOptions.getPackageName(),
-                        pendingOptions.getCustomEnterResId(),
-                        pendingOptions.getCustomExitResId());
+            switch (pendingOptions.getAnimationType()) {
+                case ActivityOptions.ANIM_CUSTOM:
+                    service.mWindowManager.overridePendingAppTransition(
+                            pendingOptions.getPackageName(),
+                            pendingOptions.getCustomEnterResId(),
+                            pendingOptions.getCustomExitResId());
+                    break;
+                case ActivityOptions.ANIM_THUMBNAIL:
+                    service.mWindowManager.overridePendingAppTransitionThumb(
+                            pendingOptions.getThumbnail(),
+                            pendingOptions.getStartX(), pendingOptions.getStartY(),
+                            pendingOptions.getOnAnimationStartListener());
+                    break;
             }
             pendingOptions = null;
         }
     }
 
     void clearOptionsLocked() {
-        pendingOptions = null;
+        if (pendingOptions != null) {
+            pendingOptions.abort();
+            pendingOptions = null;
+        }
     }
 
     void removeUriPermissionsLocked() {
diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java
index 48b4f4ff..a01ed25 100644
--- a/services/java/com/android/server/am/ActivityStack.java
+++ b/services/java/com/android/server/am/ActivityStack.java
@@ -1695,6 +1695,7 @@
                         if (VALIDATE_TOKENS) {
                             validateAppTokensLocked();
                         }
+                        ActivityOptions.abort(options);
                         return;
                     }
                     break;
@@ -1797,6 +1798,7 @@
             // because there is nothing for it to animate on top of.
             mService.mWindowManager.addAppToken(addPos, r.appToken, r.task.taskId,
                     r.info.screenOrientation, r.fullscreen);
+            ActivityOptions.abort(options);
         }
         if (VALIDATE_TOKENS) {
             validateAppTokensLocked();
@@ -2344,6 +2346,7 @@
             // Transfer the result target from the source activity to the new
             // one being started, including any failures.
             if (requestCode >= 0) {
+                ActivityOptions.abort(options);
                 return ActivityManager.START_FORWARD_AND_REQUEST_CONFLICT;
             }
             resultRecord = sourceRecord.resultTo;
@@ -2375,6 +2378,7 @@
                     Activity.RESULT_CANCELED, null);
             }
             mDismissKeyguardOnNextActivity = false;
+            ActivityOptions.abort(options);
             return err;
         }
 
@@ -2425,6 +2429,7 @@
                     // We pretend to the caller that it was really started, but
                     // they will just get a cancel result.
                     mDismissKeyguardOnNextActivity = false;
+                    ActivityOptions.abort(options);
                     return ActivityManager.START_SUCCESS;
                 }
             }
@@ -2447,6 +2452,7 @@
                     pal.startFlags = startFlags;
                     mService.mPendingActivityLaunches.add(pal);
                     mDismissKeyguardOnNextActivity = false;
+                    ActivityOptions.abort(options);
                     return ActivityManager.START_SWITCHES_CANCELED;
                 }
             }
@@ -2601,8 +2607,7 @@
                             // We really do want to push this one into the
                             // user's face, right now.
                             moveHomeToFrontFromLaunchLocked(launchFlags);
-                            r.updateOptionsLocked(options);
-                            moveTaskToFrontLocked(taskTop.task, r);
+                            moveTaskToFrontLocked(taskTop.task, r, options);
                         }
                     }
                     // If the caller has requested that the target task be
@@ -2618,6 +2623,7 @@
                         if (doResume) {
                             resumeTopActivityLocked(null);
                         }
+                        ActivityOptions.abort(options);
                         return ActivityManager.START_RETURN_INTENT_TO_CALLER;
                     }
                     if ((launchFlags &
@@ -2705,6 +2711,7 @@
                         if (doResume) {
                             resumeTopActivityLocked(null);
                         }
+                        ActivityOptions.abort(options);
                         return ActivityManager.START_TASK_TO_FRONT;
                     }
                 }
@@ -2734,6 +2741,7 @@
                             if (doResume) {
                                 resumeTopActivityLocked(null);
                             }
+                            ActivityOptions.abort(options);
                             if ((startFlags&ActivityManager.START_FLAG_ONLY_IF_NEEDED) != 0) {
                                 // We don't need to start a new activity, and
                                 // the client said not to do anything if that
@@ -2753,6 +2761,7 @@
                         r.resultTo, r.resultWho, r.requestCode,
                     Activity.RESULT_CANCELED, null);
             }
+            ActivityOptions.abort(options);
             return ActivityManager.START_CLASS_NOT_FOUND;
         }
 
@@ -2794,6 +2803,7 @@
                     if (doResume) {
                         resumeTopActivityLocked(null);
                     }
+                    ActivityOptions.abort(options);
                     return ActivityManager.START_DELIVERED_TO_TOP;
                 }
             } else if (!addingToTask &&
@@ -2948,6 +2958,7 @@
                                 Slog.w(TAG, "Unable to find app for caller " + caller
                                       + " (pid=" + realCallingPid + ") when starting: "
                                       + intent.toString());
+                                ActivityOptions.abort(options);
                                 return ActivityManager.START_PERMISSION_DENIED;
                             }
                         }
@@ -3480,6 +3491,15 @@
      */
     final boolean finishActivityLocked(ActivityRecord r, int index,
             int resultCode, Intent resultData, String reason) {
+        return finishActivityLocked(r, index, resultCode, resultData, reason, false);
+    }
+
+    /**
+     * @return Returns true if this activity has been removed from the history
+     * list, or false if it is still in the list and will be removed later.
+     */
+    final boolean finishActivityLocked(ActivityRecord r, int index,
+            int resultCode, Intent resultData, String reason, boolean immediate) {
         if (r.finishing) {
             Slog.w(TAG, "Duplicate finish request for " + r);
             return false;
@@ -3521,7 +3541,10 @@
             mService.mCancelledThumbnails.add(r);
         }
 
-        if (mResumedActivity == r) {
+        if (immediate) {
+            return finishCurrentActivityLocked(r, index,
+                    FINISH_IMMEDIATELY) == null;
+        } else if (mResumedActivity == r) {
             boolean endTask = index <= 0
                     || (mHistory.get(index-1)).task != r.task;
             if (DEBUG_TRANSITION) Slog.v(TAG,
@@ -3887,12 +3910,12 @@
             }
         }
         if (homeTask != null) {
-            moveTaskToFrontLocked(homeTask, null);
+            moveTaskToFrontLocked(homeTask, null, null);
         }
     }
 
 
-    final void moveTaskToFrontLocked(TaskRecord tr, ActivityRecord reason) {
+    final void moveTaskToFrontLocked(TaskRecord tr, ActivityRecord reason, Bundle options) {
         if (DEBUG_SWITCH) Slog.v(TAG, "moveTaskToFront: " + tr);
 
         final int task = tr.taskId;
@@ -3900,6 +3923,7 @@
 
         if (top < 0 || (mHistory.get(top)).task.taskId == task) {
             // nothing to do!
+            ActivityOptions.abort(options);
             return;
         }
 
@@ -3941,7 +3965,16 @@
             if (r != null) {
                 mNoAnimActivities.add(r);
             }
+            ActivityOptions.abort(options);
         } else {
+            if (options != null) {
+                ActivityRecord r = topRunningActivityLocked(null);
+                if (r != null && r.state != ActivityState.RESUMED) {
+                    r.updateOptionsLocked(options);
+                } else {
+                    ActivityOptions.abort(options);
+                }
+            }
             mService.mWindowManager.prepareAppTransition(
                     WindowManagerPolicy.TRANSIT_TASK_TO_FRONT, false);
         }
diff --git a/services/java/com/android/server/wm/AppWindowToken.java b/services/java/com/android/server/wm/AppWindowToken.java
index 0e110be..c29ef3f 100644
--- a/services/java/com/android/server/wm/AppWindowToken.java
+++ b/services/java/com/android/server/wm/AppWindowToken.java
@@ -21,10 +21,12 @@
 import com.android.server.wm.WindowManagerService.H;
 
 import android.content.pm.ActivityInfo;
+import android.graphics.Matrix;
 import android.os.Message;
 import android.os.RemoteException;
 import android.util.Slog;
 import android.view.IApplicationToken;
+import android.view.Surface;
 import android.view.View;
 import android.view.WindowManager;
 import android.view.WindowManagerPolicy;
@@ -90,6 +92,7 @@
 
     boolean animating;
     Animation animation;
+    boolean animInitialized;
     boolean hasTransformation;
     final Transformation transformation = new Transformation();
 
@@ -105,6 +108,15 @@
     boolean startingMoved;
     boolean firstWindowDrawn;
 
+    // Special surface for thumbnail animation.
+    Surface thumbnail;
+    int thumbnailTransactionSeq;
+    int thumbnailX;
+    int thumbnailY;
+    int thumbnailLayer;
+    Animation thumbnailAnimation;
+    final Transformation thumbnailTransformation = new Transformation();
+
     // Input application handle used by the input dispatcher.
     final InputApplicationHandle mInputApplicationHandle;
 
@@ -116,11 +128,12 @@
         mInputApplicationHandle = new InputApplicationHandle(this);
     }
 
-    public void setAnimation(Animation anim) {
+    public void setAnimation(Animation anim, boolean initialized) {
         if (WindowManagerService.localLOGV) Slog.v(
             WindowManagerService.TAG, "Setting animation in " + this + ": " + anim);
         animation = anim;
         animating = false;
+        animInitialized = initialized;
         anim.restrictDuration(WindowManagerService.MAX_ANIMATION_DURATION);
         anim.scaleCurrentDuration(service.mTransitionAnimationScale);
         int zorder = anim.getZAdjustment();
@@ -146,6 +159,7 @@
             if (WindowManagerService.localLOGV) Slog.v(
                 WindowManagerService.TAG, "Setting dummy animation in " + this);
             animation = WindowManagerService.sDummyAnimation;
+            animInitialized = false;
         }
     }
 
@@ -153,15 +167,28 @@
         if (animation != null) {
             animation = null;
             animating = true;
+            animInitialized = false;
+        }
+        clearThumbnail();
+    }
+
+    public void clearThumbnail() {
+        if (thumbnail != null) {
+            thumbnail.destroy();
+            thumbnail = null;
         }
     }
 
     void updateLayers() {
         final int N = allAppWindows.size();
         final int adj = animLayerAdjustment;
+        thumbnailLayer = -1;
         for (int i=0; i<N; i++) {
             WindowState w = allAppWindows.get(i);
             w.mAnimLayer = w.mLayer + adj;
+            if (w.mAnimLayer > thumbnailLayer) {
+                thumbnailLayer = w.mAnimLayer;
+            }
             if (WindowManagerService.DEBUG_LAYERS) Slog.v(WindowManagerService.TAG, "Updating layer " + w + ": "
                     + w.mAnimLayer);
             if (w == service.mInputMethodTarget && !service.mInputMethodTargetWaitingAnim) {
@@ -203,6 +230,38 @@
         return isAnimating;
     }
 
+    private void stepThumbnailAnimation(long currentTime) {
+        thumbnailTransformation.clear();
+        thumbnailAnimation.getTransformation(currentTime, thumbnailTransformation);
+        thumbnailTransformation.getMatrix().preTranslate(thumbnailX, thumbnailY);
+        final boolean screenAnimation = service.mAnimator.mScreenRotationAnimation != null
+                && service.mAnimator.mScreenRotationAnimation.isAnimating();
+        if (screenAnimation) {
+            thumbnailTransformation.postCompose(
+                    service.mAnimator.mScreenRotationAnimation.getEnterTransformation());
+        }
+        // cache often used attributes locally
+        final float tmpFloats[] = service.mTmpFloats;
+        thumbnailTransformation.getMatrix().getValues(tmpFloats);
+        if (WindowManagerService.SHOW_TRANSACTIONS) service.logSurface(thumbnail,
+                "thumbnail", "POS " + tmpFloats[Matrix.MTRANS_X]
+                + ", " + tmpFloats[Matrix.MTRANS_Y], null);
+        thumbnail.setPosition(tmpFloats[Matrix.MTRANS_X], tmpFloats[Matrix.MTRANS_Y]);
+        if (WindowManagerService.SHOW_TRANSACTIONS) service.logSurface(thumbnail,
+                "thumbnail", "alpha=" + thumbnailTransformation.getAlpha()
+                + " layer=" + thumbnailLayer
+                + " matrix=[" + tmpFloats[Matrix.MSCALE_X]
+                + "," + tmpFloats[Matrix.MSKEW_Y]
+                + "][" + tmpFloats[Matrix.MSKEW_X]
+                + "," + tmpFloats[Matrix.MSCALE_Y] + "]", null);
+        thumbnail.setAlpha(thumbnailTransformation.getAlpha());
+        // The thumbnail is layered below the window immediately above this
+        // token's anim layer.
+        thumbnail.setLayer(thumbnailLayer + WindowManagerService.WINDOW_LAYER_MULTIPLIER
+                - WindowManagerService.LAYER_OFFSET_THUMBNAIL);
+        thumbnail.setMatrix(tmpFloats[Matrix.MSCALE_X], tmpFloats[Matrix.MSKEW_Y],
+                tmpFloats[Matrix.MSKEW_X], tmpFloats[Matrix.MSCALE_Y]);
+    }
 
     private boolean stepAnimation(long currentTime) {
         if (animation == null) {
@@ -215,6 +274,7 @@
             ": more=" + more + ", xform=" + transformation);
         if (!more) {
             animation = null;
+            clearThumbnail();
             if (WindowManagerService.DEBUG_ANIM) Slog.v(
                 WindowManagerService.TAG, "Finished animation in " + this +
                 " @ " + currentTime);
@@ -243,12 +303,22 @@
                         " @ " + currentTime + ": dw=" + dw + " dh=" + dh
                         + " scale=" + service.mTransitionAnimationScale
                         + " allDrawn=" + allDrawn + " animating=" + animating);
-                    animation.initialize(dw, dh, dw, dh);
+                    if (!animInitialized) {
+                        animation.initialize(dw, dh, dw, dh);
+                    }
                     animation.setStartTime(currentTime);
                     animating = true;
+                    if (thumbnail != null) {
+                        thumbnail.show();
+                        thumbnailAnimation.setStartTime(currentTime);
+                    }
                 }
                 if (stepAnimation(currentTime)) {
-                    // we're done!
+                    // animation isn't over, step any thumbnail and that's
+                    // it for now.
+                    if (thumbnail != null) {
+                        stepThumbnailAnimation(currentTime);
+                    }
                     return true;
                 }
             }
@@ -440,6 +510,15 @@
                     pw.print(" startingDisplayed="); pw.print(startingDisplayed);
                     pw.print(" startingMoved"); pw.println(startingMoved);
         }
+        if (thumbnail != null) {
+            pw.print(prefix); pw.print("thumbnail="); pw.print(thumbnail);
+                    pw.print(" x="); pw.print(thumbnailX);
+                    pw.print(" y="); pw.print(thumbnailY);
+                    pw.print(" layer="); pw.println(thumbnailLayer);
+            pw.print(prefix); pw.print("thumbnailAnimation="); pw.println(thumbnailAnimation);
+            pw.print(prefix); pw.print("thumbnailTransformation=");
+                    pw.println(thumbnailTransformation.toShortString());
+        }
     }
 
     @Override
diff --git a/services/java/com/android/server/wm/WindowAnimator.java b/services/java/com/android/server/wm/WindowAnimator.java
index b3dbee1..058e09e 100644
--- a/services/java/com/android/server/wm/WindowAnimator.java
+++ b/services/java/com/android/server/wm/WindowAnimator.java
@@ -273,6 +273,15 @@
                 w.performShowLocked();
                 mPendingLayoutChanges |= WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM;
             }
+            if (atoken != null && atoken.thumbnail != null) {
+                if (atoken.thumbnailTransactionSeq != mTransactionSequence) {
+                    atoken.thumbnailTransactionSeq = mTransactionSequence;
+                    atoken.thumbnailLayer = 0;
+                }
+                if (atoken.thumbnailLayer < w.mAnimLayer) {
+                    atoken.thumbnailLayer = w.mAnimLayer;
+                }
+            }
         } // end forall windows
     }
 
diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java
index 26367d2..7f74b5f 100644
--- a/services/java/com/android/server/wm/WindowManagerService.java
+++ b/services/java/com/android/server/wm/WindowManagerService.java
@@ -117,8 +117,12 @@
 import android.view.WindowManagerPolicy;
 import android.view.WindowManager.LayoutParams;
 import android.view.WindowManagerPolicy.FakeWindow;
+import android.view.animation.AlphaAnimation;
 import android.view.animation.Animation;
+import android.view.animation.AnimationSet;
 import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+import android.view.animation.ScaleAnimation;
 import android.view.animation.Transformation;
 
 import java.io.BufferedWriter;
@@ -194,6 +198,18 @@
     static final int LAYER_OFFSET_DIM = 1;
 
     /**
+     * Blur surface layer is immediately below dim layer.
+     */
+    static final int LAYER_OFFSET_BLUR = 2;
+
+    /**
+     * Animation thumbnail is as far as possible below the window above
+     * the thumbnail (or in other words as far as possible above the window
+     * below it).
+     */
+    static final int LAYER_OFFSET_THUMBNAIL = WINDOW_LAYER_MULTIPLIER-1;
+
+    /**
      * Layer at which to put the rotation freeze snapshot.
      */
     static final int FREEZE_LAYER = (TYPE_LAYER_MULTIPLIER * 200) + 1;
@@ -479,8 +495,12 @@
     // made visible or hidden at the next transition.
     int mNextAppTransition = WindowManagerPolicy.TRANSIT_UNSET;
     String mNextAppTransitionPackage;
+    Bitmap mNextAppTransitionThumbnail;
+    IRemoteCallback mNextAppTransitionCallback;
     int mNextAppTransitionEnter;
     int mNextAppTransitionExit;
+    int mNextAppTransitionStartX;
+    int mNextAppTransitionStartY;
     boolean mAppTransitionReady = false;
     boolean mAppTransitionRunning = false;
     boolean mAppTransitionTimeout = false;
@@ -2441,6 +2461,15 @@
             Slog.i(TAG, str);
         }
     }
+
+    static void logSurface(Surface s, String title, String msg, RuntimeException where) {
+        String str = "  SURFACE " + s + ": " + msg + " / " + title;
+        if (where != null) {
+            Slog.i(TAG, str, where);
+        } else {
+            Slog.i(TAG, str);
+        }
+    }
     
     void setTransparentRegionWindow(Session session, IWindow client, Region region) {
         long origId = Binder.clearCallingIdentity();
@@ -3081,6 +3110,63 @@
         return null;
     }
 
+    private Animation createThumbnailAnimationLocked(int transit,
+            boolean enter, boolean thumb) {
+        Animation a;
+        final float thumbWidth = mNextAppTransitionThumbnail.getWidth();
+        final float thumbHeight = mNextAppTransitionThumbnail.getHeight();
+        // Pick the desired duration.  If this is an inter-activity transition,
+        // it  is the standard duration for that.  Otherwise we use the longer
+        // task transition duration.
+        int duration;
+        switch (transit) {
+            case WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN:
+            case WindowManagerPolicy.TRANSIT_ACTIVITY_CLOSE:
+                duration = mContext.getResources().getInteger(
+                        com.android.internal.R.integer.config_shortAnimTime);
+                break;
+            default:
+                duration = 500;
+                break;
+            
+        }
+        if (thumb) {
+            // Animation for zooming thumbnail from its initial size to
+            // filling the screen.
+            Animation scale = new ScaleAnimation(
+                    1, mAppDisplayWidth/thumbWidth,
+                    1, mAppDisplayHeight/thumbHeight,
+                    mNextAppTransitionStartX + thumbWidth/2,
+                    mNextAppTransitionStartY + thumbHeight/2);
+            AnimationSet set = new AnimationSet(true);
+            Animation alpha = new AlphaAnimation(1, 0);
+            scale.setDuration(duration);
+            set.addAnimation(scale);
+            alpha.setDuration(duration);
+            set.addAnimation(alpha);
+            a = set;
+        } else if (enter) {
+            // Entering app zooms out from the center of the thumbnail.
+            a = new ScaleAnimation(
+                    thumbWidth/mAppDisplayWidth, 1,
+                    thumbHeight/mAppDisplayHeight, 1,
+                    mNextAppTransitionStartX + thumbWidth/2,
+                    mNextAppTransitionStartY + thumbHeight/2);
+            a.setDuration(duration);
+        } else {
+            // Exiting app just holds in place.
+            a = new AlphaAnimation(1, 1);
+            a.setDuration(duration);
+        }
+        a.setFillAfter(true);
+        final Interpolator interpolator = AnimationUtils.loadInterpolator(mContext,
+                com.android.internal.R.interpolator.decelerate_quint);
+        a.setInterpolator(interpolator);
+        a.initialize(mAppDisplayWidth, mAppDisplayHeight,
+                mAppDisplayWidth, mAppDisplayHeight);
+        return a;
+    }
+
     private boolean applyAnimationLocked(AppWindowToken wtoken,
             WindowManager.LayoutParams lp, int transit, boolean enter) {
         // Only apply an animation if the display isn't frozen.  If it is
@@ -3089,7 +3175,11 @@
         // is running.
         if (okToDisplay()) {
             Animation a;
-            if (mNextAppTransitionPackage != null) {
+            boolean initialized = false;
+            if (mNextAppTransitionThumbnail != null) {
+                a = createThumbnailAnimationLocked(transit, enter, false);
+                initialized = true;
+            } else if (mNextAppTransitionPackage != null) {
                 a = loadAnimation(mNextAppTransitionPackage, enter ?
                         mNextAppTransitionEnter : mNextAppTransitionExit);
             } else {
@@ -3161,7 +3251,7 @@
                     }
                     Slog.v(TAG, "Loaded animation " + a + " for " + wtoken, e);
                 }
-                wtoken.setAnimation(a);
+                wtoken.setAnimation(a, initialized);
             }
         } else {
             wtoken.clearAnimation();
@@ -3689,11 +3779,23 @@
             int enterAnim, int exitAnim) {
         if (mNextAppTransition != WindowManagerPolicy.TRANSIT_UNSET) {
             mNextAppTransitionPackage = packageName;
+            mNextAppTransitionThumbnail = null;
             mNextAppTransitionEnter = enterAnim;
             mNextAppTransitionExit = exitAnim;
         }
     }
 
+    public void overridePendingAppTransitionThumb(Bitmap srcThumb, int startX,
+            int startY, IRemoteCallback startedCallback) {
+        if (mNextAppTransition != WindowManagerPolicy.TRANSIT_UNSET) {
+            mNextAppTransitionPackage = null;
+            mNextAppTransitionThumbnail = srcThumb;
+            mNextAppTransitionStartX = startX;
+            mNextAppTransitionStartY = startY;
+            mNextAppTransitionCallback = startedCallback;
+        }
+    }
+
     public void executeAppTransition() {
         if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
                 "executeAppTransition()")) {
@@ -3837,6 +3939,19 @@
                         mH.sendMessageAtFrontOfQueue(m);
                         return;
                     }
+                    if (ttoken.thumbnail != null) {
+                        // The old token is animating with a thumbnail, transfer
+                        // that to the new token.
+                        if (wtoken.thumbnail != null) {
+                            wtoken.thumbnail.destroy();
+                        }
+                        wtoken.thumbnail = ttoken.thumbnail;
+                        wtoken.thumbnailX = ttoken.thumbnailX;
+                        wtoken.thumbnailY = ttoken.thumbnailY;
+                        wtoken.thumbnailLayer = ttoken.thumbnailLayer;
+                        wtoken.thumbnailAnimation = ttoken.thumbnailAnimation;
+                        ttoken.thumbnail = null;
+                    }
                 }
             }
 
@@ -4232,6 +4347,7 @@
                     // Make sure there is no animation running on this token,
                     // so any windows associated with it will be removed as
                     // soon as their animations are complete
+                    wtoken.clearAnimation();
                     wtoken.animation = null;
                     wtoken.animating = false;
                 }
@@ -7784,11 +7900,15 @@
                 animLp = null;
             }
 
+            AppWindowToken topOpeningApp = null;
+            int topOpeningLayer = 0;
+
             NN = mOpeningApps.size();
             for (i=0; i<NN; i++) {
                 AppWindowToken wtoken = mOpeningApps.get(i);
                 if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
                         "Now opening app" + wtoken);
+                wtoken.clearThumbnail();
                 wtoken.reportedVisible = false;
                 wtoken.inPendingTransaction = false;
                 wtoken.animation = null;
@@ -7797,12 +7917,26 @@
                 wtoken.updateReportedVisibilityLocked();
                 wtoken.waitingToShow = false;
                 mAnimator.mAnimating |= wtoken.showAllWindowsLocked();
+                if (animLp != null) {
+                    int layer = -1;
+                    for (int j=0; j<wtoken.windows.size(); j++) {
+                        WindowState win = wtoken.windows.get(j);
+                        if (win.mAnimLayer > layer) {
+                            layer = win.mAnimLayer;
+                        }
+                    }
+                    if (topOpeningApp == null || layer > topOpeningLayer) {
+                        topOpeningApp = wtoken;
+                        topOpeningLayer = layer;
+                    }
+                }
             }
             NN = mClosingApps.size();
             for (i=0; i<NN; i++) {
                 AppWindowToken wtoken = mClosingApps.get(i);
                 if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
                         "Now closing app" + wtoken);
+                wtoken.clearThumbnail();
                 wtoken.inPendingTransaction = false;
                 wtoken.animation = null;
                 setTokenVisibilityLocked(wtoken, animLp, false,
@@ -7815,7 +7949,47 @@
                 wtoken.allDrawn = true;
             }
 
+            if (mNextAppTransitionThumbnail != null && topOpeningApp != null
+                    && topOpeningApp.animation != null) {
+                // 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, mNextAppTransitionThumbnail.getWidth(),
+                        mNextAppTransitionThumbnail.getHeight());
+                try {
+                    Surface surface = new Surface(mFxSession, Process.myPid(),
+                            "thumbnail anim", 0, dirty.width(), dirty.height(),
+                            PixelFormat.TRANSLUCENT, Surface.HIDDEN);
+                    topOpeningApp.thumbnail = surface;
+                    if (SHOW_TRANSACTIONS) Slog.i(TAG, "  THUMBNAIL "
+                            + surface + ": CREATE");
+                    Surface drawSurface = new Surface();
+                    drawSurface.copyFrom(surface);
+                    Canvas c = drawSurface.lockCanvas(dirty);
+                    c.drawBitmap(mNextAppTransitionThumbnail, 0, 0, null);
+                    drawSurface.unlockCanvasAndPost(c);
+                    drawSurface.release();
+                    topOpeningApp.thumbnailLayer = topOpeningLayer;
+                    Animation anim = createThumbnailAnimationLocked(transit, true, true);
+                    topOpeningApp.thumbnailAnimation = anim;
+                    anim.restrictDuration(MAX_ANIMATION_DURATION);
+                    anim.scaleCurrentDuration(mTransitionAnimationScale);
+                    topOpeningApp.thumbnailX = mNextAppTransitionStartX;
+                    topOpeningApp.thumbnailY = mNextAppTransitionStartY;
+                } catch (Surface.OutOfResourcesException e) {
+                    Slog.e(TAG, "Can't allocate thumbnail surface w=" + dirty.width()
+                            + " h=" + dirty.height(), e);
+                    topOpeningApp.clearThumbnail();
+                }
+            }
+
             mNextAppTransitionPackage = null;
+            mNextAppTransitionThumbnail = null;
+            if (mNextAppTransitionCallback != null) {
+                try {
+                    mNextAppTransitionCallback.sendResult(null);
+                } catch (RemoteException e) {
+                }
+            }
 
             mOpeningApps.clear();
             mClosingApps.clear();
@@ -8400,6 +8574,7 @@
                 // Make sure there is no animation running on this token,
                 // so any windows associated with it will be removed as
                 // soon as their animations are complete
+                token.clearAnimation();
                 token.animation = null;
                 token.animating = false;
                 if (DEBUG_ADD_REMOVE || DEBUG_TOKEN_MOVEMENT) Slog.v(TAG,
@@ -8839,6 +9014,7 @@
         if (mNextAppTransition != WindowManagerPolicy.TRANSIT_UNSET) {
             mNextAppTransition = WindowManagerPolicy.TRANSIT_UNSET;
             mNextAppTransitionPackage = null;
+            mNextAppTransitionThumbnail = null;
             mAppTransitionReady = true;
         }
 
@@ -9399,6 +9575,12 @@
                     pw.print(" mNextAppTransitionExit=0x");
                     pw.print(Integer.toHexString(mNextAppTransitionExit));
             }
+            if (mNextAppTransitionThumbnail != null) {
+                pw.print("  mNextAppTransitionThumbnail=");
+                    pw.print(mNextAppTransitionThumbnail);
+                    pw.print(" mNextAppTransitionStartX="); pw.print(mNextAppTransitionStartX);
+                    pw.print(" mNextAppTransitionStartY="); pw.println(mNextAppTransitionStartY);
+            }
             pw.print("  mStartingIconInTransition="); pw.print(mStartingIconInTransition);
                     pw.print(", mSkipAppTransitionAnimation="); pw.println(mSkipAppTransitionAnimation);
         }
diff --git a/tests/permission/src/com/android/framework/permission/tests/ActivityManagerPermissionTests.java b/tests/permission/src/com/android/framework/permission/tests/ActivityManagerPermissionTests.java
index 4cacbc4..cceed16 100644
--- a/tests/permission/src/com/android/framework/permission/tests/ActivityManagerPermissionTests.java
+++ b/tests/permission/src/com/android/framework/permission/tests/ActivityManagerPermissionTests.java
@@ -39,7 +39,7 @@
     @SmallTest
 	public void testREORDER_TASKS() {
         try {
-            mAm.moveTaskToFront(0, 0);
+            mAm.moveTaskToFront(0, 0, null);
             fail("IActivityManager.moveTaskToFront did not throw SecurityException as"
                     + " expected");
         } catch (SecurityException e) {
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowManager.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowManager.java
index bef2c95..8b1d41a 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowManager.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowManager.java
@@ -340,6 +340,12 @@
     }
 
     @Override
+    public void overridePendingAppTransitionThumb(Bitmap srcThumb, int startX, int startY,
+            IRemoteCallback startedCallback) throws RemoteException {
+        // TODO Auto-generated method stub
+    }
+
+    @Override
     public void pauseKeyDispatching(IBinder arg0) throws RemoteException {
         // TODO Auto-generated method stub