Merge "DO NOT MERGE Allow enter activity transitions to be delayed until data is ready." into lmp-preview-dev
diff --git a/api/current.txt b/api/current.txt
index 8953d7e..94be851 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -3335,6 +3335,7 @@
     method public void openContextMenu(android.view.View);
     method public void openOptionsMenu();
     method public void overridePendingTransition(int, int);
+    method public void postponeEnterTransition();
     method public void recreate();
     method public void registerForContextMenu(android.view.View);
     method public final deprecated void removeDialog(int);
@@ -3390,6 +3391,7 @@
     method public deprecated void startManagingCursor(android.database.Cursor);
     method public boolean startNextMatchingActivity(android.content.Intent);
     method public boolean startNextMatchingActivity(android.content.Intent, android.os.Bundle);
+    method public void startPostponedEnterTransition();
     method public void startSearch(java.lang.String, boolean, android.os.Bundle, boolean);
     method public void stopLockTask();
     method public deprecated void stopManagingCursor(android.database.Cursor);
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 946555f..f6883e2 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -5616,6 +5616,34 @@
         mExitTransitionListener = listener;
     }
 
+    /**
+     * Postpone the entering activity transition when Activity was started with
+     * {@link android.app.ActivityOptions#makeSceneTransitionAnimation(Activity,
+     * android.util.Pair[])}.
+     * <p>This method gives the Activity the ability to delay starting the entering and
+     * shared element transitions until all data is loaded. Until then, the Activity won't
+     * draw into its window, leaving the window transparent. This may also cause the
+     * returning animation to be delayed until data is ready. This method should be
+     * called in {@link #onCreate(android.os.Bundle)} or in
+     * {@link #onActivityReenter(int, android.content.Intent)}.
+     * {@link #startPostponedEnterTransition()} must be called to allow the Activity to
+     * start the transitions. If the Activity did not use
+     * {@link android.app.ActivityOptions#makeSceneTransitionAnimation(Activity,
+     * android.util.Pair[])}, then this method does nothing.</p>
+     */
+    public void postponeEnterTransition() {
+        mActivityTransitionState.postponeEnterTransition();
+    }
+
+    /**
+     * Begin postponed transitions after {@link #postponeEnterTransition()} was called.
+     * If postponeEnterTransition() was called, you must call startPostponedEnterTransition()
+     * to have your Activity start drawing.
+     */
+    public void startPostponedEnterTransition() {
+        mActivityTransitionState.startPostponedEnterTransition();
+    }
+
     // ------------------ Internal API ------------------
     
     final void setParent(Activity parent) {
diff --git a/core/java/android/app/ActivityTransitionCoordinator.java b/core/java/android/app/ActivityTransitionCoordinator.java
index b658597..703df51 100644
--- a/core/java/android/app/ActivityTransitionCoordinator.java
+++ b/core/java/android/app/ActivityTransitionCoordinator.java
@@ -214,11 +214,21 @@
             ArrayList<String> allSharedElementNames,
             ArrayList<String> accepted, ArrayList<String> localNames,
             SharedElementListener listener, boolean isReturning) {
+        this(window, allSharedElementNames, listener, isReturning);
+        viewsReady(accepted, localNames);
+    }
+
+    public ActivityTransitionCoordinator(Window window,
+            ArrayList<String> allSharedElementNames,
+            SharedElementListener listener, boolean isReturning) {
         super(new Handler());
         mWindow = window;
         mListener = listener;
         mAllSharedElementNames = allSharedElementNames;
         mIsReturning = isReturning;
+    }
+
+    protected void viewsReady(ArrayList<String> accepted, ArrayList<String> localNames) {
         setSharedElements(accepted, localNames);
         if (getViewsTransition() != null) {
             getDecor().captureTransitioningViews(mTransitioningViews);
@@ -274,6 +284,8 @@
         return names;
     }
 
+    public ArrayList<String> getAllSharedElementNames() { return mAllSharedElementNames; }
+
     public static void setViewVisibility(Collection<View> views, int visibility) {
         if (views != null) {
             for (View view : views) {
diff --git a/core/java/android/app/ActivityTransitionState.java b/core/java/android/app/ActivityTransitionState.java
index b32e9ad..d94dadda 100644
--- a/core/java/android/app/ActivityTransitionState.java
+++ b/core/java/android/app/ActivityTransitionState.java
@@ -87,6 +87,11 @@
      */
     private boolean mHasExited;
 
+    /**
+     * Postpone painting and starting the enter transition until this is false.
+     */
+    private boolean mIsEnterPostponed;
+
     public ActivityTransitionState() {
     }
 
@@ -140,15 +145,38 @@
         if (mEnterActivityOptions.isReturning()) {
             restoreExitedViews();
             activity.getWindow().getDecorView().setVisibility(View.VISIBLE);
-            mEnterTransitionCoordinator = new EnterTransitionCoordinator(activity,
-                    resultReceiver, sharedElementNames, mExitingFrom, mExitingTo);
+        }
+        mEnterTransitionCoordinator = new EnterTransitionCoordinator(activity,
+                resultReceiver, sharedElementNames, mEnterActivityOptions.isReturning());
+
+        if (!mIsEnterPostponed) {
+            startEnter();
+        }
+    }
+
+    public void postponeEnterTransition() {
+        mIsEnterPostponed = true;
+    }
+
+    public void startPostponedEnterTransition() {
+        if (mIsEnterPostponed) {
+            mIsEnterPostponed = false;
+            if (mEnterTransitionCoordinator != null) {
+                startEnter();
+            }
+        }
+    }
+
+    private void startEnter() {
+        if (mEnterActivityOptions.isReturning()) {
+            mEnterTransitionCoordinator.viewsReady(mExitingFrom, mExitingTo);
         } else {
-            mEnterTransitionCoordinator = new EnterTransitionCoordinator(activity,
-                    resultReceiver, sharedElementNames, null, null);
-            mEnteringNames = sharedElementNames;
+            mEnterTransitionCoordinator.viewsReady(null, null);
+            mEnteringNames = mEnterTransitionCoordinator.getAllSharedElementNames();
             mEnteringFrom = mEnterTransitionCoordinator.getAcceptedNames();
             mEnteringTo = mEnterTransitionCoordinator.getMappedNames();
         }
+
         mExitingFrom = null;
         mExitingTo = null;
         mEnterActivityOptions = null;
diff --git a/core/java/android/app/EnterTransitionCoordinator.java b/core/java/android/app/EnterTransitionCoordinator.java
index 4b052e7..779e3de 100644
--- a/core/java/android/app/EnterTransitionCoordinator.java
+++ b/core/java/android/app/EnterTransitionCoordinator.java
@@ -53,18 +53,37 @@
     private boolean mIsCanceled;
     private ObjectAnimator mBackgroundAnimator;
     private boolean mIsExitTransitionComplete;
+    private boolean mIsReadyForTransition;
+    private Bundle mSharedElementsBundle;
 
     public EnterTransitionCoordinator(Activity activity, ResultReceiver resultReceiver,
-            ArrayList<String> sharedElementNames,
-            ArrayList<String> acceptedNames, ArrayList<String> mappedNames) {
-        super(activity.getWindow(), sharedElementNames, acceptedNames, mappedNames,
-                getListener(activity, acceptedNames), acceptedNames != null);
+            ArrayList<String> sharedElementNames, boolean isReturning) {
+        super(activity.getWindow(), sharedElementNames,
+                getListener(activity, isReturning), isReturning);
         mActivity = activity;
         setResultReceiver(resultReceiver);
         prepareEnter();
         Bundle resultReceiverBundle = new Bundle();
         resultReceiverBundle.putParcelable(KEY_REMOTE_RECEIVER, this);
         mResultReceiver.send(MSG_SET_REMOTE_RECEIVER, resultReceiverBundle);
+        getDecor().getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
+            @Override
+            public boolean onPreDraw() {
+                if (mIsReadyForTransition) {
+                    getDecor().getViewTreeObserver().removeOnPreDrawListener(this);
+                }
+                return mIsReadyForTransition;
+            }
+        });
+    }
+
+    public void viewsReady(ArrayList<String> accepted, ArrayList<String> localNames) {
+        if (mIsReadyForTransition) {
+            return;
+        }
+        super.viewsReady(accepted, localNames);
+
+        mIsReadyForTransition = true;
         if (mIsReturning) {
             mHandler = new Handler() {
                 @Override
@@ -75,6 +94,13 @@
             mHandler.sendEmptyMessageDelayed(MSG_CANCEL, MAX_WAIT_MS);
             send(MSG_SEND_SHARED_ELEMENT_DESTINATION, null);
         }
+        setViewVisibility(mSharedElements, View.INVISIBLE);
+        if (getViewsTransition() != null) {
+            setViewVisibility(mTransitioningViews, View.INVISIBLE);
+        }
+        if (mSharedElementsBundle != null) {
+            onTakeSharedElements();
+        }
     }
 
     private void sendSharedElementDestination() {
@@ -94,9 +120,7 @@
         }
     }
 
-    private static SharedElementListener getListener(Activity activity,
-            ArrayList<String> acceptedNames) {
-        boolean isReturning = acceptedNames != null;
+    private static SharedElementListener getListener(Activity activity, boolean isReturning) {
         return isReturning ? activity.mExitTransitionListener : activity.mEnterTransitionListener;
     }
 
@@ -108,7 +132,8 @@
                     if (mHandler != null) {
                         mHandler.removeMessages(MSG_CANCEL);
                     }
-                    onTakeSharedElements(resultData);
+                    mSharedElementsBundle = resultData;
+                    onTakeSharedElements();
                 }
                 break;
             case MSG_EXIT_TRANSITION_COMPLETE:
@@ -139,7 +164,7 @@
             mSharedElementNames.clear();
             mSharedElements.clear();
             mAllSharedElementNames.clear();
-            onTakeSharedElements(null);
+            startSharedElementTransition(null);
             onRemoteExitTransitionComplete();
         }
     }
