Adding bounce animation for affiliated tasks. (Bug 16656169)

Change-Id: I39e4a57c4e6b707d15513dacde2d40c23bb05058
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index bc7114b..c3028b7 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -2279,6 +2279,20 @@
             return true;
         }
 
+        case START_IN_PLACE_ANIMATION_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            final Bundle bundle;
+            if (data.readInt() == 0) {
+                bundle = null;
+            } else {
+                bundle = data.readBundle();
+            }
+            final ActivityOptions options = bundle == null ? null : new ActivityOptions(bundle);
+            startInPlaceAnimationOnFrontMostApplication(options);
+            reply.writeNoException();
+            return true;
+        }
+
         case REQUEST_VISIBLE_BEHIND_TRANSACTION: {
             data.enforceInterface(IActivityManager.descriptor);
             IBinder token = data.readStrongBinder();
@@ -5298,6 +5312,24 @@
     }
 
     @Override
+    public void startInPlaceAnimationOnFrontMostApplication(ActivityOptions options)
+            throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        if (options == null) {
+            data.writeInt(0);
+        } else {
+            data.writeInt(1);
+            data.writeBundle(options.toBundle());
+        }
+        mRemote.transact(START_IN_PLACE_ANIMATION_TRANSACTION, data, reply, IBinder.FLAG_ONEWAY);
+        reply.readException();
+        data.recycle();
+        reply.recycle();
+    }
+
+    @Override
     public boolean requestVisibleBehind(IBinder token, boolean visible) throws RemoteException {
         Parcel data = Parcel.obtain();
         Parcel reply = Parcel.obtain();
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index cd6a4f5..3d390bf 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -63,6 +63,12 @@
     public static final String KEY_ANIM_EXIT_RES_ID = "android:animExitRes";
 
     /**
+     * Custom in-place animation resource ID.
+     * @hide
+     */
+    public static final String KEY_ANIM_IN_PLACE_RES_ID = "android:animInPlaceRes";
+
+    /**
      * Bitmap for thumbnail animation.
      * @hide
      */
@@ -132,11 +138,14 @@
     public static final int ANIM_THUMBNAIL_ASPECT_SCALE_UP = 8;
     /** @hide */
     public static final int ANIM_THUMBNAIL_ASPECT_SCALE_DOWN = 9;
+    /** @hide */
+    public static final int ANIM_CUSTOM_IN_PLACE = 10;
 
     private String mPackageName;
     private int mAnimationType = ANIM_NONE;
     private int mCustomEnterResId;
     private int mCustomExitResId;
+    private int mCustomInPlaceResId;
     private Bitmap mThumbnail;
     private int mStartX;
     private int mStartY;
@@ -198,6 +207,30 @@
         return opts;
     }
 
+    /**
+     * Creates an ActivityOptions specifying a custom animation to run in place on an existing
+     * activity.
+     *
+     * @param context Who is defining this.  This is the application that the
+     * animation resources will be loaded from.
+     * @param animId A resource ID of the animation resource to use for
+     * the incoming activity.
+     * @return Returns a new ActivityOptions object that you can use to
+     * supply these options as the options Bundle when running an in-place animation.
+     * @hide
+     */
+    public static ActivityOptions makeCustomInPlaceAnimation(Context context, int animId) {
+        if (animId == 0) {
+            throw new RuntimeException("You must specify a valid animation.");
+        }
+
+        ActivityOptions opts = new ActivityOptions();
+        opts.mPackageName = context.getPackageName();
+        opts.mAnimationType = ANIM_CUSTOM_IN_PLACE;
+        opts.mCustomInPlaceResId = animId;
+        return opts;
+    }
+
     private void setOnAnimationStartedListener(Handler handler,
             OnAnimationStartedListener listener) {
         if (listener != null) {
@@ -540,6 +573,10 @@
                         opts.getBinder(KEY_ANIM_START_LISTENER));
                 break;
 
+            case ANIM_CUSTOM_IN_PLACE:
+                mCustomInPlaceResId = opts.getInt(KEY_ANIM_IN_PLACE_RES_ID, 0);
+                break;
+
             case ANIM_SCALE_UP:
                 mStartX = opts.getInt(KEY_ANIM_START_X, 0);
                 mStartY = opts.getInt(KEY_ANIM_START_Y, 0);
@@ -592,6 +629,11 @@
     }
 
     /** @hide */
