Add support for cross-activity scenes and transitions

* Add theme attributes for specifying a top-level TransitionManager
  for an activity window.

* Add window feature for automatic content transitions. This
  automatically assigns/creates a Scene for setContentView calls.

* Add named transitions. This allows apps to define APIs for
  handshake-agreements about which exit/entrance transitions to play.

* Add new transition type for ActivityOptions. This lets the system
  use ActivityOptions to communicate transition specifics and
  arguments to the called activity.

* Have ActivityManager pass appropriate ActivityOptions through to the
  called Activity. Have the called activity call back into the caller
  to let it know which transition of a possible requested set was
  chosen.

Still to do:

* Define and pass arguments for transitions. This will require
  defining a Parcelable version of TransitionValues and deciding how
  much leeway apps should have for these things.

* Determine how to appropriately filter the ActivityOptions bundle so
  that only appropriate data reaches the target.

* Determine if generalizing the auto-Scenes functionality to
  ViewGroups is appropriate.

Change-Id: I10684b926129ab2fbc1adec9ef31767237acae79
diff --git a/api/current.txt b/api/current.txt
index 0a88f87..2ccce61 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -2830,6 +2830,8 @@
     method public java.lang.String getCallingPackage();
     method public int getChangingConfigurations();
     method public android.content.ComponentName getComponentName();
+    method public android.transition.Scene getContentScene();
+    method public android.transition.TransitionManager getContentTransitionManager();
     method public android.view.View getCurrentFocus();
     method public android.app.FragmentManager getFragmentManager();
     method public android.content.Intent getIntent();
@@ -2935,6 +2937,7 @@
     method public void reportFullyDrawn();
     method public final boolean requestWindowFeature(int);
     method public final void runOnUiThread(java.lang.Runnable);
+    method public void setContentTransitionManager(android.transition.TransitionManager);
     method public void setContentView(int);
     method public void setContentView(android.view.View);
     method public void setContentView(android.view.View, android.view.ViewGroup.LayoutParams);
@@ -26363,10 +26366,15 @@
     ctor public TransitionManager();
     method public static void beginDelayedTransition(android.view.ViewGroup);
     method public static void beginDelayedTransition(android.view.ViewGroup, android.transition.Transition);
+    method public android.transition.Transition getNamedTransition(java.lang.String, android.transition.Scene);
+    method public android.transition.Transition getNamedTransition(android.transition.Scene, java.lang.String);
+    method public java.lang.String[] getTargetSceneNames(android.transition.Scene);
     method public static void go(android.transition.Scene);
     method public static void go(android.transition.Scene, android.transition.Transition);
     method public void setTransition(android.transition.Scene, android.transition.Transition);
     method public void setTransition(android.transition.Scene, android.transition.Scene, android.transition.Transition);
+    method public void setTransition(android.transition.Scene, java.lang.String, android.transition.Transition);
+    method public void setTransition(java.lang.String, android.transition.Scene, android.transition.Transition);
     method public void transitionTo(android.transition.Scene);
   }
 
@@ -29289,6 +29297,7 @@
     method public final android.view.WindowManager.LayoutParams getAttributes();
     method public final android.view.Window.Callback getCallback();
     method public final android.view.Window getContainer();
+    method public android.transition.Scene getContentScene();
     method public final android.content.Context getContext();
     method public abstract android.view.View getCurrentFocus();
     method public abstract android.view.View getDecorView();
@@ -29296,6 +29305,7 @@
     method protected final int getForcedWindowFlags();
     method public abstract android.view.LayoutInflater getLayoutInflater();
     method protected final int getLocalFeatures();
+    method public android.transition.TransitionManager getTransitionManager();
     method public abstract int getVolumeControlStream();
     method public android.view.WindowManager getWindowManager();
     method public final android.content.res.TypedArray getWindowStyle();
@@ -29345,6 +29355,8 @@
     method public void setSoftInputMode(int);
     method public abstract void setTitle(java.lang.CharSequence);
     method public abstract deprecated void setTitleColor(int);
+    method public void setTransitionManager(android.transition.TransitionManager);
+    method public void setTransitionOptions(android.os.Bundle);
     method public void setType(int);
     method public void setUiOptions(int);
     method public void setUiOptions(int, int);
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index d34b05d..698f06a 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -17,6 +17,9 @@
 package android.app;
 
 import android.annotation.NonNull;
+import android.transition.Scene;
+import android.transition.Transition;
+import android.transition.TransitionManager;
 import android.util.ArrayMap;
 import android.util.SuperNotCalledException;
 import com.android.internal.app.ActionBarImpl;
@@ -1990,6 +1993,41 @@
     }
 
     /**
+     * Retrieve the {@link TransitionManager} responsible for default transitions in this window.
+     * Requires {@link Window#FEATURE_CONTENT_TRANSITIONS}.
+     *
+     * <p>This method will return non-null after content has been initialized (e.g. by using
+     * {@link #setContentView}) if {@link Window#FEATURE_CONTENT_TRANSITIONS} has been granted.</p>
+     *
+     * @return This window's content TransitionManager or null if none is set.
+     */
+    public TransitionManager getContentTransitionManager() {
+        return getWindow().getTransitionManager();
+    }
+
+    /**
+     * Set the {@link TransitionManager} to use for default transitions in this window.
+     * Requires {@link Window#FEATURE_CONTENT_TRANSITIONS}.
+     *
+     * @param tm The TransitionManager to use for scene changes.
+     */
+    public void setContentTransitionManager(TransitionManager tm) {
+        getWindow().setTransitionManager(tm);
+    }
+
+    /**
+     * Retrieve the {@link Scene} representing this window's current content.
+     * Requires {@link Window#FEATURE_CONTENT_TRANSITIONS}.
+     *
+     * <p>This method will return null if the current content is not represented by a Scene.</p>
+     *
+     * @return Current Scene being shown or null
+     */
+    public Scene getContentScene() {
+        return getWindow().getContentScene();
+    }
+
+    /**
      * Sets whether this activity is finished when touched outside its window's
      * bounds.
      */
