Entry animation for docking windows.

We achieve the animation in the same way we would do that for maximizing
windows. We preserve the exiting window of relaunched activity until the
activity adds a new one. Then we animation the new window from the
bounds of the old window to the dock bounds.

This mostly reuses existing infrastructure for maximize animations, but
unfortunately needs scaling. The before window might be have one
dimension larger than after window and using cropping is not sufficient.

Change-Id: I9538ba983a0af1e64ea48aad6836173d6fd25f3b
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 17a4472..0e0ebf8 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -1348,8 +1348,7 @@
 
         r.launchFailed = false;
         if (stack.updateLRUListLocked(r)) {
-            Slog.w(TAG, "Activity " + r
-                  + " being launched, but already in LRU list");
+            Slog.w(TAG, "Activity " + r + " being launched, but already in LRU list");
         }
 
         if (andResume) {
@@ -3110,16 +3109,6 @@
                 ensureActivitiesVisibleLocked(r, 0, !PRESERVE_WINDOWS);
                 if (!kept) {
                     resumeTopActivitiesLocked(stack, null, null);
-                    if (changedStacks && stackId == FULLSCREEN_WORKSPACE_STACK_ID) {
-                        // We are about to relaunch the activity because its configuration changed
-                        // due to being maximized, i.e. size change. The activity will first
-                        // remove the old window and then add a new one. This call will tell window
-                        // manager about this, so it can preserve the old window until the new
-                        // one is drawn. This prevents having a gap between the removal and
-                        // addition, in which no window is visible. We also want the entrace of the
-                        // new window to be properly animated.
-                        mWindowManager.setReplacingWindow(r.appToken, true /* animate */);
-                    }
                 }
             }
         }
@@ -3246,6 +3235,16 @@
             return;
         }
         final String reason = "moveTaskToStack";
+        if (stackId == DOCKED_STACK_ID || stackId == FULLSCREEN_WORKSPACE_STACK_ID) {
+            // We are about to relaunch the activity because its configuration changed due to
+            // being maximized, i.e. size change. The activity will first remove the old window
+            // and then add a new one. This call will tell window manager about this, so it can
+            // preserve the old window until the new one is drawn. This prevents having a gap
+            // between the removal and addition, in which no window is visible. We also want the
+            // entrace of the new window to be properly animated.
+            ActivityRecord r = task.getTopActivity();
+            mWindowManager.setReplacingWindow(r.appToken, true /* animate */);
+        }
         final ActivityStack stack =
                 moveTaskToStackUncheckedLocked(task, stackId, toTop, forceFocus, reason);
 
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index 192b168..a64cda6 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -1003,17 +1003,33 @@
         return prepareThumbnailAnimation(a, appWidth, appHeight, transit);
     }
 
-    private Animation createRelaunchAnimation(int appWidth, int appHeight) {
+    private Animation createRelaunchAnimation(int appWidth, int appHeight,
+            Rect containingFrame) {
         getDefaultNextAppTransitionStartRect(mTmpFromClipRect);
         final int left = mTmpFromClipRect.left;
         final int top = mTmpFromClipRect.top;
         mTmpFromClipRect.offset(-left, -top);
         mTmpToClipRect.set(0, 0, appWidth, appHeight);
         AnimationSet set = new AnimationSet(true);
-        ClipRectAnimation clip = new ClipRectAnimation(mTmpFromClipRect, mTmpToClipRect);
-        TranslateAnimation translate = new TranslateAnimation(left, 0, top, 0);
-        clip.setInterpolator(mDecelerateInterpolator);
-        set.addAnimation(clip);
+        float fromWidth = mTmpFromClipRect.width();
+        float toWidth = mTmpToClipRect.width();
+        float fromHeight = mTmpFromClipRect.height();
+        float toHeight = mTmpToClipRect.height();
+        if (fromWidth <= toWidth && fromHeight <= toHeight) {
+            // The final window is larger in both dimensions than current window (e.g. we are
+            // maximizing), so we can simply unclip the new window and there will be no disappearing
+            // frame.
+            set.addAnimation(new ClipRectAnimation(mTmpFromClipRect, mTmpToClipRect));
+        } else {
+            // The disappearing window has one larger dimension. We need to apply scaling, so the
+            // first frame of the entry animation matches the old window.
+            set.addAnimation(new ScaleAnimation(fromWidth / toWidth, 1, fromHeight / toHeight, 1));
+        }
+
+        // We might not be going exactly full screen, but instead be aligned under the status bar.
+        // We need to take this into account when creating the translate animation.
+        TranslateAnimation translate = new TranslateAnimation(left - containingFrame.left,
+                0, top - containingFrame.top, 0);
         set.addAnimation(translate);
         set.setDuration(DEFAULT_APP_TRANSITION_DURATION);
         return set;
@@ -1056,7 +1072,12 @@
                     + " anim=" + a + " transit=" + appTransitionToString(transit)
                     + " isEntrance=" + enter + " Callers=" + Debug.getCallers(3));
         } else if (transit == TRANSIT_ACTIVITY_RELAUNCH) {
-            a = createRelaunchAnimation(appWidth, appHeight);
+            a = createRelaunchAnimation(appWidth, appHeight, containingFrame);
+            if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG,
+                    "applyAnimation:"
+                    + " anim=" + a + " nextAppTransition=" + mNextAppTransition
+                    + " transit=" + appTransitionToString(transit)
+                    + " Callers=" + Debug.getCallers(3));
         } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_CUSTOM) {
             a = loadAnimationRes(mNextAppTransitionPackage, enter ?
                     mNextAppTransitionEnter : mNextAppTransitionExit);
@@ -1077,6 +1098,7 @@
             if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG,
                     "applyAnimation:"
                             + " anim=" + a + " nextAppTransition=ANIM_CLIP_REVEAL"
+                            + " transit=" + appTransitionToString(transit)
                             + " Callers=" + Debug.getCallers(3));
         } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_SCALE_UP) {
             a = createScaleUpAnimationLocked(transit, enter, appWidth, appHeight);
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 96fcf93..3357a9c 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -438,8 +438,7 @@
                 for (int winNdx = appWindows.size() - 1; winNdx >= 0; --winNdx) {
                     // We are in the middle of changing the state of displays/stacks/tasks. We need
                     // to finish that, before we let layout interfere with it.
-                    mService.removeWindowInnerLocked(appWindows.get(winNdx),
-                            false /* performLayout */);
+                    mService.removeWindowLocked(appWindows.get(winNdx));
                     doAnotherLayoutPass = true;
                 }
             }
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 22f9f50..11960fe 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2206,7 +2206,7 @@
         removeWindowInnerLocked(win, true);
     }
 