+    public int getCustomInPlaceResId() {
+        return mCustomInPlaceResId;
+    }
+
+    /** @hide */
     public Bitmap getThumbnail() {
         return mThumbnail;
     }
@@ -689,6 +731,9 @@
                 }
                 mAnimationStartedListener = otherOptions.mAnimationStartedListener;
                 break;
+            case ANIM_CUSTOM_IN_PLACE:
+                mCustomInPlaceResId = otherOptions.mCustomInPlaceResId;
+                break;
             case ANIM_SCALE_UP:
                 mStartX = otherOptions.mStartX;
                 mStartY = otherOptions.mStartY;
@@ -756,6 +801,9 @@
                 b.putBinder(KEY_ANIM_START_LISTENER, mAnimationStartedListener
                         != null ? mAnimationStartedListener.asBinder() : null);
                 break;
+            case ANIM_CUSTOM_IN_PLACE:
+                b.putInt(KEY_ANIM_IN_PLACE_RES_ID, mCustomInPlaceResId);
+                break;
             case ANIM_SCALE_UP:
                 b.putInt(KEY_ANIM_START_X, mStartX);
                 b.putInt(KEY_ANIM_START_Y, mStartY);
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index efcb197..6433f3f 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -456,6 +456,9 @@
             throws RemoteException;
     public Bitmap getTaskDescriptionIcon(String filename) throws RemoteException;
 
+    public void startInPlaceAnimationOnFrontMostApplication(ActivityOptions opts)
+            throws RemoteException;
+
     public boolean requestVisibleBehind(IBinder token, boolean visible) throws RemoteException;
     public boolean isBackgroundVisibleBehind(IBinder token) throws RemoteException;
     public void backgroundResourcesReleased(IBinder token) throws RemoteException;
@@ -781,4 +784,5 @@
     int BOOT_ANIMATION_COMPLETE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+237;
     int GET_TASK_DESCRIPTION_ICON_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+238;
     int LAUNCH_ASSIST_INTENT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+239;
+    int START_IN_PLACE_ANIMATION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+240;
 }
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 6aa86c7..7b20e72 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -96,6 +96,7 @@
     void overridePendingAppTransitionAspectScaledThumb(in Bitmap srcThumb, int startX,
             int startY, int targetWidth, int targetHeight, IRemoteCallback startedCallback,
             boolean scaleUp);