@@ -3408,7 +3446,29 @@
      * @see #startActivity 
      */
     public void startActivityForResult(Intent intent, int requestCode) {
-        startActivityForResult(intent, requestCode, null);
+        final TransitionManager tm = getWindow().getTransitionManager();
+        final Scene currScene = getWindow().getContentScene();
+        final String[] targetSceneNames = currScene != null && tm != null ?
+                tm.getTargetSceneNames(currScene) : null;
+
+        if (targetSceneNames == null || targetSceneNames.length == 0) {
+            startActivityForResult(intent, requestCode, null);
+        } else {
+            // TODO Capture the scene transition args and send along
+            final ActivityOptions opts = ActivityOptions.makeSceneTransitionAnimation(
+                    targetSceneNames, null,
+                    new ActivityOptions.OnSceneTransitionStartedListener() {
+                        @Override public void onSceneTransitionStarted(String destSceneName) {
+                            final Transition t = tm.getNamedTransition(currScene, destSceneName);
+                            // TODO Fill this in to notify the outgoing activity that it should
+                            // treat this as a sync point for the transition - the target
+                            // transition has started.
+                            Log.d(TAG, "Scene transition to scene " + destSceneName +
+                                    " transition " + t);
+                        }
+                    }, mHandler);
+            startActivityForResult(intent, requestCode, opts.toBundle());
+        }
     }
 
     /**
@@ -5229,6 +5289,16 @@
             CharSequence title, Activity parent, String id,
             NonConfigurationInstances lastNonConfigurationInstances,
             Configuration config) {
+        attach(context, aThread, instr, token, ident, application, intent, info, title, parent, id,
+                lastNonConfigurationInstances, config, null);
+    }
+
+    final void attach(Context context, ActivityThread aThread,
+            Instrumentation instr, IBinder token, int ident,
+            Application application, Intent intent, ActivityInfo info,
+            CharSequence title, Activity parent, String id,
+            NonConfigurationInstances lastNonConfigurationInstances,
+            Configuration config, Bundle options) {
         attachBaseContext(context);
 
         mFragments.attachActivity(this, mContainer, null);
@@ -5265,6 +5335,7 @@
             mWindow.setContainer(mParent.getWindow());
         }
         mWindowManager = mWindow.getWindowManager();
+        mWindow.setTransitionOptions(options);
         mCurrentConfig = config;
     }
 
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 44f6859..582ce3c 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -22,6 +22,8 @@
 import android.os.Handler;
 import android.os.IRemoteCallback;
 import android.os.RemoteException;
+import android.text.TextUtils;
+import android.util.Log;
 import android.view.View;
 
 /**
@@ -30,6 +32,8 @@
  * Context.startActivity(Intent, Bundle)} and related methods.
  */
 public class ActivityOptions {
+    private static final String TAG = "ActivityOptions";
+
     /**
      * The package name that created the options.
      * @hide
@@ -481,8 +485,19 @@
     }
 
     /** @hide */
-    public IRemoteCallback getOnSceneTransitionStartedListener() {
-        return mSceneTransitionStartedListener;
+    public void dispatchSceneTransitionStarted(String destScene) {
+        if (mSceneTransitionStartedListener != null) {
+            Bundle data = null;
+            if (!TextUtils.isEmpty(destScene)) {
+                data = new Bundle();
+                data.putString(KEY_DEST_SCENE_NAME_CHOSEN, destScene);
+            }
+            try {
+                mSceneTransitionStartedListener.sendResult(data);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Caught exception dispatching scene transition start", e);
+            }
+        }
     }
 
     /** @hide */
@@ -493,6 +508,12 @@
             } catch (RemoteException e) {
             }
         }
+        if (mSceneTransitionStartedListener != null) {
+            try {
+                mSceneTransitionStartedListener.sendResult(null);
+            } catch (RemoteException e) {
+            }
+        }
     }
 
     /** @hide */
@@ -572,6 +593,8 @@
                 }
                 mSceneTransitionStartedListener = otherOptions.mSceneTransitionStartedListener;
                 mDestSceneNames = otherOptions.mDestSceneNames;
+                mTransitionArgs = otherOptions.mTransitionArgs;
+                mThumbnail = null;
                 mAnimationStartedListener = null;
                 break;
         }
@@ -595,7 +618,7 @@
                 b.putInt(KEY_ANIM_TYPE, mAnimationType);
                 b.putInt(KEY_ANIM_ENTER_RES_ID, mCustomEnterResId);
                 b.putInt(KEY_ANIM_EXIT_RES_ID, mCustomExitResId);