-    void removeWindowInnerLocked(WindowState win, boolean performLayout) {
+    private void removeWindowInnerLocked(WindowState win, boolean performLayout) {
         if (win.mRemoved) {
             // Nothing to do.
             return;
@@ -8317,7 +8317,7 @@
                 final int numTokens = tokens.size();
                 for (int tokenNdx = 0; tokenNdx < numTokens; ++tokenNdx) {
                     final AppWindowToken wtoken = tokens.get(tokenNdx);
-                    if (wtoken.mIsExiting) {
+                    if (wtoken.mIsExiting && !wtoken.mReplacingWindow) {
                         continue;
                     }
                     i = reAddAppWindowsLocked(displayContent, i, wtoken);
@@ -9909,6 +9909,12 @@
         return mWindowMap;
     }
 
+    /**
+     * Hint to a token that its activity will relaunch, which will trigger removal and addition of
+     * a window.
+     * @param token Application token for which the activity will be relaunched.
+     * @param animate Whether to animate the addition of the new window.
+     */
     public void setReplacingWindow(IBinder token, boolean animate) {
         synchronized (mWindowMap) {
             AppWindowToken appWindowToken = findAppWindowToken(token);
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 07e1fce..fe98900 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -1368,7 +1368,12 @@
         // so we need to translate to match the actual surface coordinates.
         clipRect.offset(attrs.surfaceInsets.left, attrs.surfaceInsets.top);
 
-        adjustCropToStackBounds(w, clipRect);
+        // We don't want to clip to stack bounds windows that are currently doing entrance
+        // animation. This is necessary for docking operation, otherwise the window will be
+        // suddenly cut off.
+        if (!mAnimator.mAnimating) {
+            adjustCropToStackBounds(w, clipRect);
+        }
 
         if (!clipRect.equals(mLastClipRect)) {
             mLastClipRect.set(clipRect);
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index e77ebe7..d86a8af 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -47,6 +47,7 @@
 import android.os.SystemClock;
 import android.os.Trace;
 import android.provider.Settings;
+import android.util.ArraySet;
 import android.util.Slog;
 import android.view.Display;
 import android.view.DisplayInfo;
@@ -1228,11 +1229,15 @@
         final WindowState oldWallpaper =
                 mWallpaperControllerLocked.isWallpaperTargetAnimating()
                         ? null : wallpaperTarget;
+        final ArraySet<AppWindowToken> openingApps = mService.mOpeningApps;
+        final ArraySet<AppWindowToken> closingApps = mService.mClosingApps;
         if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
                 "New wallpaper target=" + wallpaperTarget
                         + ", oldWallpaper=" + oldWallpaper
                         + ", lower target=" + lowerWallpaperTarget
-                        + ", upper target=" + upperWallpaperTarget);
+                        + ", upper target=" + upperWallpaperTarget
+                        + ", openingApps=" + openingApps
+                        + ", closingApps=" + closingApps);
         mService.mAnimateWallpaperWithTarget = false;
         if (closingAppHasWallpaper && openingAppHasWallpaper) {
             if (DEBUG_APP_TRANSITIONS)
@@ -1251,15 +1256,16 @@
             }
             if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
                     "New transit: " + AppTransition.appTransitionToString(transit));
-        } else if ((oldWallpaper != null) && !mService.mOpeningApps.isEmpty()
-                && !mService.mOpeningApps.contains(oldWallpaper.mAppToken)) {
-            // We are transitioning from an activity with
-            // a wallpaper to one without.
+        } else if (oldWallpaper != null && !mService.mOpeningApps.isEmpty()
+                && !openingApps.contains(oldWallpaper.mAppToken)
+                && closingApps.contains(oldWallpaper.mAppToken)) {
+            // We are transitioning from an activity with a wallpaper to one without.
             transit = AppTransition.TRANSIT_WALLPAPER_CLOSE;
             if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
                     "New transit away from wallpaper: "
                     + AppTransition.appTransitionToString(transit));
-        } else if (wallpaperTarget != null && wallpaperTarget.isVisibleLw()) {
+        } else if (wallpaperTarget != null && wallpaperTarget.isVisibleLw() &&
+                openingApps.contains(wallpaperTarget.mAppToken)) {
             // We are transitioning from an activity without
             // a wallpaper to now showing the wallpaper
             transit = AppTransition.TRANSIT_WALLPAPER_OPEN;