Synchronize recents to freeform transition.

Recents to freeform animation must hang on the first frame and inform
Recents to hide its views. This mirrors the transition from freeform
to Recents, where the animation needs to hang on the last frame.

We need a special window flag for recents to force a redraw after the
animation launches. At this point Recents will become not visible
from the perspective of the activity manager, which would prevent
further drawing. We make recents ignore that and instead depend on
window visibility which will change after recents exit animation
finishes.

Bug: 24913782
Change-Id: Ief743b7e6fcebb3d8789d4745fb122ac607c1cf0
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index fdea1ba..cd9dd97 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -16,6 +16,8 @@
 
 package android.view;
 
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY;
+
 import android.Manifest;
 import android.animation.LayoutTransition;
 import android.app.ActivityManagerNative;
@@ -37,7 +39,6 @@
 import android.graphics.drawable.Drawable;
 import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManager.DisplayListener;
-import android.hardware.input.InputManager;
 import android.media.AudioManager;
 import android.os.Binder;
 import android.os.Build;
@@ -74,7 +75,6 @@
 import android.view.animation.Interpolator;
 import android.view.inputmethod.InputConnection;
 import android.view.inputmethod.InputMethodManager;
-import android.view.WindowCallbacks;
 import android.widget.Scroller;
 
 import com.android.internal.R;
@@ -176,6 +176,11 @@
 
     int mViewVisibility;
     boolean mAppVisible = true;
+    // For recents to freeform transition we need to keep drawing after the app receives information
+    // that it became invisible. This will ignore that information and depend on the decor view
+    // visibility to control drawing. The decor view visibility will get adjusted when the app get
+    // stopped and that's when the app will stop drawing further frames.
+    private boolean mForceDecorViewVisibility = false;
     int mOrigWindowType = -1;
 
     /** Whether the window had focus during the most recent traversal. */
@@ -561,6 +566,8 @@
                         & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
                     mInputChannel = new InputChannel();
                 }
+                mForceDecorViewVisibility = (mWindowAttributes.privateFlags
+                        & PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY) != 0;
                 try {
                     mOrigWindowType = mWindowAttributes.type;
                     mAttachInfo.mRecomputeGlobalAttributes = true;
@@ -1063,7 +1070,7 @@
     }
 
     int getHostVisibility() {
-        return mAppVisible ? mView.getVisibility() : View.GONE;
+        return (mAppVisible || mForceDecorViewVisibility) ? mView.getVisibility() : View.GONE;
     }
 
     /**
@@ -1568,7 +1575,7 @@
         boolean insetsPending = false;
         int relayoutResult = 0;
 
-        boolean isViewVisible = viewVisibility == View.VISIBLE;
+        final boolean isViewVisible = viewVisibility == View.VISIBLE;
         if (mFirst || windowShouldResize || insetsChanged ||
                 viewVisibilityChanged || params != null) {
 
@@ -2471,8 +2478,7 @@
                 if (callbacks != null) {
                     for (SurfaceHolder.Callback c : callbacks) {
                         if (c instanceof SurfaceHolder.Callback2) {
-                            ((SurfaceHolder.Callback2)c).surfaceRedrawNeeded(
-                                    mSurfaceHolder);
+                            ((SurfaceHolder.Callback2)c).surfaceRedrawNeeded(mSurfaceHolder);
                         }
                     }
                 }
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index edf4297..1521f2e 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -118,8 +118,7 @@
      */
     public void removeViewImmediate(View view);
 