-                b.putIBinder(KEY_ANIM_START_LISTENER, mAnimationStartedListener
+                b.putBinder(KEY_ANIM_START_LISTENER, mAnimationStartedListener
                         != null ? mAnimationStartedListener.asBinder() : null);
                 break;
             case ANIM_SCALE_UP:
@@ -611,10 +634,31 @@
                 b.putParcelable(KEY_ANIM_THUMBNAIL, mThumbnail);
                 b.putInt(KEY_ANIM_START_X, mStartX);
                 b.putInt(KEY_ANIM_START_Y, mStartY);
-                b.putIBinder(KEY_ANIM_START_LISTENER, mAnimationStartedListener
+                b.putBinder(KEY_ANIM_START_LISTENER, mAnimationStartedListener
                         != null ? mAnimationStartedListener.asBinder() : null);
                 break;
+            case ANIM_SCENE_TRANSITION:
+                b.putInt(KEY_ANIM_TYPE, mAnimationType);
+                b.putStringArray(KEY_DEST_SCENE_NAMES, mDestSceneNames);
+                b.putBundle(KEY_SCENE_TRANSITION_ARGS, mTransitionArgs);
+                b.putBinder(KEY_SCENE_TRANSITION_START_LISTENER, mSceneTransitionStartedListener
+                        != null ? mSceneTransitionStartedListener.asBinder() : null);
+                break;
         }
         return b;
     }
+
+    /**
+     * Return the filtered options only meant to be seen by the target activity itself
+     * @hide
+     */
+    public ActivityOptions forTargetActivity() {
+        if (mAnimationType == ANIM_SCENE_TRANSITION) {
+            final ActivityOptions result = new ActivityOptions();
+            result.update(this);
+            return result;
+        }
+
+        return null;
+    }
 }
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 5e3dc02..e667bad 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -56,6 +56,7 @@
 import android.os.Environment;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.IRemoteCallback;
 import android.os.Looper;
 import android.os.Message;
 import android.os.MessageQueue;
@@ -68,12 +69,15 @@
 import android.os.SystemProperties;
 import android.os.Trace;
 import android.os.UserHandle;
+import android.transition.Scene;
+import android.transition.TransitionManager;
 import android.util.AndroidRuntimeException;
 import android.util.ArrayMap;
 import android.util.DisplayMetrics;
 import android.util.EventLog;
 import android.util.Log;
 import android.util.LogPrinter;
+import android.util.Pair;
 import android.util.PrintWriterPrinter;
 import android.util.Slog;
 import android.util.SuperNotCalledException;
@@ -284,6 +288,7 @@
         boolean isForward;
         int pendingConfigChanges;
         boolean onlyLocalRequest;
+        Bundle activityOptions;
 
         View mPendingRemoveWindow;
         WindowManager mPendingRemoveWindowManager;