+    void overridePendingAppTransitionInPlace(String packageName, int anim);
     void executeAppTransition();
     void setAppStartingWindow(IBinder token, String pkg, int theme,
             in CompatibilityInfo compatInfo, CharSequence nonLocalizedLabel, int labelRes,
diff --git a/core/res/res/anim/launch_task_behind_source.xml b/core/res/res/anim/launch_task_behind_source.xml
index cd3e30a..a715705 100644
--- a/core/res/res/anim/launch_task_behind_source.xml
+++ b/core/res/res/anim/launch_task_behind_source.xml
@@ -22,38 +22,27 @@
 
     <alpha android:fromAlpha="1.0" android:toAlpha="0.6"
         android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
-        android:interpolator="@interpolator/accelerate_cubic"
-        android:duration="133"/>
+        android:interpolator="@interpolator/linear_out_slow_in"
+        android:duration="417"/>
 
-    <translate android:fromYDelta="0" android:toYDelta="10%"
-        android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
-        android:interpolator="@interpolator/accelerate_cubic"
-        android:duration="350"/>
-
-    <scale android:fromXScale="1.0" android:toXScale="0.9"
-        android:fromYScale="1.0" android:toYScale="0.9"
+    <scale android:fromXScale="1.0" android:toXScale="0.918"
+        android:fromYScale="1.0" android:toYScale="0.918"
         android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
         android:pivotX="50%p" android:pivotY="50%p"
-        android:interpolator="@interpolator/fast_out_slow_in"
-        android:duration="350" />
+        android:interpolator="@interpolator/launch_task_behind_source_scale_1"
+        android:duration="417" />
 
     <alpha android:fromAlpha="1.0" android:toAlpha="1.6666666666"
         android:fillEnabled="true" android:fillBefore="false" android:fillAfter="true"
-        android:interpolator="@interpolator/decelerate_cubic"
-        android:startOffset="433"
-        android:duration="133"/>
+        android:interpolator="@interpolator/linear"
+        android:startOffset="500"
+        android:duration="167"/>
 
-    <translate android:fromYDelta="0%" android:toYDelta="-8.8888888888%"
-        android:fillEnabled="true" android:fillBefore="false" android:fillAfter="true"
-        android:interpolator="@interpolator/decelerate_cubic"
-        android:startOffset="433"
-        android:duration="350"/>
-
-    <scale android:fromXScale="1.0" android:toXScale="1.1111111111"
-        android:fromYScale="1.0" android:toYScale="1.1111111111"
+    <scale android:fromXScale="1.0" android:toXScale="1.08932461873638"
+        android:fromYScale="1.0" android:toYScale="1.08932461873638"
         android:fillEnabled="true" android:fillBefore="false" android:fillAfter="true"
         android:pivotX="50%p" android:pivotY="50%p"
-        android:interpolator="@interpolator/decelerate_cubic"
-        android:startOffset="433"
-        android:duration="350" />
+        android:interpolator="@interpolator/launch_task_behind_source_scale_2"
+        android:startOffset="500"
+        android:duration="317" />
 </set>
\ No newline at end of file
diff --git a/core/res/res/anim/launch_task_behind_target.xml b/core/res/res/anim/launch_task_behind_target.xml
index 358511f..805918b 100644
--- a/core/res/res/anim/launch_task_behind_target.xml
+++ b/core/res/res/anim/launch_task_behind_target.xml
@@ -20,15 +20,15 @@
 <set xmlns:android="http://schemas.android.com/apk/res/android"
      android:background="#ff000000" android:shareInterpolator="false" android:zAdjustment="top">
 
-    <translate android:fromYDelta="110%" android:toYDelta="66%"
+    <translate android:fromYDelta="110%" android:toYDelta="70%"
                android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
-               android:interpolator="@interpolator/decelerate_quint"
+               android:interpolator="@interpolator/launch_task_behind_target_ydelta"
                android:startOffset="50"
-               android:duration="300" />
+               android:duration="333" />
 
-    <translate android:fromYDelta="0%" android:toYDelta="167%"
+    <translate android:fromYDelta="0%" android:toYDelta="50%"
                android:fillEnabled="true" android:fillBefore="false" android:fillAfter="true"
-               android:interpolator="@interpolator/accelerate_quint"
-               android:startOffset="433"
-               android:duration="300" />
-</set>
\ No newline at end of file
+               android:interpolator="@interpolator/fast_out_linear_in"
+               android:startOffset="467"
+               android:duration="317" />
+</set>
diff --git a/core/res/res/interpolator/launch_task_behind_source_scale_1.xml b/core/res/res/interpolator/launch_task_behind_source_scale_1.xml
new file mode 100644
index 0000000..7e295d8
--- /dev/null
+++ b/core/res/res/interpolator/launch_task_behind_source_scale_1.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2014, 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.
+*/
+-->
+
+<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
+    android:pathData="M 0,0 c 0.541795,0 0.2,1 1,1" />
diff --git a/core/res/res/interpolator/launch_task_behind_source_scale_2.xml b/core/res/res/interpolator/launch_task_behind_source_scale_2.xml
new file mode 100644
index 0000000..1601fd0
--- /dev/null
+++ b/core/res/res/interpolator/launch_task_behind_source_scale_2.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2014, 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.
+*/
+-->
+
+<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
+    android:pathData="M 0,0 c 0.220434,0 0.833333,1 1,1" />
diff --git a/core/res/res/interpolator/launch_task_behind_target_ydelta.xml b/core/res/res/interpolator/launch_task_behind_target_ydelta.xml
new file mode 100644
index 0000000..96b539f
--- /dev/null
+++ b/core/res/res/interpolator/launch_task_behind_target_ydelta.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2014, 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.
+*/
+-->
+
+<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
+    android:pathData="M 0,0 c 0.3,0 0,1 1,1" />
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 6e881a7..df1550a 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1810,6 +1810,7 @@
   <java-symbol type="anim" name="lock_screen_behind_enter_wallpaper" />
   <java-symbol type="anim" name="lock_screen_behind_enter_fade_in" />
   <java-symbol type="anim" name="lock_screen_wallpaper_exit" />
+  <java-symbol type="anim" name="launch_task_behind_source" />
 
   <java-symbol type="bool" name="config_alwaysUseCdmaRssi" />
   <java-symbol type="dimen" name="status_bar_icon_size" />
diff --git a/packages/SystemUI/res/anim/recents_launch_next_affiliated_task_bounce.xml b/packages/SystemUI/res/anim/recents_launch_next_affiliated_task_bounce.xml
index a571cbc..74f2814 100644
--- a/packages/SystemUI/res/anim/recents_launch_next_affiliated_task_bounce.xml
+++ b/packages/SystemUI/res/anim/recents_launch_next_affiliated_task_bounce.xml
@@ -20,40 +20,30 @@
 <set xmlns:android="http://schemas.android.com/apk/res/android"
      android:background="#ff000000" android:shareInterpolator="false" android:zAdjustment="normal">
 
-    <alpha android:fromAlpha="1.0" android:toAlpha="0.6"
+
+    <translate android:fromYDelta="0" android:toYDelta="2%"
         android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
-        android:interpolator="@android:interpolator/accelerate_cubic"
+        android:interpolator="@android:interpolator/fast_out_slow_in"
         android:duration="133"/>
 
-    <translate android:fromYDelta="0" android:toYDelta="10%"
-        android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
-        android:interpolator="@android:interpolator/accelerate_cubic"
-        android:duration="350"/>
-
-    <scale android:fromXScale="1.0" android:toXScale="0.9"
-        android:fromYScale="1.0" android:toYScale="0.9"
+    <scale android:fromXScale="1.0" android:toXScale="0.98"
+        android:fromYScale="1.0" android:toYScale="0.98"
         android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
         android:pivotX="50%p" android:pivotY="50%p"
         android:interpolator="@android:interpolator/fast_out_slow_in"
-        android:duration="350" />
+        android:duration="133" />
 
-    <alpha android:fromAlpha="1.0" android:toAlpha="1.6666666666"
-        android:fillEnabled="true" android:fillBefore="false" android:fillAfter="true"
-        android:interpolator="@android:interpolator/decelerate_cubic"
-        android:startOffset="350"
-        android:duration="133"/>
+    <translate android:fromYDelta="0" android:toYDelta="-2%"
+        android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
+        android:interpolator="@interpolator/recents_launch_prev_affiliated_task_bounce_ydelta"
+        android:startOffset="133"
+        android:duration="217"/>
 
-    <translate android:fromYDelta="0%" android:toYDelta="-8.8888888888%"
-        android:fillEnabled="true" android:fillBefore="false" android:fillAfter="true"
-        android:interpolator="@android:interpolator/decelerate_cubic"
-        android:startOffset="350"
-        android:duration="350"/>
-
-    <scale android:fromXScale="1.0" android:toXScale="1.1111111111"
-        android:fromYScale="1.0" android:toYScale="1.1111111111"
+    <scale android:fromXScale="1.0" android:toXScale="1.02040816326531"
+        android:fromYScale="1.0" android:toYScale="1.02040816326531"
         android:fillEnabled="true" android:fillBefore="false" android:fillAfter="true"
         android:pivotX="50%p" android:pivotY="50%p"
-        android:interpolator="@android:interpolator/decelerate_cubic"
-        android:startOffset="350"
-        android:duration="350" />
+        android:interpolator="@interpolator/recents_launch_next_affiliated_task_bounce_scale"
+        android:startOffset="133"
+        android:duration="217" />
 </set>
\ No newline at end of file
diff --git a/packages/SystemUI/res/anim/recents_launch_prev_affiliated_task_bounce.xml b/packages/SystemUI/res/anim/recents_launch_prev_affiliated_task_bounce.xml
index 46045ac..b19167d 100644
--- a/packages/SystemUI/res/anim/recents_launch_prev_affiliated_task_bounce.xml
+++ b/packages/SystemUI/res/anim/recents_launch_prev_affiliated_task_bounce.xml
@@ -22,12 +22,12 @@
 
     <translate android:fromYDelta="0%" android:toYDelta="10%"
                android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
-               android:interpolator="@android:interpolator/decelerate_quint"
-               android:duration="300" />
+               android:interpolator="@android:interpolator/fast_out_slow_in"
+               android:duration="133" />
 
-    <translate android:fromYDelta="10%" android:toYDelta="0%"
+    <translate android:fromYDelta="0%" android:toYDelta="-10%"
                android:fillEnabled="true" android:fillBefore="false" android:fillAfter="true"
-               android:interpolator="@android:interpolator/accelerate_quint"
-               android:startOffset="300"
-               android:duration="300" />
+               android:interpolator="@interpolator/recents_launch_prev_affiliated_task_bounce_ydelta"
+               android:startOffset="133"
+               android:duration="217" />
 </set>
\ No newline at end of file
diff --git a/packages/SystemUI/res/interpolator/recents_launch_next_affiliated_task_bounce_scale.xml b/packages/SystemUI/res/interpolator/recents_launch_next_affiliated_task_bounce_scale.xml
new file mode 100644
index 0000000..c4e5d97
--- /dev/null
+++ b/packages/SystemUI/res/interpolator/recents_launch_next_affiliated_task_bounce_scale.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2014, 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.
+*/
+-->
+<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
+    android:pathData="M 0,0 c 0.8,0 0.2,1 1,1" />
diff --git a/packages/SystemUI/res/interpolator/recents_launch_prev_affiliated_task_bounce_ydelta.xml b/packages/SystemUI/res/interpolator/recents_launch_prev_affiliated_task_bounce_ydelta.xml
new file mode 100644
index 0000000..40a08b9
--- /dev/null
+++ b/packages/SystemUI/res/interpolator/recents_launch_prev_affiliated_task_bounce_ydelta.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2014, 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.
+*/
+-->
+<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
+    android:pathData="M 0,0 c 0.6,0 0.2,1 1,1" />
diff --git a/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java b/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java
index 76e8181..4f4c06f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java
@@ -16,13 +16,11 @@
 
 package com.android.systemui.recents;
 
-import android.app.Activity;
 import android.app.ActivityManager;
 import android.app.ActivityOptions;
 import android.appwidget.AppWidgetHost;
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.ActivityNotFoundException;
-import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -31,7 +29,6 @@
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Rect;
-import android.os.Handler;
 import android.os.UserHandle;
 import android.util.Pair;
 import android.view.LayoutInflater;
@@ -199,6 +196,7 @@
         Task toTask = null;
         ActivityOptions launchOpts = null;
         int taskCount = tasks.size();
+        int numAffiliatedTasks = 0;
         for (int i = 0; i < taskCount; i++) {
             Task task = tasks.get(i);
             if (task.key.id == runningTask.id) {
@@ -218,16 +216,23 @@
                 if (toTaskKey != null) {
                     toTask = stack.findTaskWithId(toTaskKey.id);
                 }
+                numAffiliatedTasks = group.getTaskCount();
                 break;
             }
         }
 
         // Return early if there is no next task
         if (toTask == null) {
-            if (showNextTask) {
-                // XXX: Show the next-task bounce animation
-            } else {
-                // XXX: Show the prev-task bounce animation
+            if (numAffiliatedTasks > 1) {
+                if (showNextTask) {
+                    mSystemServicesProxy.startInPlaceAnimationOnFrontMostApplication(
+                            ActivityOptions.makeCustomInPlaceAnimation(mContext,
+                                    R.anim.recents_launch_next_affiliated_task_bounce));
+                } else {
+                    mSystemServicesProxy.startInPlaceAnimationOnFrontMostApplication(
+                            ActivityOptions.makeCustomInPlaceAnimation(mContext,
+                                    R.anim.recents_launch_prev_affiliated_task_bounce));
+                }
             }
             return;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
index b661385..646d701 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -48,12 +48,14 @@
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.util.Log;
 import android.util.Pair;
 import android.view.Display;
 import android.view.DisplayInfo;
+import android.view.IWindowManager;
 import android.view.SurfaceControl;
 import android.view.WindowManager;
 import android.view.accessibility.AccessibilityManager;
@@ -429,6 +431,7 @@
         opts.putInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY,
                 AppWidgetProviderInfo.WIDGET_CATEGORY_SEARCHBOX);
         if (!mAwm.bindAppWidgetIdIfAllowed(searchWidgetId, searchWidgetInfo.provider, opts)) {
+            host.deleteAppWidgetId(searchWidgetId);
             return null;
         }
         return new Pair<Integer, AppWidgetProviderInfo>(searchWidgetId, searchWidgetInfo);
@@ -532,4 +535,15 @@
         }
         return false;
     }