-    public static class LayoutParams extends ViewGroup.LayoutParams
-            implements Parcelable {
+    public static class LayoutParams extends ViewGroup.LayoutParams implements Parcelable {
         /**
          * X position for this window.  With the default gravity it is ignored.
          * When using {@link Gravity#LEFT} or {@link Gravity#START} or {@link Gravity#RIGHT} or
@@ -1149,13 +1148,21 @@
 
         /**
          * Flag indicating that the x, y, width, and height members should be
-         * ignored (and thus their previous value preserved). For example 
+         * ignored (and thus their previous value preserved). For example
          * because they are being managed externally through repositionChild.
          *
          * {@hide}
          */
         public static final int PRIVATE_FLAG_PRESERVE_GEOMETRY = 0x00002000;
 
+        /**
+         * Flag that will make window ignore app visibility and instead depend purely on the decor
+         * view visibility for determining window visibility. This is used by recents to keep
+         * drawing after it launches an app.
+         * @hide
+         */
+        public static final int PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY = 0x00004000;
+
 
         /**
          * Control flags that are private to the platform.
diff --git a/packages/SystemUI/src/com/android/systemui/recents/ExitRecentsWindowFirstAnimationFrameEvent.java b/packages/SystemUI/src/com/android/systemui/recents/ExitRecentsWindowFirstAnimationFrameEvent.java
new file mode 100644
index 0000000..8ae8c53
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/ExitRecentsWindowFirstAnimationFrameEvent.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents;
+
+import com.android.systemui.recents.events.EventBus;
+
+/**
+ * Event sent when the exit animation is started.
+ *
+ * This is sent so parts of UI can synchronize on this event and adjust their appearance. An example
+ * of that is hiding the tasks when the launched application window becomes visible.
+ */
+public class ExitRecentsWindowFirstAnimationFrameEvent extends EventBus.Event {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index 3ae8827..1e46e6f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -37,6 +37,7 @@
 import android.view.View;
 import android.view.ViewStub;
 import android.view.ViewTreeObserver;
+import android.view.WindowManager;
 import com.android.internal.logging.MetricsLogger;
 import com.android.systemui.R;
 import com.android.systemui.recents.events.EventBus;
@@ -352,6 +353,8 @@
                 View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
         mEmptyViewStub = (ViewStub) findViewById(R.id.empty_view_stub);
         mScrimViews = new SystemBarScrimViews(this);
+        getWindow().getAttributes().privateFlags |=
+                WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY;
 
         // Create the home intent runnable
         Intent homeIntent = new Intent(Intent.ACTION_MAIN, null);
@@ -648,6 +651,11 @@
         mRecentsView.getViewTreeObserver().addOnPreDrawListener(this);
     }
 
+    public final void onBusEvent(ExitRecentsWindowFirstAnimationFrameEvent event) {
+        mRecentsView.setStackViewVisibility(View.INVISIBLE);
+        mRecentsView.getViewTreeObserver().addOnPreDrawListener(this);
+    }
+
     public final void onBusEvent(CancelEnterRecentsWindowAnimationEvent event) {
         RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
         int launchToTaskId = launchState.launchedToTaskId;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
index 50aa2f7..b491f94 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
@@ -68,8 +68,8 @@
  * An implementation of the Recents component for the current user.  For secondary users, this can
  * be called remotely from the system user.
  */
-public class RecentsImpl extends IRecentsNonSystemUserCallbacks.Stub
-        implements ActivityOptions.OnAnimationFinishedListener {
+public class RecentsImpl extends IRecentsNonSystemUserCallbacks.Stub implements
+        ActivityOptions.OnAnimationFinishedListener {
 
     private final static String TAG = "RecentsImpl";
     private final static boolean DEBUG = false;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
index 85b8fcf..b2f716d 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
@@ -27,12 +27,12 @@
 import android.os.IRemoteCallback;
 import android.os.RemoteException;
 import android.util.Log;
-import android.util.SparseArray;
 import android.view.AppTransitionAnimationSpec;
 import android.view.IAppTransitionAnimationSpecsFuture;
 import android.view.WindowManagerGlobal;
 import com.android.internal.annotations.GuardedBy;
 import com.android.systemui.recents.Constants;
+import com.android.systemui.recents.ExitRecentsWindowFirstAnimationFrameEvent;
 import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.events.EventBus;
 import com.android.systemui.recents.events.activity.CancelEnterRecentsWindowAnimationEvent;
@@ -106,6 +106,7 @@
                     // If we are launching into another task, cancel the previous task's
                     // window transition
                     EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(task));
+                    EventBus.getDefault().send(new ExitRecentsWindowFirstAnimationFrameEvent());
 
                     if (lockToTask) {
                         // Request screen pinning after the animation runs
@@ -116,7 +117,12 @@
         } else {
             // This is only the case if the task is not on screen (scrolled offscreen for example)
             transitionFuture = null;
-            animStartedListener = null;
+            animStartedListener = new ActivityOptions.OnAnimationStartedListener() {
+                @Override
+                public void onAnimationStarted() {
+                    EventBus.getDefault().send(new ExitRecentsWindowFirstAnimationFrameEvent());
+                }
+            };
         }
 
         if (taskView == null) {
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index 89f5658..943c9ed 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -139,7 +139,7 @@
     private static final long APP_TRANSITION_TIMEOUT_MS = 5000;
 
     private final Context mContext;
-    private final Handler mH;
+    private final WindowManagerService mService;
 
     private int mNextAppTransition = TRANSIT_UNSET;
 
@@ -208,15 +208,10 @@
 
     private final ArrayList<AppTransitionListener> mListeners = new ArrayList<>();
     private final ExecutorService mDefaultExecutor = Executors.newSingleThreadExecutor();
-    private final Object mServiceLock;
-    private final WindowSurfacePlacer mWindowSurfacePlacer;
 
-    AppTransition(Context context, Handler h, Object serviceLock,
-            WindowSurfacePlacer windowSurfacePlacer) {
+    AppTransition(Context context, WindowManagerService service) {
         mContext = context;
-        mH = h;
-        mServiceLock = serviceLock;
-        mWindowSurfacePlacer = windowSurfacePlacer;
+        mService = service;
         mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
                 com.android.internal.R.interpolator.linear_out_slow_in);
         mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator(context,
@@ -971,7 +966,7 @@
 
                 @Override
                 public void onAnimationEnd(Animation animation) {
-                    mH.obtainMessage(H.DO_ANIMATION_CALLBACK, callback).sendToTarget();
+                    mService.mH.obtainMessage(H.DO_ANIMATION_CALLBACK, callback).sendToTarget();
                 }
 
                 @Override
@@ -1326,7 +1321,8 @@
 
     void postAnimationCallback() {
         if (mNextAppTransitionCallback != null) {
-            mH.sendMessage(mH.obtainMessage(H.DO_ANIMATION_CALLBACK, mNextAppTransitionCallback));
+            mService.mH.sendMessage(mService.mH.obtainMessage(H.DO_ANIMATION_CALLBACK,
+                    mNextAppTransitionCallback));
             mNextAppTransitionCallback = null;
         }
     }
@@ -1478,14 +1474,15 @@
                     } catch (RemoteException e) {
                         Slog.w(TAG, "Failed to fetch app transition specs: " + e);
                     }
-                    synchronized (mServiceLock) {
+                    synchronized (mService.mWindowMap) {
                         mNextAppTransitionAnimationsSpecsPending = false;
                         overridePendingAppTransitionMultiThumb(specs,
                                 mNextAppTransitionFutureCallback, null /* finishedCallback */,
                                 mNextAppTransitionScaleUp);
                         mNextAppTransitionFutureCallback = null;
-                        mWindowSurfacePlacer.requestTraversal();
+                        mService.prolongAnimationsFromSpecs(specs, mNextAppTransitionScaleUp);
                     }
+                    mService.requestTraversal();
                 }
             });
         }
@@ -1672,8 +1669,8 @@
         }
         boolean prepared = prepare();
         if (isTransitionSet()) {
-            mH.removeMessages(H.APP_TRANSITION_TIMEOUT);
-            mH.sendEmptyMessageDelayed(H.APP_TRANSITION_TIMEOUT, APP_TRANSITION_TIMEOUT_MS);
+            mService.mH.removeMessages(H.APP_TRANSITION_TIMEOUT);
+            mService.mH.sendEmptyMessageDelayed(H.APP_TRANSITION_TIMEOUT, APP_TRANSITION_TIMEOUT_MS);
         }
         return prepared;
     }
diff --git a/services/core/java/com/android/server/wm/AppWindowAnimator.java b/services/core/java/com/android/server/wm/AppWindowAnimator.java
index 2905269..dfd01ef 100644
--- a/services/core/java/com/android/server/wm/AppWindowAnimator.java
+++ b/services/core/java/com/android/server/wm/AppWindowAnimator.java
@@ -37,6 +37,10 @@
 public class AppWindowAnimator {
     static final String TAG = "AppWindowAnimator";
 
+    private static final int PROLONG_ANIMATION_DISABLED = 0;
+    static final int PROLONG_ANIMATION_AT_END = 1;
+    static final int PROLONG_ANIMATION_AT_START = 2;
+
     final AppWindowToken mAppToken;
     final WindowManagerService mService;
     final WindowAnimator mAnimator;
@@ -85,7 +89,7 @@
     // If true when the animation hits the last frame, it will keep running on that last frame.
     // This is used to synchronize animation with Recents and we wait for Recents to tell us to
     // finish or for a new animation be set as fail-safe mechanism.
-    private boolean mProlongAnimation;
+    private int mProlongAnimation;
     // Whether the prolong animation can be removed when animation is set. The purpose of this is
     // that if recents doesn't tell us to remove the prolonged animation, we will get rid of it
     // when new animation is set.
@@ -142,7 +146,7 @@
             anim.setBackgroundColor(0);
         }
         if (mClearProlongedAnimation) {
-            mProlongAnimation = false;
+            mProlongAnimation = PROLONG_ANIMATION_DISABLED;
         } else {
             mClearProlongedAnimation = true;
         }
@@ -266,6 +270,10 @@
             return false;
         }
         transformation.clear();