@@ -576,9 +581,10 @@
         }
 
         public final void scheduleResumeActivity(IBinder token, int processState,
-                boolean isForward) {
+                boolean isForward, Bundle resumeArgs) {
             updateProcessState(processState, false);
-            sendMessage(H.RESUME_ACTIVITY, token, isForward ? 1 : 0);
+            sendMessage(H.RESUME_ACTIVITY, new Pair<IBinder, Bundle>(token, resumeArgs),
+                    isForward ? 1 : 0);
         }
 
         public final void scheduleSendResult(IBinder token, List<ResultInfo> results) {
@@ -594,7 +600,8 @@
                 ActivityInfo info, Configuration curConfig, CompatibilityInfo compatInfo,
                 int procState, Bundle state, List<ResultInfo> pendingResults,
                 List<Intent> pendingNewIntents, boolean notResumed, boolean isForward,
-                String profileName, ParcelFileDescriptor profileFd, boolean autoStopProfiler) {
+                String profileName, ParcelFileDescriptor profileFd, boolean autoStopProfiler,
+                Bundle resumeArgs) {
 
             updateProcessState(procState, false);
 
@@ -616,6 +623,7 @@
             r.profileFile = profileName;
             r.profileFd = profileFd;
             r.autoStopProfiler = autoStopProfiler;
+            r.activityOptions = resumeArgs;
 
             updatePendingConfiguration(curConfig);
 
@@ -1189,7 +1197,7 @@
             switch (msg.what) {
                 case LAUNCH_ACTIVITY: {
                     Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
-                    ActivityClientRecord r = (ActivityClientRecord)msg.obj;
+                    final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
 
                     r.packageInfo = getPackageInfoNoCheck(
                             r.activityInfo.applicationInfo, r.compatInfo);
@@ -1235,7 +1243,8 @@
                     break;
                 case RESUME_ACTIVITY:
                     Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityResume");
-                    handleResumeActivity((IBinder)msg.obj, true,
+                    final Pair<IBinder, Bundle> resumeArgs = (Pair<IBinder, Bundle>) msg.obj;
+                    handleResumeActivity(resumeArgs.first, resumeArgs.second, true,
                             msg.arg1 != 0, true);
                     Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                     break;
@@ -2032,7 +2041,7 @@
                     + ", comp=" + name
                     + ", token=" + token);
         }
-        return performLaunchActivity(r, null);
+        return performLaunchActivity(r, null, null);
     }
 
     public final Activity getActivity(IBinder token) {
@@ -2085,7 +2094,8 @@
         sendMessage(H.CLEAN_UP_CONTEXT, cci);
     }
 
-    private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
+    private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent,
+            Bundle options) {
         // System.out.println("##### [" + System.currentTimeMillis() + "] ActivityThread.performLaunchActivity(" + r + ")");
 
         ActivityInfo aInfo = r.activityInfo;
@@ -2143,7 +2153,7 @@
                         + r.activityInfo.name + " with config " + config);
                 activity.attach(appContext, this, getInstrumentation(), r.token,
                         r.ident, app, r.intent, r.activityInfo, title, r.parent,
-                        r.embeddedID, r.lastNonConfigurationInstances, config);
+                        r.embeddedID, r.lastNonConfigurationInstances, config, options);
 
                 if (customIntent != null) {
                     activity.mIntent = customIntent;
@@ -2242,12 +2252,13 @@
 
         if (localLOGV) Slog.v(
             TAG, "Handling launch of " + r);
-        Activity a = performLaunchActivity(r, customIntent);
+
+        Activity a = performLaunchActivity(r, customIntent, r.activityOptions);
 
         if (a != null) {
             r.createdConfig = new Configuration(mConfiguration);
             Bundle oldState = r.state;
-            handleResumeActivity(r.token, false, r.isForward,
+            handleResumeActivity(r.token, r.activityOptions, false, r.isForward,
                     !r.activity.mFinished && !r.startsNotResumed);
 
             if (!r.activity.mFinished && r.startsNotResumed) {
@@ -2808,12 +2819,13 @@
         r.mPendingRemoveWindowManager = null;
     }
 
-    final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward,
-            boolean reallyResume) {
+    final void handleResumeActivity(IBinder token, Bundle resumeArgs,
+            boolean clearHide, boolean isForward, boolean reallyResume) {
         // If we are getting ready to gc after going to the background, well
         // we are back active so skip it.
         unscheduleGcIdler();
 
+        // TODO Push resumeArgs into the activity for consideration
         ActivityClientRecord r = performResumeActivity(token, clearHide);
 
         if (r != null) {
@@ -3734,6 +3746,7 @@
             }
         }
         r.startsNotResumed = tmp.startsNotResumed;
+        r.activityOptions = null;
 
         handleLaunchActivity(r, currentIntent);
     }
diff --git a/core/java/android/app/ApplicationThreadNative.java b/core/java/android/app/ApplicationThreadNative.java
index 347d43f..c8f1280 100644
--- a/core/java/android/app/ApplicationThreadNative.java
+++ b/core/java/android/app/ApplicationThreadNative.java
@@ -113,7 +113,8 @@
             IBinder b = data.readStrongBinder();
             int procState = data.readInt();
             boolean isForward = data.readInt() != 0;
-            scheduleResumeActivity(b, procState, isForward);
+            Bundle resumeArgs = data.readBundle();
+            scheduleResumeActivity(b, procState, isForward, resumeArgs);
             return true;
         }
         
@@ -145,8 +146,10 @@
             ParcelFileDescriptor profileFd = data.readInt() != 0
                     ? ParcelFileDescriptor.CREATOR.createFromParcel(data) : null;
             boolean autoStopProfiler = data.readInt() != 0;
+            Bundle resumeArgs = data.readBundle();
             scheduleLaunchActivity(intent, b, ident, info, curConfig, compatInfo, procState, state,
-                    ri, pi, notResumed, isForward, profileName, profileFd, autoStopProfiler);
+                    ri, pi, notResumed, isForward, profileName, profileFd, autoStopProfiler,
+                    resumeArgs);
             return true;
         }
         
@@ -696,13 +699,15 @@
         data.recycle();
     }
 
-    public final void scheduleResumeActivity(IBinder token, int procState, boolean isForward)
+    public final void scheduleResumeActivity(IBinder token, int procState, boolean isForward,
+            Bundle resumeArgs)
             throws RemoteException {
         Parcel data = Parcel.obtain();
         data.writeInterfaceToken(IApplicationThread.descriptor);
         data.writeStrongBinder(token);
         data.writeInt(procState);
         data.writeInt(isForward ? 1 : 0);
+        data.writeBundle(resumeArgs);
         mRemote.transact(SCHEDULE_RESUME_ACTIVITY_TRANSACTION, data, null,
                 IBinder.FLAG_ONEWAY);
         data.recycle();
@@ -722,9 +727,10 @@
     public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
             ActivityInfo info, Configuration curConfig, CompatibilityInfo compatInfo,
             int procState, Bundle state, List<ResultInfo> pendingResults,
-    		List<Intent> pendingNewIntents, boolean notResumed, boolean isForward,
-    		String profileName, ParcelFileDescriptor profileFd, boolean autoStopProfiler)
-    		throws RemoteException {
+            List<Intent> pendingNewIntents, boolean notResumed, boolean isForward,
+            String profileName, ParcelFileDescriptor profileFd, boolean autoStopProfiler,
+            Bundle resumeArgs)
+            throws RemoteException {
         Parcel data = Parcel.obtain();
         data.writeInterfaceToken(IApplicationThread.descriptor);
         intent.writeToParcel(data, 0);
@@ -747,6 +753,7 @@
             data.writeInt(0);
         }
         data.writeInt(autoStopProfiler ? 1 : 0);
+        data.writeBundle(resumeArgs);
         mRemote.transact(SCHEDULE_LAUNCH_ACTIVITY_TRANSACTION, data, null,
                 IBinder.FLAG_ONEWAY);
         data.recycle();
diff --git a/core/java/android/app/IApplicationThread.java b/core/java/android/app/IApplicationThread.java
index d0cc1bb..1ea9d87 100644
--- a/core/java/android/app/IApplicationThread.java
+++ b/core/java/android/app/IApplicationThread.java
@@ -50,15 +50,16 @@
             int configChanges) throws RemoteException;
     void scheduleWindowVisibility(IBinder token, boolean showWindow) throws RemoteException;
     void scheduleSleeping(IBinder token, boolean sleeping) throws RemoteException;
