Add wallpaper leash for remote and recents animations

Bug: 140626334
Test: atest RecentsAnimationControllerTest
Test: atest RemoteAnimationControllerTest

Change-Id: Id638256983801e722b57da0abd22e3e480d5559d
Signed-off-by: Winson Chung <winsonc@google.com>
diff --git a/apct-tests/perftests/core/src/android/wm/RecentsAnimationPerfTest.java b/apct-tests/perftests/core/src/android/wm/RecentsAnimationPerfTest.java
index 2b6f455..9cfc3d2 100644
--- a/apct-tests/perftests/core/src/android/wm/RecentsAnimationPerfTest.java
+++ b/apct-tests/perftests/core/src/android/wm/RecentsAnimationPerfTest.java
@@ -188,8 +188,8 @@
 
             @Override
             public void onAnimationStart(IRecentsAnimationController controller,
-                    RemoteAnimationTarget[] apps, Rect homeContentInsets,
-                    Rect minimizedHomeBounds) throws RemoteException {
+                    RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers,
+                    Rect homeContentInsets, Rect minimizedHomeBounds) throws RemoteException {
                 final Pair<String, Boolean> finishCase = finishCases.get(mIteration++ % 2);
                 final boolean moveRecentsToTop = finishCase.second;
                 makeInterval();
diff --git a/core/java/android/view/IRecentsAnimationRunner.aidl b/core/java/android/view/IRecentsAnimationRunner.aidl
index 724d96e..6eb90fc 100644
--- a/core/java/android/view/IRecentsAnimationRunner.aidl
+++ b/core/java/android/view/IRecentsAnimationRunner.aidl
@@ -54,6 +54,6 @@
      */
     @UnsupportedAppUsage
     void onAnimationStart(in IRecentsAnimationController controller,
-            in RemoteAnimationTarget[] apps, in Rect homeContentInsets,
-            in Rect minimizedHomeBounds) = 2;
+            in RemoteAnimationTarget[] apps, in RemoteAnimationTarget[] wallpapers,
+            in Rect homeContentInsets, in Rect minimizedHomeBounds) = 2;
 }
diff --git a/core/java/android/view/IRemoteAnimationRunner.aidl b/core/java/android/view/IRemoteAnimationRunner.aidl
index 73b023c..7b35aa2 100644
--- a/core/java/android/view/IRemoteAnimationRunner.aidl
+++ b/core/java/android/view/IRemoteAnimationRunner.aidl
@@ -34,7 +34,7 @@
      * @param finishedCallback The callback to invoke when the animation is finished.
      */
     @UnsupportedAppUsage