+
+    /** Starts an in-place animation on the front most application windows. */
+    public void startInPlaceAnimationOnFrontMostApplication(ActivityOptions opts) {
+        if (mIam == null) return;
+
+        try {
+            mIam.startInPlaceAnimationOnFrontMostApplication(opts);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 91e2df0..5897924 100755
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -8285,6 +8285,20 @@
         return mTaskPersister.getTaskDescriptionIcon(filename);
     }
 
+    @Override
+    public void startInPlaceAnimationOnFrontMostApplication(ActivityOptions opts)
+            throws RemoteException {
+        if (opts.getAnimationType() != ActivityOptions.ANIM_CUSTOM_IN_PLACE ||
+                opts.getCustomInPlaceResId() == 0) {
+            throw new IllegalArgumentException("Expected in-place ActivityOption " +
+                    "with valid animation");
+        }
+        mWindowManager.prepareAppTransition(AppTransition.TRANSIT_TASK_IN_PLACE, false);
+        mWindowManager.overridePendingAppTransitionInPlace(opts.getPackageName(),
+                opts.getCustomInPlaceResId());
+        mWindowManager.executeAppTransition();
+    }
+
     private void cleanUpRemovedTaskLocked(TaskRecord tr, boolean killProcess) {
         mRecentTasks.remove(tr);
         tr.removedFromRecents(mTaskPersister);
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index eeb007c..f6e8bcf 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -109,6 +109,8 @@
     /** A window in a new task is being opened behind an existing one in another activity's task.
      * The new window will show briefly and then be gone. */
     public static final int TRANSIT_TASK_OPEN_BEHIND = 16;
+    /** A window in a task is being animated in-place. */
+    public static final int TRANSIT_TASK_IN_PLACE = 17;
 
     /** Fraction of animation at which the recents thumbnail stays completely transparent */
     private static final float RECENTS_THUMBNAIL_FADEIN_FRACTION = 0.7f;
@@ -131,6 +133,7 @@
     private static final int NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_DOWN = 4;
     private static final int NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP = 5;
     private static final int NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_DOWN = 6;
+    private static final int NEXT_TRANSIT_TYPE_CUSTOM_IN_PLACE = 7;
     private int mNextAppTransitionType = NEXT_TRANSIT_TYPE_NONE;
 
     // These are the possible states for the enter/exit activities during a thumbnail transition
@@ -146,6 +149,7 @@
     private IRemoteCallback mNextAppTransitionCallback;
     private int mNextAppTransitionEnter;
     private int mNextAppTransitionExit;
+    private int mNextAppTransitionInPlace;
     private int mNextAppTransitionStartX;
     private int mNextAppTransitionStartY;
     private int mNextAppTransitionStartWidth;
@@ -835,6 +839,12 @@
                     + " anim=" + a + " nextAppTransition=ANIM_CUSTOM"
                     + " transit=" + transit + " isEntrance=" + enter
                     + " Callers=" + Debug.getCallers(3));
+        } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_CUSTOM_IN_PLACE) {
+            a = loadAnimationRes(mNextAppTransitionPackage, mNextAppTransitionInPlace);
+            if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG,
+                    "applyAnimation:"
+                            + " anim=" + a + " nextAppTransition=ANIM_CUSTOM_IN_PLACE"
+                            + " transit=" + transit + " Callers=" + Debug.getCallers(3));
         } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_SCALE_UP) {
             a = createScaleUpAnimationLocked(transit, enter, appWidth, appHeight);
             if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG,
@@ -1013,6 +1023,16 @@
         }
     }
 