+        if (mProlongAnimation == PROLONG_ANIMATION_AT_START) {
+            animation.setStartTime(currentTime);
+            currentTime += 1;
+        }
         boolean hasMoreFrames = animation.getTransformation(currentTime, transformation);
         if (!hasMoreFrames) {
             if (deferThumbnailDestruction && !deferFinalFrameCleanup) {
@@ -278,7 +286,7 @@
                         "Stepped animation in " + mAppToken + ": more=" + hasMoreFrames +
                         ", xform=" + transformation + ", mProlongAnimation=" + mProlongAnimation);
                 deferFinalFrameCleanup = false;
-                if (mProlongAnimation) {
+                if (mProlongAnimation == PROLONG_ANIMATION_AT_END) {
                     hasMoreFrames = true;
                 } else {
                     animation = null;
@@ -434,13 +442,13 @@
         }
     }
 
-    void startProlongAnimation() {
-        mProlongAnimation = true;
+    void startProlongAnimation(int prolongType) {
+        mProlongAnimation = prolongType;
         mClearProlongedAnimation = false;
     }
 
     void endProlongedAnimation() {
-        mProlongAnimation = false;
+        mProlongAnimation = PROLONG_ANIMATION_DISABLED;
     }
 
     // This is an animation that does nothing: it just immediately finishes
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index c359c53..a9bd71f 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -50,6 +50,9 @@
 import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED;
 import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
 
+import static com.android.server.wm.AppWindowAnimator.PROLONG_ANIMATION_AT_END;
+import static com.android.server.wm.AppWindowAnimator.PROLONG_ANIMATION_AT_START;
+
 import android.Manifest;
 import android.animation.ValueAnimator;
 import android.annotation.Nullable;
@@ -909,7 +912,7 @@
                 PowerManager.PARTIAL_WAKE_LOCK, "SCREEN_FROZEN");
         mScreenFrozenLock.setReferenceCounted(false);
 
-        mAppTransition = new AppTransition(context, mH, mWindowMap, mWindowPlacerLocked);
+        mAppTransition = new AppTransition(context, this);
         mAppTransition.registerListenerLocked(mActivityManagerAppTransitionNotifier);
 
         mActivityManager = ActivityManagerNative.getDefault();
@@ -3692,22 +3695,26 @@
         synchronized (mWindowMap) {
             mAppTransition.overridePendingAppTransitionMultiThumb(specs, onAnimationStartedCallback,
                     onAnimationFinishedCallback, scaleUp);
-            if (!scaleUp) {
-                // This is used by freeform to recents windows transition. We need to synchronize
-                // the animation with the appearance of the content of recents, so we will make
-                // animation stay on the last frame a little longer.
-                mTmpTaskIds.clear();
-                for (int i = specs.length - 1; i >= 0; i--) {
-                    mTmpTaskIds.put(specs[i].taskId, 0);
-                }
-                for (final WindowState win : mWindowMap.values()) {
-                    final Task task = win.getTask();
-                    if (task != null && mTmpTaskIds.get(task.mTaskId, -1) != -1) {
-                        final AppWindowToken appToken = win.mAppToken;
-                        if (appToken != null && appToken.mAppAnimator != null) {
-                            appToken.mAppAnimator.startProlongAnimation();
-                        }
-                    }
+            prolongAnimationsFromSpecs(specs, scaleUp);
+
+        }
+    }
+
+    void prolongAnimationsFromSpecs(AppTransitionAnimationSpec[] specs, boolean scaleUp) {
+        // This is used by freeform <-> recents windows transition. We need to synchronize
+        // the animation with the appearance of the content of recents, so we will make
+        // animation stay on the first or last frame a little longer.
+        mTmpTaskIds.clear();
+        for (int i = specs.length - 1; i >= 0; i--) {
+            mTmpTaskIds.put(specs[i].taskId, 0);
+        }
+        for (final WindowState win : mWindowMap.values()) {
+            final Task task = win.getTask();
+            if (task != null && mTmpTaskIds.get(task.mTaskId, -1) != -1) {
+                final AppWindowToken appToken = win.mAppToken;
+                if (appToken != null && appToken.mAppAnimator != null) {
+                    appToken.mAppAnimator.startProlongAnimation(scaleUp ?
+                            PROLONG_ANIMATION_AT_START : PROLONG_ANIMATION_AT_END);
                 }
             }
         }