Add dynamic scene creation/transition capability

Add TransitionManager.beginDelayedTransition() to handle starting a transition
on the next frame for a given scene root based on all changes that
take place between the first call to that method and the next animation frame.

Issue #9321937 Transitions: consider batching up multiple scene actions

Change-Id: I3fc92b6b4ec5ff42b1e678bcfd385703e32eba2a
diff --git a/api/current.txt b/api/current.txt
index 7907e56..b063fa7 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -28512,6 +28512,7 @@
 
   public class TransitionManager {
     ctor public TransitionManager();
+    method public static void beginDelayedTransition(android.view.ViewGroup, android.view.transition.Transition);
     method public android.view.transition.Transition getDefaultTransition();
     method public static void go(android.view.transition.Scene);
     method public static void go(android.view.transition.Scene, android.view.transition.Transition);
diff --git a/core/java/android/view/transition/TransitionManager.java b/core/java/android/view/transition/TransitionManager.java
index 4971a92..8088f6b 100644
--- a/core/java/android/view/transition/TransitionManager.java
+++ b/core/java/android/view/transition/TransitionManager.java
@@ -19,6 +19,8 @@
 import android.view.ViewGroup;
 import android.view.ViewTreeObserver;
 
+import java.util.ArrayList;
+
 /**
  * This class manages the set of transitions that fire when there is a
  * change of {@link Scene}. To use the manager, add scenes along with
@@ -41,8 +43,10 @@
             new ArrayMap<Scene, ArrayMap<Scene, Transition>>();
     static ArrayMap<ViewGroup, Transition> sRunningTransitions =
             new ArrayMap<ViewGroup, Transition>();
+    private static ArrayList<ViewGroup> sPendingTransitions = new ArrayList<ViewGroup>();
 
-        /**
+
+    /**
      * Sets the transition to be used for any scene change for which no
      * other transition is explicitly set. The initial value is
      * an {@link AutoTransition} instance.
@@ -142,24 +146,15 @@
 
         final ViewGroup sceneRoot = scene.getSceneRoot();
 
-        Transition runningTransition = sRunningTransitions.get(sceneRoot);
-        if (runningTransition != null) {
-            runningTransition.cancelTransition();
-        }
-
-        // Capture current values
-        if (transition != null) {
-            transition.captureValues(sceneRoot, true);
-        }
-
-        // Notify previous scene that it is being exited
-        Scene previousScene = sceneRoot.getCurrentScene();
-        if (previousScene != null) {
-            previousScene.exit();
-        }
+        sceneChangeSetup(sceneRoot, transition);
 
         scene.enter();
 
+        sceneChangeRunTransition(sceneRoot, transition);
+    }
+
+    private static void sceneChangeRunTransition(final ViewGroup sceneRoot,
+            final Transition transition) {
         if (transition != null) {
             final ViewTreeObserver observer = sceneRoot.getViewTreeObserver();
             observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@@ -181,6 +176,25 @@
         }
     }
 
+    private static void sceneChangeSetup(ViewGroup sceneRoot, Transition transition) {
+
+        Transition runningTransition = sRunningTransitions.get(sceneRoot);
+        if (runningTransition != null) {
+            runningTransition.cancelTransition();
+        }
+
+        // Capture current values
+        if (transition != null) {
+            transition.captureValues(sceneRoot, true);
+        }
+
+        // Notify previous scene that it is being exited
+        Scene previousScene = sceneRoot.getCurrentScene();
+        if (previousScene != null) {
+            previousScene.exit();
+        }
+    }
+
     /**
      * Change to the given scene, using the
      * appropriate transition for this particular scene change
@@ -272,4 +286,47 @@
         scene.setEnterAction(action);
         changeScene(scene, transition);
     }
+
+    /**
+     * Static utility method to animate to a new scene defined by all changes within
+     * the given scene root between calling this method and the next rendering frame.
+     * Calling this method causes TransitionManager to capture current values in the
+     * scene root and then post a request to run a transition on the next frame.
+     * At that time, the new values in the scene root will be captured and changes
+     * will be animated. There is no need to create a Scene; it is implied by
+     * changes which take place between calling this method and the next frame when
+     * the transition begins.
+     *
+     * <p>Calling this method several times before the next frame (for example, if
+     * unrelated code also wants to make dynamic changes and run a transition on
+     * the same scene root), only the first call will trigger capturing values
+     * and exiting the current scene. Subsequent calls to the method with the
+     * same scene root during the same frame will be ignored.</p>
+     *
+     * <p>Passing in <code>null</code> for the transition parameter will
+     * cause the TransitionManager to use its default transition.</p>
+     *
+     * @param sceneRoot The root of the View hierarchy to run the transition on.
+     * @param transition The transition to use for this change. A
+     * value of null causes the TransitionManager to use the default transition.
+     */
+    public static void beginDelayedTransition(final ViewGroup sceneRoot, Transition transition) {
+
+        if (!sPendingTransitions.contains(sceneRoot)) {
+            sPendingTransitions.add(sceneRoot);
+            if (transition == null) {
+                transition = sDefaultTransition;
+            }
+            final Transition finalTransition = transition;
+            sceneChangeSetup(sceneRoot, transition);
+            sceneRoot.setCurrentScene(null);
+            sceneRoot.postOnAnimation(new Runnable() {
+                @Override
+                public void run() {
+                    sPendingTransitions.remove(sceneRoot);
+                    sceneChangeRunTransition(sceneRoot, finalTransition);
+                }
+            });
+        }
+    }
 }