@@ -149,10 +174,6 @@
     }
 
     protected void prepareEnter() {
-        setViewVisibility(mSharedElements, View.INVISIBLE);
-        if (getViewsTransition() != null) {
-            setViewVisibility(mTransitioningViews, View.INVISIBLE);
-        }
         mActivity.overridePendingTransition(0, 0);
         if (!mIsReturning) {
             mActivity.convertToTranslucent(null, null);
@@ -185,7 +206,25 @@
         }
     }
 
-    protected void onTakeSharedElements(Bundle sharedElementState) {
+    protected void onTakeSharedElements() {
+        if (!mIsReadyForTransition || mSharedElementsBundle == null) {
+            return;
+        }
+        final Bundle sharedElementState = mSharedElementsBundle;
+        mSharedElementsBundle = null;
+        getDecor().getViewTreeObserver()
+                .addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
+                    @Override
+                    public boolean onPreDraw() {
+                        getDecor().getViewTreeObserver().removeOnPreDrawListener(this);
+                        startSharedElementTransition(sharedElementState);
+                        return false;
+                    }
+                });
+        getDecor().invalidate();
+    }
+
+    private void startSharedElementTransition(Bundle sharedElementState) {
         setEpicenter();
         // Remove rejected shared elements
         ArrayList<String> rejectedNames = new ArrayList<String>(mAllSharedElementNames);
@@ -299,8 +338,8 @@
     }
 
     public void stop() {
+        makeOpaque();
         mHasStopped = true;
-        mActivity = null;
         mIsCanceled = true;
         mResultReceiver = null;
         if (mBackgroundAnimator != null) {
@@ -310,7 +349,7 @@
     }
 
     private void makeOpaque() {
-        if (!mHasStopped) {
+        if (!mHasStopped && mActivity != null) {
             mActivity.convertFromTranslucent();
             mActivity = null;
         }