-    void onAnimationStart(in RemoteAnimationTarget[] apps,
+    void onAnimationStart(in RemoteAnimationTarget[] apps, in RemoteAnimationTarget[] wallpapers,
             in IRemoteAnimationFinishedCallback finishedCallback);
 
     /**
diff --git a/core/java/android/view/SyncRtSurfaceTransactionApplier.java b/core/java/android/view/SyncRtSurfaceTransactionApplier.java
index 1b6c575..a6536f5d 100644
--- a/core/java/android/view/SyncRtSurfaceTransactionApplier.java
+++ b/core/java/android/view/SyncRtSurfaceTransactionApplier.java
@@ -255,7 +255,7 @@
             this.surface = surface;
             this.alpha = alpha;
             this.matrix = new Matrix(matrix);
-            this.windowCrop = new Rect(windowCrop);
+            this.windowCrop = windowCrop != null ? new Rect(windowCrop) : null;
             this.layer = layer;
             this.cornerRadius = cornerRadius;
             this.visible = visible;
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
index 82287873..00e8b53 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
@@ -225,14 +225,16 @@
                 runner = new IRecentsAnimationRunner.Stub() {
                     @Override
                     public void onAnimationStart(IRecentsAnimationController controller,
-                            RemoteAnimationTarget[] apps, Rect homeContentInsets,
-                            Rect minimizedHomeBounds) {
+                            RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers,
+                            Rect homeContentInsets, Rect minimizedHomeBounds) {
                         final RecentsAnimationControllerCompat controllerCompat =
                                 new RecentsAnimationControllerCompat(controller);
                         final RemoteAnimationTargetCompat[] appsCompat =
                                 RemoteAnimationTargetCompat.wrap(apps);
+                        final RemoteAnimationTargetCompat[] wallpapersCompat =
+                                RemoteAnimationTargetCompat.wrap(wallpapers);
                         animationHandler.onAnimationStart(controllerCompat, appsCompat,
-                                homeContentInsets, minimizedHomeBounds);
+                                wallpapersCompat, homeContentInsets, minimizedHomeBounds);
                     }
 
                     @Override
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java
index 579858a..2c99c5c 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java
@@ -21,12 +21,12 @@
 import com.android.systemui.shared.recents.model.ThumbnailData;
 
 public interface RecentsAnimationListener {
-
     /**
      * Called when the animation into Recents can start. This call is made on the binder thread.
      */
     void onAnimationStart(RecentsAnimationControllerCompat controller,
-            RemoteAnimationTargetCompat[] apps, Rect homeContentInsets, Rect minimizedHomeBounds);
+            RemoteAnimationTargetCompat[] apps, RemoteAnimationTargetCompat[] wallpapers,
+            Rect homeContentInsets, Rect minimizedHomeBounds);
 
     /**
      * Called when the animation into Recents was canceled. This call is made on the binder thread.
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java
index 61be076..02e509e 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java
@@ -45,9 +45,12 @@
         return new IRemoteAnimationRunner.Stub() {
             @Override
             public void onAnimationStart(RemoteAnimationTarget[] apps,
+                    RemoteAnimationTarget[] wallpapers,
                     final IRemoteAnimationFinishedCallback finishedCallback) {
                 final RemoteAnimationTargetCompat[] appsCompat =
                         RemoteAnimationTargetCompat.wrap(apps);
+                final RemoteAnimationTargetCompat[] wallpapersCompat =
+                        RemoteAnimationTargetCompat.wrap(wallpapers);
                 final Runnable animationFinishedCallback = new Runnable() {
                     @Override
                     public void run() {
@@ -59,7 +62,8 @@
                         }
                     }
                 };
-                remoteAnimationAdapter.onAnimationStart(appsCompat, animationFinishedCallback);
+                remoteAnimationAdapter.onAnimationStart(appsCompat, wallpapersCompat,
+                        animationFinishedCallback);
             }
 
             @Override
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java
index 5a85df9..33372f6 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java
@@ -17,6 +17,7 @@
 package com.android.systemui.shared.system;
 
 public interface RemoteAnimationRunnerCompat {
-    void onAnimationStart(RemoteAnimationTargetCompat[] apps, Runnable finishedCallback);
+    void onAnimationStart(RemoteAnimationTargetCompat[] apps,
+            RemoteAnimationTargetCompat[] wallpapers, Runnable finishedCallback);
     void onAnimationCancelled();
 }
\ No newline at end of file
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
index 221782e..ca88f13 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
@@ -68,7 +68,7 @@
 
     public static RemoteAnimationTargetCompat[] wrap(RemoteAnimationTarget[] apps) {
         final RemoteAnimationTargetCompat[] appsCompat =
-                new RemoteAnimationTargetCompat[apps.length];
+                new RemoteAnimationTargetCompat[apps != null ? apps.length : 0];
         for (int i = 0; i < apps.length; i++) {
             appsCompat[i] = new RemoteAnimationTargetCompat(apps[i]);
         }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java
index 9ba21a3..e80b437 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java
@@ -142,7 +142,9 @@
     public static void applyParams(TransactionCompat t,
             SyncRtSurfaceTransactionApplierCompat.SurfaceParams params) {
         t.setMatrix(params.surface, params.matrix);
-        t.setWindowCrop(params.surface, params.windowCrop);
+        if (params.windowCrop != null) {
+            t.setWindowCrop(params.surface, params.windowCrop);
+        }
         t.setAlpha(params.surface, params.alpha);
         t.setLayer(params.surface, params.layer);
         t.setCornerRadius(params.surface, params.cornerRadius);
@@ -187,14 +189,14 @@
          * @param surface The surface to modify.
          * @param alpha Alpha to apply.
          * @param matrix Matrix to apply.
-         * @param windowCrop Crop to apply.
+         * @param windowCrop Crop to apply, only applied if not {@code null}
          */
         public SurfaceParams(SurfaceControlCompat surface, float alpha, Matrix matrix,
                 Rect windowCrop, int layer, float cornerRadius) {
             this.surface = surface;
             this.alpha = alpha;
             this.matrix = new Matrix(matrix);
-            this.windowCrop = new Rect(windowCrop);
+            this.windowCrop = windowCrop != null ? new Rect(windowCrop) : null;
             this.layer = layer;
             this.cornerRadius = cornerRadius;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java
index 91d4707..13c6f27 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java
@@ -34,7 +34,6 @@
 
 import com.android.internal.policy.ScreenDecorationsUtils;
 import com.android.systemui.Interpolators;
-import com.android.systemui.shared.system.SurfaceControlCompat;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment;
@@ -145,6 +144,7 @@
 
         @Override
         public void onAnimationStart(RemoteAnimationTarget[] remoteAnimationTargets,
+                RemoteAnimationTarget[] remoteAnimationWallpaperTargets,
                 IRemoteAnimationFinishedCallback iRemoteAnimationFinishedCallback)
                     throws RemoteException {
             mSourceNotification.post(() -> {
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index 19b5f31..985c77a 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -93,6 +93,8 @@
     private IRecentsAnimationRunner mRunner;
     private final RecentsAnimationCallbacks mCallbacks;
     private final ArrayList<TaskAnimationAdapter> mPendingAnimations = new ArrayList<>();
+    private final ArrayList<WallpaperAnimationAdapter> mPendingWallpaperAnimations =
+            new ArrayList<>();
     private final int mDisplayId;
     private final Runnable mFailsafeRunnable = () ->
             cancelAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION, "failSafeRunnable");
@@ -434,6 +436,13 @@
         mPendingAnimations.remove(taskAdapter);
     }
 
+    @VisibleForTesting
+    void removeWallpaperAnimation(WallpaperAnimationAdapter wallpaperAdapter) {
+        if (DEBUG_RECENTS_ANIMATIONS) Slog.d(TAG, "removeWallpaperAnimation()");
+        wallpaperAdapter.getLeashFinishedCallback().onAnimationFinished(wallpaperAdapter);
+        mPendingWallpaperAnimations.remove(wallpaperAdapter);
+    }
+
     void startAnimation() {
         if (DEBUG_RECENTS_ANIMATIONS) Slog.d(TAG, "startAnimation(): mPendingStart=" + mPendingStart
                 + " mCanceled=" + mCanceled);
@@ -442,25 +451,18 @@
             return;
         }
         try {
-            final ArrayList<RemoteAnimationTarget> appAnimations = new ArrayList<>();
-            for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
-                final TaskAnimationAdapter taskAdapter = mPendingAnimations.get(i);
-                final RemoteAnimationTarget target = taskAdapter.createRemoteAnimationApp();
-                if (target != null) {
-                    appAnimations.add(target);
-                } else {
-                    removeAnimation(taskAdapter);
-                }
-            }
+            // Create the app targets
+            final RemoteAnimationTarget[] appTargets = createAppAnimations();
 
             // Skip the animation if there is nothing to animate
-            if (appAnimations.isEmpty()) {
+            if (appTargets.length == 0) {
                 cancelAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION, "startAnimation-noAppWindows");
                 return;
             }
 
-            final RemoteAnimationTarget[] appTargets = appAnimations.toArray(
-                    new RemoteAnimationTarget[appAnimations.size()]);
+            // Create the wallpaper targets
+            final RemoteAnimationTarget[] wallpaperTargets = createWallpaperAnimations();
+
             mPendingStart = false;
 
             // Perform layout if it was scheduled before to make sure that we get correct content
@@ -479,7 +481,8 @@
                 mService.getStableInsets(mDisplayId, mTmpRect);
                 contentInsets = mTmpRect;
             }
-            mRunner.onAnimationStart(mController, appTargets, contentInsets, minimizedHomeBounds);
+            mRunner.onAnimationStart(mController, appTargets, wallpaperTargets, contentInsets,
+                    minimizedHomeBounds);
             if (DEBUG_RECENTS_ANIMATIONS) {
                 Slog.d(TAG, "startAnimation(): Notify animation start:");
                 for (int i = 0; i < mPendingAnimations.size(); i++) {
@@ -495,6 +498,32 @@
         mService.mAtmInternal.notifyAppTransitionStarting(reasons, SystemClock.uptimeMillis());
     }
 
+    private RemoteAnimationTarget[] createAppAnimations() {
+        final ArrayList<RemoteAnimationTarget> targets = new ArrayList<>();
+        for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
+            final TaskAnimationAdapter taskAdapter = mPendingAnimations.get(i);
+            final RemoteAnimationTarget target = taskAdapter.createRemoteAnimationTarget();
+            if (target != null) {
+                targets.add(target);
+            } else {
+                removeAnimation(taskAdapter);
+            }
+        }
+        return targets.toArray(new RemoteAnimationTarget[targets.size()]);
+    }
+
+    private RemoteAnimationTarget[] createWallpaperAnimations() {
+        if (DEBUG_RECENTS_ANIMATIONS) Slog.d(TAG, "createWallpaperAnimations()");
+        return WallpaperAnimationAdapter.startWallpaperAnimations(mService, 0L, 0L,
+                adapter -> {
+                    synchronized (mService.mGlobalLock) {
+                        // If the wallpaper animation is canceled, continue with the recents
+                        // animation
+                        mPendingWallpaperAnimations.remove(adapter);
+                    }
+                }, mPendingWallpaperAnimations);
+    }
+
     void cancelAnimation(@ReorderMode int reorderMode, String reason) {
         cancelAnimation(reorderMode, false /*screenshot */, reason);
     }
@@ -619,6 +648,11 @@
             removeAnimation(taskAdapter);
         }
 