diff --git a/tests/TransitionTests/AndroidManifest.xml b/tests/TransitionTests/AndroidManifest.xml
index be6b145..98174ab 100644
--- a/tests/TransitionTests/AndroidManifest.xml
+++ b/tests/TransitionTests/AndroidManifest.xml
@@ -212,6 +212,13 @@
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity>
+        <activity android:label="DelayedTransition"
+                  android:name="DelayedTransition">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
 
     </application>
 
diff --git a/tests/TransitionTests/res/layout/two_buttons.xml b/tests/TransitionTests/res/layout/two_buttons.xml
new file mode 100644
index 0000000..23d59f8
--- /dev/null
+++ b/tests/TransitionTests/res/layout/two_buttons.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:orientation="vertical"
+              android:layout_width="fill_parent"
+              android:layout_height="fill_parent"
+              android:id="@+id/container">
+    <Button
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:id="@+id/button1"/>
+    <Button
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:id="@+id/button2"/>
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/TransitionTests/src/com/android/transitiontests/DelayedTransition.java b/tests/TransitionTests/src/com/android/transitiontests/DelayedTransition.java
new file mode 100644
index 0000000..f05ea78
--- /dev/null
+++ b/tests/TransitionTests/src/com/android/transitiontests/DelayedTransition.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2013 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.transitiontests;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.transition.TransitionManager;
+import android.widget.Button;
+import android.widget.LinearLayout;
+import static android.widget.LinearLayout.LayoutParams;
+
+public class DelayedTransition extends Activity {
+
+    private static final int SEARCH_SCREEN = 0;
+    private static final int RESULTS_SCREEN = 1;
+    ViewGroup mSceneRoot;
+    static int mCurrentScene;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.two_buttons);
+
+        final Button button1 = (Button) findViewById(R.id.button1);
+        final Button button2 = (Button) findViewById(R.id.button2);
+        final LinearLayout container = (LinearLayout) findViewById(R.id.container);
+        button1.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                int buttonWidth = button1.getWidth();
+                int containerWidth = container.getWidth();
+                if (buttonWidth < containerWidth) {
+                    TransitionManager.beginDelayedTransition(container, null);
+                    button1.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT,
+                            LayoutParams.WRAP_CONTENT));
+                    TransitionManager.beginDelayedTransition(container, null);
+                    button2.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,
+                            LayoutParams.MATCH_PARENT));
+                } else {
+                    TransitionManager.beginDelayedTransition(container, null);
+                    button1.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,
+                            LayoutParams.WRAP_CONTENT));
+                    TransitionManager.beginDelayedTransition(container, null);
+                    button2.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,
+                            LayoutParams.WRAP_CONTENT));
+                }
+            }
+        });
+    }
+
+}