-    void scheduleResumeActivity(IBinder token, int procState, boolean isForward)
+    void scheduleResumeActivity(IBinder token, int procState, boolean isForward, Bundle resumeArgs)
             throws RemoteException;
     void scheduleSendResult(IBinder token, List<ResultInfo> results) throws RemoteException;
     void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
             ActivityInfo info, Configuration curConfig, CompatibilityInfo compatInfo,
             int procState, Bundle state, List<ResultInfo> pendingResults,
-    		List<Intent> pendingNewIntents, boolean notResumed, boolean isForward,
-    		String profileName, ParcelFileDescriptor profileFd, boolean autoStopProfiler)
-    		throws RemoteException;
+            List<Intent> pendingNewIntents, boolean notResumed, boolean isForward,
+            String profileName, ParcelFileDescriptor profileFd, boolean autoStopProfiler,
+            Bundle resumeArgs)
+            throws RemoteException;
     void scheduleRelaunchActivity(IBinder token, List<ResultInfo> pendingResults,
             List<Intent> pendingNewIntents, int configChanges,
             boolean notResumed, Configuration config) throws RemoteException;
diff --git a/core/java/android/transition/TransitionInflater.java b/core/java/android/transition/TransitionInflater.java
index 9f77d5e..9fa554c 100644
--- a/core/java/android/transition/TransitionInflater.java
+++ b/core/java/android/transition/TransitionInflater.java
@@ -20,6 +20,7 @@
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.content.res.XmlResourceParser;
+import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.util.Xml;
 import android.view.InflateException;
@@ -291,19 +292,40 @@
         int toId = a.getResourceId(
                 com.android.internal.R.styleable.TransitionManager_toScene, -1);
         if (toId >= 0) toScene = Scene.getSceneForLayout(sceneRoot, toId, mContext);