+    void overrideInPlaceAppTransition(String packageName, int anim) {
+        if (isTransitionSet()) {
+            mNextAppTransitionType = NEXT_TRANSIT_TYPE_CUSTOM_IN_PLACE;
+            mNextAppTransitionPackage = packageName;
+            mNextAppTransitionInPlace = anim;
+        } else {
+            postAnimationCallback();
+        }
+    }
+
     @Override
     public String toString() {
         return "mNextAppTransition=0x" + Integer.toHexString(mNextAppTransition);
@@ -1092,6 +1112,8 @@
                 return "NEXT_TRANSIT_TYPE_NONE";
             case NEXT_TRANSIT_TYPE_CUSTOM:
                 return "NEXT_TRANSIT_TYPE_CUSTOM";
+            case NEXT_TRANSIT_TYPE_CUSTOM_IN_PLACE:
+                return "NEXT_TRANSIT_TYPE_CUSTOM_IN_PLACE";
             case NEXT_TRANSIT_TYPE_SCALE_UP:
                 return "NEXT_TRANSIT_TYPE_SCALE_UP";
             case NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_UP:
@@ -1123,6 +1145,12 @@
                         pw.print(" mNextAppTransitionExit=0x");
                         pw.println(Integer.toHexString(mNextAppTransitionExit));
                 break;
+            case NEXT_TRANSIT_TYPE_CUSTOM_IN_PLACE:
+                pw.print("  mNextAppTransitionPackage=");
+                        pw.println(mNextAppTransitionPackage);
+                pw.print("  mNextAppTransitionInPlace=0x");
+                        pw.print(Integer.toHexString(mNextAppTransitionInPlace));
+                break;
             case NEXT_TRANSIT_TYPE_SCALE_UP:
                 pw.print("  mNextAppTransitionStartX="); pw.print(mNextAppTransitionStartX);
                         pw.print(" mNextAppTransitionStartY=");
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 968b35c..6ee4537 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -4124,6 +4124,13 @@
     }
 
     @Override