+        for (int i = mPendingWallpaperAnimations.size() - 1; i >= 0; i--) {
+            final WallpaperAnimationAdapter wallpaperAdapter = mPendingWallpaperAnimations.get(i);
+            removeWallpaperAnimation(wallpaperAdapter);
+        }
+
         // Clear any pending failsafe runnables
         mService.mH.removeCallbacks(mFailsafeRunnable);
         mDisplayContent.mAppTransition.unregisterListener(mAppTransitionListener);
@@ -747,6 +781,15 @@
         return false;
     }
 
+    boolean isAnimatingWallpaper(WallpaperWindowToken token) {
+        for (int i = mPendingWallpaperAnimations.size() - 1; i >= 0; i--) {
+            if (token == mPendingWallpaperAnimations.get(i).getToken()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     private boolean isAnimatingApp(AppWindowToken appToken) {
         for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
             final Task task = mPendingAnimations.get(i).mTask;
@@ -784,7 +827,7 @@
             mBounds.set(container.getDisplayedBounds());
         }
 
-        RemoteAnimationTarget createRemoteAnimationApp() {
+        RemoteAnimationTarget createRemoteAnimationTarget() {
             final AppWindowToken topApp = mTask.getTopVisibleAppToken();
             final WindowState mainWindow = topApp != null
                     ? topApp.findMainWindow()
@@ -830,6 +873,7 @@
 
         @Override
         public void onAnimationCancelled(SurfaceControl animationLeash) {
+            // Cancel the animation immediately if any single task animator is canceled
             cancelAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION, "taskAnimationAdapterCanceled");
         }
 
diff --git a/services/core/java/com/android/server/wm/RemoteAnimationController.java b/services/core/java/com/android/server/wm/RemoteAnimationController.java
index 7448e00..42430ba 100644
--- a/services/core/java/com/android/server/wm/RemoteAnimationController.java
+++ b/services/core/java/com/android/server/wm/RemoteAnimationController.java
@@ -58,6 +58,8 @@
     private final WindowManagerService mService;
     private final RemoteAnimationAdapter mRemoteAnimationAdapter;
     private final ArrayList<RemoteAnimationRecord> mPendingAnimations = new ArrayList<>();
+    private final ArrayList<WallpaperAnimationAdapter> mPendingWallpaperAnimations =
+            new ArrayList<>();
     private final Rect mTmpRect = new Rect();
     private final Handler mHandler;
     private final Runnable mTimeoutRunnable = () -> cancelAnimation("timeoutRunnable");
@@ -110,16 +112,21 @@
                 (long) (TIMEOUT_MS * mService.getCurrentAnimatorScale()));
         mFinishedCallback = new FinishedCallback(this);
 
-        final RemoteAnimationTarget[] animations = createAnimations();
-        if (animations.length == 0) {
+        // Create the app targets
+        final RemoteAnimationTarget[] appTargets = createAppAnimations();
+        if (appTargets.length == 0) {
             if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "goodToGo(): No apps to animate");
             onAnimationFinished();
             return;
         }
+
+        // Create the remote wallpaper animation targets (if any)
+        final RemoteAnimationTarget[] wallpaperTargets = createWallpaperAnimations();
         mService.mAnimator.addAfterPrepareSurfacesRunnable(() -> {
             try {
                 linkToDeathOfRunner();
-                mRemoteAnimationAdapter.getRunner().onAnimationStart(animations, mFinishedCallback);
+                mRemoteAnimationAdapter.getRunner().onAnimationStart(appTargets, wallpaperTargets,
+                        mFinishedCallback);
             } catch (RemoteException e) {
                 Slog.e(TAG, "Failed to start remote animation", e);
                 onAnimationFinished();
@@ -155,8 +162,8 @@
         Slog.i(TAG, sw.toString());
     }
 
-    private RemoteAnimationTarget[] createAnimations() {
-        if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "createAnimations()");
+    private RemoteAnimationTarget[] createAppAnimations() {
+        if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "createAppAnimations()");
         final ArrayList<RemoteAnimationTarget> targets = new ArrayList<>();
         for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
             final RemoteAnimationRecord wrappers = mPendingAnimations.get(i);
@@ -186,6 +193,19 @@
         return targets.toArray(new RemoteAnimationTarget[targets.size()]);
     }
 
+    private RemoteAnimationTarget[] createWallpaperAnimations() {
+        if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "createWallpaperAnimations()");
+        return WallpaperAnimationAdapter.startWallpaperAnimations(mService,
+                mRemoteAnimationAdapter.getDuration(),
+                mRemoteAnimationAdapter.getStatusBarTransitionDelay(),
+                adapter -> {
+                    synchronized (mService.mGlobalLock) {
+                        // If the wallpaper animation is canceled, continue with the app animation
+                        mPendingWallpaperAnimations.remove(adapter);
+                    }
+                }, mPendingWallpaperAnimations);
+    }
+
     private void onAnimationFinished() {
         if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "onAnimationFinished(): mPendingAnimations="
                 + mPendingAnimations.size());
@@ -207,7 +227,15 @@
                         adapters.mThumbnailAdapter.mCapturedFinishCallback
                                 .onAnimationFinished(adapters.mThumbnailAdapter);
                     }
-                    if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "\t" + adapters.mAppWindowToken);
+                    mPendingAnimations.remove(i);
+                    if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "\tapp=" + adapters.mAppWindowToken);
+                }
+
+                for (int i = mPendingWallpaperAnimations.size() - 1; i >= 0; i--) {
+                    final WallpaperAnimationAdapter adapter = mPendingWallpaperAnimations.get(i);
+                    adapter.getLeashFinishedCallback().onAnimationFinished(adapter);
+                    mPendingWallpaperAnimations.remove(i);
+                    if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "\twallpaper=" + adapter.getToken());
                 }
             } catch (Exception e) {
                 Slog.e(TAG, "Failed to finish remote animation", e);
@@ -424,10 +452,7 @@
                 mPendingAnimations.remove(mRecord);
             }
             if (mPendingAnimations.isEmpty()) {
-                mHandler.removeCallbacks(mTimeoutRunnable);
-                releaseFinishedCallback();
-                invokeAnimationCancelled();
-                setRunningRemoteAnimation(false);
+                cancelAnimation("allAppAnimationsCanceled");
             }
         }
 