+        String fromName = a.getString(
+                com.android.internal.R.styleable.TransitionManager_fromSceneName);
+        String toName = a.getString(
+                com.android.internal.R.styleable.TransitionManager_toSceneName);
         if (transitionId >= 0) {
             Transition transition = inflateTransition(transitionId);
             if (transition != null) {
                 if (fromScene != null) {
-                    if (toScene == null){
-                        throw new RuntimeException("No matching toScene for given fromScene " +
-                                "for transition ID " + transitionId);
-                    } else {
+                    boolean hasDest = false;
+                    if (toScene != null) {
                         transitionManager.setTransition(fromScene, toScene, transition);
+                        hasDest = true;
+                    }
+
+                    if (!TextUtils.isEmpty(toName)) {
+                        transitionManager.setTransition(fromScene, toName, transition);
+                        hasDest = true;
+                    }
+
+                    if (!hasDest) {
+                        throw new RuntimeException("No matching toScene or toSceneName for given " +
+                                "fromScene for transition ID " + transitionId);
                     }
                 } else if (toId >= 0) {
                     transitionManager.setTransition(toScene, transition);
                 }
+                if (fromName != null) {
+                    if (toScene != null) {
+                        transitionManager.setTransition(fromName, toScene, transition);
+                    } else {
+                        throw new RuntimeException("No matching toScene for given fromSceneName " +
+                                "for transition ID " + transitionId);
+                    }
+                }
             }
         }
         a.recycle();
diff --git a/core/java/android/transition/TransitionManager.java b/core/java/android/transition/TransitionManager.java
index 3bf6790..0106f7fb 100644
--- a/core/java/android/transition/TransitionManager.java
+++ b/core/java/android/transition/TransitionManager.java
@@ -67,9 +67,15 @@
 
     private static Transition sDefaultTransition = new AutoTransition();
 
+    private static final String[] EMPTY_STRINGS = new String[0];
+
     ArrayMap<Scene, Transition> mSceneTransitions = new ArrayMap<Scene, Transition>();
     ArrayMap<Scene, ArrayMap<Scene, Transition>> mScenePairTransitions =
             new ArrayMap<Scene, ArrayMap<Scene, Transition>>();
+    ArrayMap<Scene, ArrayMap<String, Transition>> mSceneNameTransitions =
+            new ArrayMap<Scene, ArrayMap<String, Transition>>();
+    ArrayMap<String, ArrayMap<Scene, Transition>> mNameSceneTransitions =
+            new ArrayMap<String, ArrayMap<Scene, Transition>>();
     private static ThreadLocal<WeakReference<ArrayMap<ViewGroup, ArrayList<Transition>>>>
             sRunningTransitions =
             new ThreadLocal<WeakReference<ArrayMap<ViewGroup, ArrayList<Transition>>>>();
@@ -218,6 +224,141 @@
     }
 
     /**
+     * Retrieve the transition from a named scene to a target defined scene if one has been
+     * associated with this TransitionManager.
+     *
+     * <p>A named scene is an indirect link for a transition. Fundamentally a named
+     * scene represents a potentially arbitrary intersection point of two otherwise independent
+     * transitions. Activity A may define a transition from scene X to "com.example.scene.FOO"
+     * while activity B may define a transition from scene "com.example.scene.FOO" to scene Y.
+     * In this way applications may define an API for more sophisticated transitions between
+     * caller and called activities very similar to the way that <code>Intent</code> extras
+     * define APIs for arguments and data propagation between activities.</p>
+     *
+     * @param fromName Named scene that this transition corresponds to
+     * @param toScene Target scene that this transition will move to
+     * @return Transition corresponding to the given fromName and toScene or null
+     *         if no association exists in this TransitionManager
+     *
+     * @see #setTransition(String, Scene, Transition)
+     */
+    public Transition getNamedTransition(String fromName, Scene toScene) {
+        ArrayMap<Scene, Transition> m = mNameSceneTransitions.get(fromName);
+        if (m != null) {
+            return m.get(toScene);
+        }
+        return null;
+    }
+
+    /**
+     * Retrieve the transition from a defined scene to a target named scene if one has been
+     * associated with this TransitionManager.
+     *
+     * <p>A named scene is an indirect link for a transition. Fundamentally a named
+     * scene represents a potentially arbitrary intersection point of two otherwise independent
+     * transitions. Activity A may define a transition from scene X to "com.example.scene.FOO"
+     * while activity B may define a transition from scene "com.example.scene.FOO" to scene Y.
+     * In this way applications may define an API for more sophisticated transitions between
+     * caller and called activities very similar to the way that <code>Intent</code> extras
+     * define APIs for arguments and data propagation between activities.</p>
+     *
+     * @param fromScene Scene that this transition starts from
+     * @param toName Name of the target scene
+     * @return Transition corresponding to the given fromScene and toName or null
+     *         if no association exists in this TransitionManager
+     */
+    public Transition getNamedTransition(Scene fromScene, String toName) {
+        ArrayMap<String, Transition> m = mSceneNameTransitions.get(fromScene);
+        if (m != null) {
+            return m.get(toName);
+        }
+        return null;
+    }
+
+    /**
+     * Retrieve the supported target named scenes when transitioning away from the given scene.
+     *
+     * <p>A named scene is an indirect link for a transition. Fundamentally a named
+     * scene represents a potentially arbitrary intersection point of two otherwise independent
+     * transitions. Activity A may define a transition from scene X to "com.example.scene.FOO"
+     * while activity B may define a transition from scene "com.example.scene.FOO" to scene Y.
+     * In this way applications may define an API for more sophisticated transitions between
+     * caller and called activities very similar to the way that <code>Intent</code> extras
+     * define APIs for arguments and data propagation between activities.</p>
+     *
+     * @param fromScene Scene to transition from
+     * @return An array of Strings naming each supported transition starting from
+     *         <code>fromScene</code>. If no transitions to a named scene from the given
+     *         scene are supported this function will return a String[] of length 0.
+     *
+     * @see #setTransition(Scene, String, Transition)
+     */
+    public String[] getTargetSceneNames(Scene fromScene) {
+        final ArrayMap<String, Transition> m = mSceneNameTransitions.get(fromScene);
+        if (m == null) {
+            return EMPTY_STRINGS;
+        }
+        final int count = m.size();
+        final String[] result = new String[count];
+        for (int i = 0; i < count; i++) {
+            result[i] = m.keyAt(i);
+        }
+        return result;
+    }
+
+    /**
+     * Set a transition from a specific scene to a named scene.
+     *
+     * <p>A named scene is an indirect link for a transition. Fundamentally a named
+     * scene represents a potentially arbitrary intersection point of two otherwise independent
+     * transitions. Activity A may define a transition from scene X to "com.example.scene.FOO"
+     * while activity B may define a transition from scene "com.example.scene.FOO" to scene Y.
+     * In this way applications may define an API for more sophisticated transitions between
+     * caller and called activities very similar to the way that <code>Intent</code> extras
+     * define APIs for arguments and data propagation between activities.</p>
+     *
+     * @param fromScene Scene to transition from
+     * @param toName Named scene to transition to
+     * @param transition Transition to use
+     *
+     * @see #getTargetSceneNames(Scene)
+     */
+    public void setTransition(Scene fromScene, String toName, Transition transition) {
+        ArrayMap<String, Transition> m = mSceneNameTransitions.get(fromScene);
+        if (m == null) {
+            m = new ArrayMap<String, Transition>();
+            mSceneNameTransitions.put(fromScene, m);
+        }
+        m.put(toName, transition);
+    }
+
+    /**
+     * Set a transition from a named scene to a concrete scene.
+     *
+     * <p>A named scene is an indirect link for a transition. Fundamentally a named
+     * scene represents a potentially arbitrary intersection point of two otherwise independent
+     * transitions. Activity A may define a transition from scene X to "com.example.scene.FOO"
+     * while activity B may define a transition from scene "com.example.scene.FOO" to scene Y.
+     * In this way applications may define an API for more sophisticated transitions between
+     * caller and called activities very similar to the way that <code>Intent</code> extras
+     * define APIs for arguments and data propagation between activities.</p>
+     *
+     * @param fromName Named scene to transition from
+     * @param toScene Scene to transition to
+     * @param transition Transition to use
+     *
+     * @see #getNamedTransition(String, Scene)
+     */
+    public void setTransition(String fromName, Scene toScene, Transition transition) {
+        ArrayMap<Scene, Transition> m = mNameSceneTransitions.get(fromName);
+        if (m == null) {
+            m = new ArrayMap<Scene, Transition>();
+            mNameSceneTransitions.put(fromName, m);
+        }
+        m.put(toScene, transition);
+    }
+
+    /**
      * This private utility class is used to listen for both OnPreDraw and
      * OnAttachStateChange events. OnPreDraw events are the main ones we care
      * about since that's what triggers the transition to take place.
@@ -329,7 +470,6 @@
         // Auto transition if there is no transition declared for the Scene, but there is
         // a root or parent view
         changeScene(scene, getTransition(scene));
-
     }
 
     /**
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index 11d8d36..2f62431 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -27,6 +27,8 @@
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.SystemProperties;
+import android.transition.Scene;
+import android.transition.TransitionManager;
 import android.view.accessibility.AccessibilityEvent;
 
 /**
@@ -1333,4 +1335,47 @@
      * @param event A key or touch event to inject to this window.
      */
     public void injectInputEvent(InputEvent event) { }