+    public void overridePendingAppTransitionInPlace(String packageName, int anim) {
+        synchronized(mWindowMap) {
+            mAppTransition.overrideInPlaceAppTransition(packageName, anim);
+        }
+    }
+
+    @Override
     public void executeAppTransition() {
         if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
                 "executeAppTransition()")) {
@@ -4535,6 +4542,15 @@
         return delayed;
     }
 
+    void updateTokenInPlaceLocked(AppWindowToken wtoken, int transit) {
+        if (transit != AppTransition.TRANSIT_UNSET) {
+            if (wtoken.mAppAnimator.animation == AppWindowAnimator.sDummyAnimation) {
+                wtoken.mAppAnimator.animation = null;
+            }
+            applyAnimationLocked(wtoken, null, transit, false, false);
+        }
+    }
+
     @Override
     public void setAppVisibility(IBinder token, boolean visible) {
         if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
@@ -9125,6 +9141,29 @@
             int topOpeningLayer = 0;
             int topClosingLayer = 0;
 
+            // Process all applications animating in place
+            if (transit == AppTransition.TRANSIT_TASK_IN_PLACE) {
+                // Find the focused window
+                final WindowState win =
+                        findFocusedWindowLocked(getDefaultDisplayContentLocked());
+                if (win != null) {
+                    final AppWindowToken wtoken = win.mAppToken;
+                    final AppWindowAnimator appAnimator = wtoken.mAppAnimator;
+                    if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "Now animating app in place " + wtoken);
+                    appAnimator.clearThumbnail();
+                    appAnimator.animation = null;
+                    updateTokenInPlaceLocked(wtoken, transit);
+                    wtoken.updateReportedVisibilityLocked();
+
+                    appAnimator.mAllAppWinAnimators.clear();
+                    final int N = wtoken.allAppWindows.size();
+                    for (int j = 0; j < N; j++) {
+                        appAnimator.mAllAppWinAnimators.add(wtoken.allAppWindows.get(j).mWinAnimator);
+                    }
+                    mAnimator.mAnimating |= appAnimator.showAllWindowsLocked();
+                }
+            }
+
             NN = mOpeningApps.size();
             for (i=0; i<NN; i++) {
                 AppWindowToken wtoken = mOpeningApps.valueAt(i);
diff --git a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
index c403ce6..5176419 100644
--- a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
+++ b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
@@ -228,6 +228,11 @@
     }
 
     @Override
+    public void overridePendingAppTransitionInPlace(String packageName, int anim) {
+        // TODO Auto-generated method stub
+    }
+
+    @Override
     public void pauseKeyDispatching(IBinder arg0) throws RemoteException {
         // TODO Auto-generated method stub