diff --git a/services/core/java/com/android/server/wm/WallpaperAnimationAdapter.java b/services/core/java/com/android/server/wm/WallpaperAnimationAdapter.java
new file mode 100644
index 0000000..895350b
--- /dev/null
+++ b/services/core/java/com/android/server/wm/WallpaperAnimationAdapter.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.wm;
+
+import static com.android.server.wm.AnimationAdapterProto.REMOTE;
+import static com.android.server.wm.RemoteAnimationAdapterWrapperProto.TARGET;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_RECENTS_ANIMATIONS;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_REMOTE_ANIMATIONS;
+
+import android.graphics.Point;
+import android.os.SystemClock;
+import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
+import android.view.RemoteAnimationTarget;
+import android.view.SurfaceControl;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.function.Consumer;
+
+/**
+ * An animation adapter for wallpaper windows.
+ */
+class WallpaperAnimationAdapter implements AnimationAdapter {
+    private static final String TAG = "WallpaperAnimationAdapter";
+
+    private final WallpaperWindowToken mWallpaperToken;
+    private SurfaceControl mCapturedLeash;
+    private SurfaceAnimator.OnAnimationFinishedCallback mCapturedLeashFinishCallback;
+
+    private long mDurationHint;
+    private long mStatusBarTransitionDelay;
+
+    private Consumer<WallpaperAnimationAdapter> mAnimationCanceledRunnable;
+    private RemoteAnimationTarget mTarget;
+
+    WallpaperAnimationAdapter(WallpaperWindowToken wallpaperToken,
+            long durationHint, long statusBarTransitionDelay,
+            Consumer<WallpaperAnimationAdapter> animationCanceledRunnable) {
+        mWallpaperToken = wallpaperToken;
+        mDurationHint = durationHint;
+        mStatusBarTransitionDelay = statusBarTransitionDelay;
+        mAnimationCanceledRunnable = animationCanceledRunnable;
+    }
+
+    /**
+     * Creates and starts remote animations for all the visible wallpaper windows.
+     *
+     * @return RemoteAnimationTarget[] targets for all the visible wallpaper windows
+     */
+    public static RemoteAnimationTarget[] startWallpaperAnimations(WindowManagerService service,
+            long durationHint, long statusBarTransitionDelay,
+            Consumer<WallpaperAnimationAdapter> animationCanceledRunnable,
+            ArrayList<WallpaperAnimationAdapter> adaptersOut) {
+        final ArrayList<RemoteAnimationTarget> targets = new ArrayList<>();
+        service.mRoot.forAllWallpaperWindows(wallpaperWindow -> {
+            if (!wallpaperWindow.getDisplayContent().mWallpaperController.isWallpaperVisible()) {
+                if (DEBUG_REMOTE_ANIMATIONS || DEBUG_RECENTS_ANIMATIONS) {
+                    Slog.d(TAG, "\tNot visible=" + wallpaperWindow);
+                }
+                return;
+            }
+
+            if (DEBUG_REMOTE_ANIMATIONS || DEBUG_RECENTS_ANIMATIONS) {
+                Slog.d(TAG, "\tvisible=" + wallpaperWindow);
+            }
+            final WallpaperAnimationAdapter wallpaperAdapter = new WallpaperAnimationAdapter(
+                    wallpaperWindow, durationHint, statusBarTransitionDelay,
+                    animationCanceledRunnable);
+            wallpaperWindow.startAnimation(wallpaperWindow.getPendingTransaction(),
+                    wallpaperAdapter, false /* hidden */);
+            targets.add(wallpaperAdapter.createRemoteAnimationTarget());
+            adaptersOut.add(wallpaperAdapter);
+        });
+        return targets.toArray(new RemoteAnimationTarget[targets.size()]);
+    }
+
+    /**
+     * Create a remote animation target for this animation adapter.
+     */
+    RemoteAnimationTarget createRemoteAnimationTarget() {
+        mTarget = new RemoteAnimationTarget(-1, -1, getLeash(), false, null, null,
+                mWallpaperToken.getPrefixOrderIndex(), new Point(), null,
+                mWallpaperToken.getWindowConfiguration(), true, null, null);
+        return mTarget;
+    }
+
+    /**
+     * @return the leash for this animation (only valid after the wallpaper window surface animation
+     * has started).
+     */
+    SurfaceControl getLeash() {
+        return mCapturedLeash;
+    }
+
+    /**
+     * @return the callback to call to clean up when the animation has finished.
+     */
+    SurfaceAnimator.OnAnimationFinishedCallback getLeashFinishedCallback() {
+        return mCapturedLeashFinishCallback;
+    }
+
+    /**
+     * @return the wallpaper window
+     */
+    WallpaperWindowToken getToken() {
+        return mWallpaperToken;
+    }
+
+    @Override
+    public boolean getShowWallpaper() {
+        // Not used
+        return false;
+    }
+
+    @Override
+    public void startAnimation(SurfaceControl animationLeash, SurfaceControl.Transaction t,
+            SurfaceAnimator.OnAnimationFinishedCallback finishCallback) {
+        if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "startAnimation");
+
+        // Restore z-layering until client has a chance to modify it.
+        t.setLayer(animationLeash, mWallpaperToken.getPrefixOrderIndex());
+        mCapturedLeash = animationLeash;
+        mCapturedLeashFinishCallback = finishCallback;
+    }
+
+    @Override
+    public void onAnimationCancelled(SurfaceControl animationLeash) {
+        if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "onAnimationCancelled");
+        mAnimationCanceledRunnable.accept(this);
+    }
+
+    @Override
+    public long getDurationHint() {
+        return mDurationHint;
+    }
+
+    @Override
+    public long getStatusBarTransitionsStartTime() {
+        return SystemClock.uptimeMillis() + mStatusBarTransitionDelay;
+    }
+
+    @Override
+    public void dump(PrintWriter pw, String prefix) {
+        pw.print(prefix);
+        pw.print("token=");
+        pw.println(mWallpaperToken);
+        if (mTarget != null) {
+            pw.print(prefix);
+            pw.println("Target:");
+            mTarget.dump(pw, prefix + "  ");
+        } else {
+            pw.print(prefix);
+            pw.println("Target: null");
+        }
+    }
+
+    @Override
+    public void writeToProto(ProtoOutputStream proto) {
+        final long token = proto.start(REMOTE);
+        if (mTarget != null) {
+            mTarget.writeToProto(proto, TARGET);
+        }
+        proto.end(token);
+    }
+}
diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
index e15b783..528cece 100644
--- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java
+++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
@@ -17,6 +17,7 @@
 package com.android.server.wm;
 
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
+
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYERS;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -29,6 +30,8 @@
 import android.view.DisplayInfo;
 import android.view.animation.Animation;
 