+
+    /**
+     * Retrieve the {@link TransitionManager} responsible for  for default transitions
+     * in this window. Requires {@link #FEATURE_CONTENT_TRANSITIONS}.
+     *
+     * <p>This method will return non-null after content has been initialized (e.g. by using
+     * {@link #setContentView}) if {@link #FEATURE_CONTENT_TRANSITIONS} has been granted.</p>
+     *
+     * @return This window's content TransitionManager or null if none is set.
+     */
+    public TransitionManager getTransitionManager() {
+        return null;
+    }
+
+    /**
+     * Set the {@link TransitionManager} to use for default transitions in this window.
+     * Requires {@link #FEATURE_CONTENT_TRANSITIONS}.
+     *
+     * @param tm The TransitionManager to use for scene changes.
+     */
+    public void setTransitionManager(TransitionManager tm) {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Retrieve the {@link Scene} representing this window's current content.
+     * Requires {@link #FEATURE_CONTENT_TRANSITIONS}.
+     *
+     * <p>This method will return null if the current content is not represented by a Scene.</p>
+     *
+     * @return Current Scene being shown or null
+     */
+    public Scene getContentScene() {
+        return null;
+    }
+
+    /**
+     * Set options that can affect the transition behavior within this window.
+     * @param options Options to set or null for none
+     */
+    public void setTransitionOptions(Bundle options) {
+        throw new UnsupportedOperationException();
+    }
 }
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindow.java b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
index 27a7caf..d0abe9c 100644
--- a/policy/src/com/android/internal/policy/impl/PhoneWindow.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
@@ -22,7 +22,9 @@
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
 import static android.view.WindowManager.LayoutParams.*;
 
+import android.app.ActivityOptions;
 import android.transition.Scene;
+import android.transition.Transition;
 import android.transition.TransitionInflater;
 import android.transition.TransitionManager;
 import android.view.ViewConfiguration;
@@ -148,6 +150,8 @@
     private PanelMenuPresenterCallback mPanelMenuPresenterCallback;
 
     private TransitionManager mTransitionManager;
+    private Scene mContentScene;
+    private Bundle mTransitionOptions;
 
     // The icon resource has been explicitly set elsewhere
     // and should not be overwritten with a default.
@@ -284,6 +288,26 @@
     }
 
     @Override
+    public TransitionManager getTransitionManager() {
+        return mTransitionManager;
+    }
+
+    @Override
+    public void setTransitionManager(TransitionManager tm) {
+        mTransitionManager = tm;
+    }
+
+    @Override
+    public Scene getContentScene() {
+        return mContentScene;
+    }
+
+    @Override
+    public void setTransitionOptions(Bundle options) {
+        mTransitionOptions = options;
+    }
+
+    @Override
     public void setContentView(int layoutResID) {
         // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
         // decor, when theme attributes and the like are crystalized. Do not check the feature
@@ -297,7 +321,7 @@
         if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
             final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                     getContext());
-            mTransitionManager.transitionTo(newScene);
+            transitionTo(newScene);
         } else {
             mLayoutInflater.inflate(layoutResID, mContentParent);
         }
@@ -326,7 +350,7 @@
         if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
             view.setLayoutParams(params);
             final Scene newScene = new Scene(mContentParent, view);
-            mTransitionManager.transitionTo(newScene);
+            transitionTo(newScene);
         } else {
             mContentParent.addView(view, params);
         }
@@ -353,6 +377,33 @@
         }
     }
 
+    private void transitionTo(Scene scene) {
+        Transition selected = null;
+        if (mTransitionOptions != null) {
+            final ActivityOptions opts = new ActivityOptions(mTransitionOptions);
+            mTransitionOptions = null;
+
+            String selectedName = null;
+            for (String sceneName : opts.getDestSceneNames()) {
+                final Transition t = mTransitionManager.getNamedTransition(sceneName, scene);
+                if (t != null) {
+                    // TODO handle args/state; inject into t/clone with params
+                    selected = t;
+                    selectedName = sceneName;
+                    break;
+                }
+            }
+            opts.dispatchSceneTransitionStarted(selectedName);
+        }
+
+        if (selected != null) {
+            TransitionManager.go(scene, selected);
+        } else {
+            mTransitionManager.transitionTo(scene);
+        }
+        mContentScene = scene;
+    }
+
     @Override
     public View getCurrentFocus() {
         return mDecor != null ? mDecor.findFocus() : null;
diff --git a/services/java/com/android/server/am/ActivityRecord.java b/services/java/com/android/server/am/ActivityRecord.java
index 5776181..8e85914 100644
--- a/services/java/com/android/server/am/ActivityRecord.java
+++ b/services/java/com/android/server/am/ActivityRecord.java
@@ -723,6 +723,10 @@
         }
     }
 