+import java.util.function.Consumer;
+
 /**
  * A token that represents a set of wallpaper windows.
  */
@@ -153,6 +156,11 @@
     }
 
     @Override
+    void forAllWallpaperWindows(Consumer<WallpaperWindowToken> callback) {
+        callback.accept(this);
+    }
+
+    @Override
     public String toString() {
         if (stringName == null) {
             StringBuilder sb = new StringBuilder();
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index e280a663..586375f 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -895,6 +895,12 @@
         }
     }
 
+    void forAllWallpaperWindows(Consumer<WallpaperWindowToken> callback) {
+        for (int i = mChildren.size() - 1; i >= 0; --i) {
+            mChildren.get(i).forAllWallpaperWindows(callback);
+        }
+    }
+
     /**
      * For all tasks at or below this container call the callback.
      *
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 8444ab2..30c8eb3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -658,6 +658,7 @@
 
                     @Override
                     public void onAnimationStart(RemoteAnimationTarget[] apps,
+                            RemoteAnimationTarget[] wallpapers,
                             IRemoteAnimationFinishedCallback finishedCallback) {
 
                     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java
index 035568f..629a954 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java
@@ -75,6 +75,7 @@
     class TestRemoteAnimationRunner implements IRemoteAnimationRunner {
         @Override
         public void onAnimationStart(RemoteAnimationTarget[] apps,
+                RemoteAnimationTarget[] wallpapers,
                 IRemoteAnimationFinishedCallback finishedCallback) {
             for (RemoteAnimationTarget target : apps) {
                 assertNotNull(target.startBounds);
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
index c162b6a..45e6890 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
@@ -248,6 +248,7 @@
         boolean mCancelled = false;
         @Override
         public void onAnimationStart(RemoteAnimationTarget[] apps,
+                RemoteAnimationTarget[] wallpapers,
                 IRemoteAnimationFinishedCallback finishedCallback) throws RemoteException {
         }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
index 9ca0180..f792b0d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
@@ -26,13 +26,17 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.atLeast;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.reset;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verifyNoMoreInteractions;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
 import static com.android.server.wm.ActivityStackSupervisor.ON_TOP;
 import static com.android.server.wm.RecentsAnimationController.REORDER_KEEP_IN_PLACE;
 import static com.android.server.wm.RecentsAnimationController.REORDER_MOVE_TO_ORIGINAL_POSITION;
+import static com.android.server.wm.RecentsAnimationController.REORDER_MOVE_TO_TOP;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -44,9 +48,12 @@
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 
 import android.app.ActivityManager.TaskSnapshot;
 import android.os.Binder;
+import android.os.IBinder;
 import android.os.IInterface;
 import android.platform.test.annotations.Presubmit;
 import android.util.SparseBooleanArray;
@@ -88,8 +95,8 @@
             doReturn(mDisplayContent).when(mWm.mRoot).getDisplayContent(anyInt());
         }
         when(mMockRunner.asBinder()).thenReturn(new Binder());
-        mController = new RecentsAnimationController(mWm, mMockRunner, mAnimationCallbacks,
-                DEFAULT_DISPLAY);
+        mController = spy(new RecentsAnimationController(mWm, mMockRunner, mAnimationCallbacks,
+                DEFAULT_DISPLAY));
     }
 
     @Test
@@ -133,11 +140,11 @@
     @Test
     public void testIncludedApps_expectTargetAndVisible() {
         mWm.setRecentsAnimationController(mController);
-        final ActivityStack homStack = mDisplayContent.mAcitvityDisplay.getOrCreateStack(
+        final ActivityStack homeStack = mDisplayContent.mAcitvityDisplay.getOrCreateStack(
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, ON_TOP);
         final AppWindowToken homeAppWindow =
                 new ActivityTestsBase.ActivityBuilder(mWm.mAtmService)
-                        .setStack(homStack)
+                        .setStack(homeStack)
                         .setCreateTask(true)
                         .build()
                         .mAppWindowToken;
@@ -157,6 +164,102 @@
     }
 
     @Test
+    public void testWallpaperIncluded_expectTarget() throws Exception {
+        mWm.setRecentsAnimationController(mController);
+        final ActivityStack homeStack = mDisplayContent.mAcitvityDisplay.getOrCreateStack(
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, ON_TOP);
+        final AppWindowToken homeAppWindow =
+                new ActivityTestsBase.ActivityBuilder(mWm.mAtmService)
+                        .setStack(homeStack)
+                        .setCreateTask(true)
+                        .build()
+                        .mAppWindowToken;
+        final AppWindowToken appWindow = createAppWindowToken(mDisplayContent,
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
+        final WindowState win1 = createWindow(null, TYPE_BASE_APPLICATION, appWindow, "win1");
+        appWindow.addWindow(win1);
+        final WallpaperWindowToken wallpaperWindowToken = new WallpaperWindowToken(mWm,
+                mock(IBinder.class), true, mDisplayContent, true /* ownerCanManageAppTokens */);
+        spyOn(mDisplayContent.mWallpaperController);
+        doReturn(true).when(mDisplayContent.mWallpaperController).isWallpaperVisible();
+
+        mDisplayContent.getConfiguration().windowConfiguration.setRotation(
+                mDisplayContent.getRotation());
+        mController.initialize(ACTIVITY_TYPE_HOME, new SparseBooleanArray());
+        mController.startAnimation();
+
+        // Ensure that we are animating the app and wallpaper target
+        assertTrue(mController.isAnimatingTask(appWindow.getTask()));
+        assertTrue(mController.isAnimatingWallpaper(wallpaperWindowToken));
+    }
+
+    @Test
+    public void testWallpaperAnimatorCanceled_expectAnimationKeepsRunning() throws Exception {
+        mWm.setRecentsAnimationController(mController);
+        final ActivityStack homeStack = mDisplayContent.mAcitvityDisplay.getOrCreateStack(
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, ON_TOP);
+        final AppWindowToken homeAppWindow =
+                new ActivityTestsBase.ActivityBuilder(mWm.mAtmService)
+                        .setStack(homeStack)
+                        .setCreateTask(true)
+                        .build()
+                        .mAppWindowToken;
+        final AppWindowToken appWindow = createAppWindowToken(mDisplayContent,
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
+        final WindowState win1 = createWindow(null, TYPE_BASE_APPLICATION, appWindow, "win1");
+        appWindow.addWindow(win1);
+        final WallpaperWindowToken wallpaperWindowToken = new WallpaperWindowToken(mWm,
+                mock(IBinder.class), true, mDisplayContent, true /* ownerCanManageAppTokens */);
+        spyOn(mDisplayContent.mWallpaperController);
+        doReturn(true).when(mDisplayContent.mWallpaperController).isWallpaperVisible();
+
+        mDisplayContent.getConfiguration().windowConfiguration.setRotation(
+                mDisplayContent.getRotation());
+        mController.initialize(ACTIVITY_TYPE_HOME, new SparseBooleanArray());
+        mController.startAnimation();
+
+        // Cancel the animation and ensure the controller is still running
+        wallpaperWindowToken.cancelAnimation();
+        assertTrue(mController.isAnimatingTask(appWindow.getTask()));
+        assertFalse(mController.isAnimatingWallpaper(wallpaperWindowToken));
+        verify(mMockRunner, never()).onAnimationCanceled(null /* taskSnapshot */);
+    }
+
+    @Test
+    public void testFinish_expectTargetAndWallpaperAdaptersRemoved() {
+        mWm.setRecentsAnimationController(mController);
+        final ActivityStack homeStack = mDisplayContent.mAcitvityDisplay.getOrCreateStack(
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, ON_TOP);
+        final AppWindowToken homeAppWindow =
+                new ActivityTestsBase.ActivityBuilder(mWm.mAtmService)
+                        .setStack(homeStack)
+                        .setCreateTask(true)
+                        .build()
+                        .mAppWindowToken;
+        final WindowState hwin1 = createWindow(null, TYPE_BASE_APPLICATION, homeAppWindow, "hwin1");
+        homeAppWindow.addWindow(hwin1);
+        final AppWindowToken appWindow = createAppWindowToken(mDisplayContent,
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
+        final WindowState win1 = createWindow(null, TYPE_BASE_APPLICATION, appWindow, "win1");
+        appWindow.addWindow(win1);
+        final WallpaperWindowToken wallpaperWindowToken = new WallpaperWindowToken(mWm,
+                mock(IBinder.class), true, mDisplayContent, true /* ownerCanManageAppTokens */);
+        spyOn(mDisplayContent.mWallpaperController);
+        doReturn(true).when(mDisplayContent.mWallpaperController).isWallpaperVisible();
+
+        // Start and finish the animation
+        mController.initialize(ACTIVITY_TYPE_HOME, new SparseBooleanArray());
+        mController.startAnimation();
+        // Reset at this point since we may remove adapters that couldn't be created
+        reset(mController);
+        mController.cleanupAnimation(REORDER_MOVE_TO_TOP);
+
+        // Ensure that we remove the task (home & app) and wallpaper adapters
+        verify(mController, times(2)).removeAnimation(any());
+        verify(mController, times(1)).removeWallpaperAnimation(any());
+    }
+
+    @Test
     public void testDeferCancelAnimation() throws Exception {
         mWm.setRecentsAnimationController(mController);
         final AppWindowToken appWindow = createAppWindowToken(mDisplayContent,
diff --git a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
index 3e05dcc..3b9c3bb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
@@ -19,7 +19,9 @@
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.atLeast;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verifyNoMoreInteractions;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
@@ -27,10 +29,12 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
 
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.Binder;
+import android.os.IBinder;
 import android.os.IInterface;
 import android.platform.test.annotations.Presubmit;
 import android.view.IRemoteAnimationFinishedCallback;
@@ -96,9 +100,12 @@
             mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
             final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor =
                     ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
+            final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor =
+                    ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
             final ArgumentCaptor<IRemoteAnimationFinishedCallback> finishedCaptor =
                     ArgumentCaptor.forClass(IRemoteAnimationFinishedCallback.class);
-            verify(mMockRunner).onAnimationStart(appsCaptor.capture(), finishedCaptor.capture());
+            verify(mMockRunner).onAnimationStart(appsCaptor.capture(), wallpapersCaptor.capture(),
+                    finishedCaptor.capture());
             assertEquals(1, appsCaptor.getValue().length);
             final RemoteAnimationTarget app = appsCaptor.getValue()[0];
             assertEquals(new Point(50, 100), app.position);
@@ -201,9 +208,12 @@
         mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
         final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor =
                 ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
+        final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor =
+                ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
         final ArgumentCaptor<IRemoteAnimationFinishedCallback> finishedCaptor =
                 ArgumentCaptor.forClass(IRemoteAnimationFinishedCallback.class);
-        verify(mMockRunner).onAnimationStart(appsCaptor.capture(), finishedCaptor.capture());
+        verify(mMockRunner).onAnimationStart(appsCaptor.capture(), wallpapersCaptor.capture(),
+                finishedCaptor.capture());
         assertEquals(1, appsCaptor.getValue().length);
         assertEquals(mMockLeash, appsCaptor.getValue()[0].leash);
     }
@@ -237,9 +247,12 @@
             mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
             final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor =
                     ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
+            final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor =
+                    ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
             final ArgumentCaptor<IRemoteAnimationFinishedCallback> finishedCaptor =
                     ArgumentCaptor.forClass(IRemoteAnimationFinishedCallback.class);
-            verify(mMockRunner).onAnimationStart(appsCaptor.capture(), finishedCaptor.capture());
+            verify(mMockRunner).onAnimationStart(appsCaptor.capture(), wallpapersCaptor.capture(),
+                    finishedCaptor.capture());
             assertEquals(1, appsCaptor.getValue().length);
             final RemoteAnimationTarget app = appsCaptor.getValue()[0];
             assertEquals(RemoteAnimationTarget.MODE_CHANGING, app.mode);
@@ -264,6 +277,66 @@
         }
     }
 
+    @Test
+    public void testWallpaperIncluded_expectTarget() throws Exception {
+        final WindowToken wallpaperWindowToken = new WallpaperWindowToken(mWm, mock(IBinder.class),
+                true, mDisplayContent, true /* ownerCanManageAppTokens */);
+        spyOn(mDisplayContent.mWallpaperController);
+        doReturn(true).when(mDisplayContent.mWallpaperController).isWallpaperVisible();
+        final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin");
+        mDisplayContent.mOpeningApps.add(win.mAppToken);
+        try {
+            final AnimationAdapter adapter = mController.createRemoteAnimationRecord(win.mAppToken,
+                    new Point(50, 100), new Rect(50, 100, 150, 150), null).mAdapter;
+            adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback);
+            mController.goodToGo();
+            mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
+            final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor =
+                    ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
+            final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor =
+                    ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
+            final ArgumentCaptor<IRemoteAnimationFinishedCallback> finishedCaptor =
+                    ArgumentCaptor.forClass(IRemoteAnimationFinishedCallback.class);
+            verify(mMockRunner).onAnimationStart(appsCaptor.capture(), wallpapersCaptor.capture(),
+                    finishedCaptor.capture());
+            assertEquals(1, wallpapersCaptor.getValue().length);
+        } finally {
+            mDisplayContent.mOpeningApps.clear();
+        }
+    }
+
+    @Test
+    public void testWallpaperAnimatorCanceled_expectAnimationKeepsRunning() throws Exception {
+        final WindowToken wallpaperWindowToken = new WallpaperWindowToken(mWm, mock(IBinder.class),
+                true, mDisplayContent, true /* ownerCanManageAppTokens */);
+        spyOn(mDisplayContent.mWallpaperController);
+        doReturn(true).when(mDisplayContent.mWallpaperController).isWallpaperVisible();
+        final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin");
+        mDisplayContent.mOpeningApps.add(win.mAppToken);
+        try {
+            final AnimationAdapter adapter = mController.createRemoteAnimationRecord(win.mAppToken,
+                    new Point(50, 100), new Rect(50, 100, 150, 150), null).mAdapter;
+            adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback);
+            mController.goodToGo();
+            mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
+            final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor =
+                    ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
+            final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor =
+                    ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
+            final ArgumentCaptor<IRemoteAnimationFinishedCallback> finishedCaptor =
+                    ArgumentCaptor.forClass(IRemoteAnimationFinishedCallback.class);
+            verify(mMockRunner).onAnimationStart(appsCaptor.capture(), wallpapersCaptor.capture(),
+                    finishedCaptor.capture());
+            assertEquals(1, wallpapersCaptor.getValue().length);
+
+            // Cancel the wallpaper window animator and ensure the runner is not canceled
+            wallpaperWindowToken.cancelAnimation();
+            verify(mMockRunner, never()).onAnimationCancelled();
+        } finally {
+            mDisplayContent.mOpeningApps.clear();
+        }
+    }
+
     private static void verifyNoMoreInteractionsExceptAsBinder(IInterface binder) {
         verify(binder, atLeast(0)).asBinder();
         verifyNoMoreInteractions(binder);