+    ActivityOptions getOptionsForTargetActivityLocked() {
+        return pendingOptions != null ? pendingOptions.forTargetActivity() : null;
+    }
+
     void clearOptionsLocked() {
         if (pendingOptions != null) {
             pendingOptions.abort();
diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java
index cd2cb331..2e914aa 100644
--- a/services/java/com/android/server/am/ActivityStack.java
+++ b/services/java/com/android/server/am/ActivityStack.java
@@ -36,6 +36,8 @@
 import static com.android.server.am.ActivityStackSupervisor.DEBUG_STATES;
 import static com.android.server.am.ActivityStackSupervisor.HOME_STACK_ID;
 
+import android.os.Trace;
+import android.util.Log;
 import com.android.internal.os.BatteryStatsImpl;
 import com.android.internal.util.Objects;
 import com.android.server.Watchdog;
@@ -1078,7 +1080,7 @@
                                 mWindowManager.setAppVisibility(r.appToken, true);
                             }
                             if (r != starting) {
-                                mStackSupervisor.startSpecificActivityLocked(r, false, false);
+                                mStackSupervisor.startSpecificActivityLocked(r, false, false, null);
                             }
                         }
 
@@ -1511,7 +1513,13 @@
                 mWindowManager.prepareAppTransition(AppTransition.TRANSIT_ACTIVITY_OPEN, false);
             }
         }
+
+        Bundle resumeAnimOptions = null;
         if (anim) {
+            ActivityOptions opts = next.getOptionsForTargetActivityLocked();
+            if (opts != null) {
+                resumeAnimOptions = opts.toBundle();
+            }
             next.applyOptionsLocked();
         } else {
             next.clearOptionsLocked();
@@ -1604,7 +1612,7 @@
                 next.app.pendingUiClean = true;
                 next.app.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_TOP);
                 next.app.thread.scheduleResumeActivity(next.appToken, next.app.repProcState,
-                        mService.isNextTransitionForward());
+                        mService.isNextTransitionForward(), resumeAnimOptions);
 
                 mStackSupervisor.checkReadyForSleepLocked();
 
@@ -1628,7 +1636,7 @@
                             next.nonLocalizedLabel, next.labelRes, next.icon, next.logo,
                             next.windowFlags, null, true);
                 }
-                mStackSupervisor.startSpecificActivityLocked(next, true, false);
+                mStackSupervisor.startSpecificActivityLocked(next, true, false, resumeAnimOptions);
                 if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked();
                 return true;
             }
@@ -1666,7 +1674,7 @@
                 if (DEBUG_SWITCH) Slog.v(TAG, "Restarting: " + next);
             }
             if (DEBUG_STATES) Slog.d(TAG, "resumeTopActivityLocked: Restarting " + next);
-            mStackSupervisor.startSpecificActivityLocked(next, true, true);
+            mStackSupervisor.startSpecificActivityLocked(next, true, true, resumeAnimOptions);
         }
 
         if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked();
diff --git a/services/java/com/android/server/am/ActivityStackSupervisor.java b/services/java/com/android/server/am/ActivityStackSupervisor.java
index f4ca324..04617af 100644
--- a/services/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/java/com/android/server/am/ActivityStackSupervisor.java
@@ -379,7 +379,7 @@
                 if (hr.app == null && app.uid == hr.info.applicationInfo.uid
                         && processName.equals(hr.processName)) {
                     try {
-                        if (realStartActivityLocked(hr, app, true, true)) {
+                        if (realStartActivityLocked(hr, app, true, true, null)) {
                             didSomething = true;
                         }
                     } catch (Exception e) {
@@ -868,7 +868,7 @@
     }
 
     final boolean realStartActivityLocked(ActivityRecord r,
-            ProcessRecord app, boolean andResume, boolean checkConfig)
+            ProcessRecord app, boolean andResume, boolean checkConfig, Bundle resumeArgs)
             throws RemoteException {
 
         r.startFreezingScreenLocked(app, 0);
@@ -960,13 +960,14 @@
                     }
                 }
             }
+
             app.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_TOP);
             app.thread.scheduleLaunchActivity(new Intent(r.intent), r.appToken,
                     System.identityHashCode(r), r.info,
                     new Configuration(mService.mConfiguration), r.compat,
                     app.repProcState, r.icicle, results, newIntents, !andResume,
                     mService.isNextTransitionForward(), profileFile, profileFd,
-                    profileAutoStop);
+                    profileAutoStop, resumeArgs);
 
             if ((app.info.flags&ApplicationInfo.FLAG_CANT_SAVE_STATE) != 0) {
                 // This may be a heavy-weight process!  Note that the package
@@ -1040,7 +1041,7 @@
     }
 
     void startSpecificActivityLocked(ActivityRecord r,
-            boolean andResume, boolean checkConfig) {
+            boolean andResume, boolean checkConfig, Bundle resumeArgs) {
         // Is this activity's application already running?
         ProcessRecord app = mService.getProcessRecordLocked(r.processName,
                 r.info.applicationInfo.uid, true);
@@ -1057,7 +1058,7 @@
                     // separate apk in the process.
                     app.addPackage(r.info.packageName, mService.mProcessStats);
                 }
-                realStartActivityLocked(r, app, andResume, checkConfig);
+                realStartActivityLocked(r, app, andResume, checkConfig, resumeArgs);
                 return;
             } catch (RemoteException e) {
                 Slog.w(TAG, "Exception when starting activity "