Merge "Make FrameInfoVisualizer use an IRenderPipeline to draw"
diff --git a/api/current.txt b/api/current.txt
index a3d8131..564653a 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -4489,6 +4489,7 @@
     method public void onTrimMemory(int);
     method public void onViewCreated(android.view.View, android.os.Bundle);
     method public void onViewStateRestored(android.os.Bundle);
+    method public void postponeEnterTransition();
     method public void registerForContextMenu(android.view.View);
     method public final void requestPermissions(java.lang.String[], int);
     method public void setAllowEnterTransitionOverlap(boolean);
@@ -4514,6 +4515,7 @@
     method public void startActivityForResult(android.content.Intent, int);
     method public void startActivityForResult(android.content.Intent, int, android.os.Bundle);
     method public void startIntentSenderForResult(android.content.IntentSender, int, android.content.Intent, int, int, int, android.os.Bundle) throws android.content.IntentSender.SendIntentException;
+    method public void startPostponedEnterTransition();
     method public void unregisterForContextMenu(android.view.View);
   }
 
@@ -44025,8 +44027,8 @@
     field public static final int ROTATION_ANIMATION_CHANGED = 4096; // 0x1000
     field public static final int ROTATION_ANIMATION_CROSSFADE = 1; // 0x1
     field public static final int ROTATION_ANIMATION_JUMPCUT = 2; // 0x2
-    field public static final int ROTATION_ANIMATION_SEAMLESS = 3; // 0x3
     field public static final int ROTATION_ANIMATION_ROTATE = 0; // 0x0
+    field public static final int ROTATION_ANIMATION_SEAMLESS = 3; // 0x3
     field public static final int SCREEN_BRIGHTNESS_CHANGED = 2048; // 0x800
     field public static final int SCREEN_ORIENTATION_CHANGED = 1024; // 0x400
     field public static final int SOFT_INPUT_ADJUST_NOTHING = 48; // 0x30
diff --git a/api/system-current.txt b/api/system-current.txt
index b2f72fc..fd51a6a 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -103,7 +103,6 @@
     field public static final java.lang.String GET_ACCOUNTS = "android.permission.GET_ACCOUNTS";
     field public static final java.lang.String GET_ACCOUNTS_PRIVILEGED = "android.permission.GET_ACCOUNTS_PRIVILEGED";
     field public static final java.lang.String GET_APP_OPS_STATS = "android.permission.GET_APP_OPS_STATS";
-    field public static final java.lang.String GET_PACKAGE_IMPORTANCE = "android.permission.GET_PACKAGE_IMPORTANCE";
     field public static final java.lang.String GET_PACKAGE_SIZE = "android.permission.GET_PACKAGE_SIZE";
     field public static final java.lang.String GET_PROCESS_STATE_AND_OOM_SCORE = "android.permission.GET_PROCESS_STATE_AND_OOM_SCORE";
     field public static final deprecated java.lang.String GET_TASKS = "android.permission.GET_TASKS";
@@ -4634,6 +4633,7 @@
     method public void onTrimMemory(int);
     method public void onViewCreated(android.view.View, android.os.Bundle);
     method public void onViewStateRestored(android.os.Bundle);
+    method public void postponeEnterTransition();
     method public void registerForContextMenu(android.view.View);
     method public final void requestPermissions(java.lang.String[], int);
     method public void setAllowEnterTransitionOverlap(boolean);
@@ -4659,6 +4659,7 @@
     method public void startActivityForResult(android.content.Intent, int);
     method public void startActivityForResult(android.content.Intent, int, android.os.Bundle);
     method public void startIntentSenderForResult(android.content.IntentSender, int, android.content.Intent, int, int, int, android.os.Bundle) throws android.content.IntentSender.SendIntentException;
+    method public void startPostponedEnterTransition();
     method public void unregisterForContextMenu(android.view.View);
   }
 
@@ -47207,8 +47208,8 @@
     field public static final int ROTATION_ANIMATION_CHANGED = 4096; // 0x1000
     field public static final int ROTATION_ANIMATION_CROSSFADE = 1; // 0x1
     field public static final int ROTATION_ANIMATION_JUMPCUT = 2; // 0x2
-    field public static final int ROTATION_ANIMATION_SEAMLESS = 3; // 0x3
     field public static final int ROTATION_ANIMATION_ROTATE = 0; // 0x0
+    field public static final int ROTATION_ANIMATION_SEAMLESS = 3; // 0x3
     field public static final int SCREEN_BRIGHTNESS_CHANGED = 2048; // 0x800
     field public static final int SCREEN_ORIENTATION_CHANGED = 1024; // 0x400
     field public static final int SOFT_INPUT_ADJUST_NOTHING = 48; // 0x30
diff --git a/api/test-current.txt b/api/test-current.txt
index 22fd955..150fbfe 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -4492,6 +4492,7 @@
     method public void onTrimMemory(int);
     method public void onViewCreated(android.view.View, android.os.Bundle);
     method public void onViewStateRestored(android.os.Bundle);
+    method public void postponeEnterTransition();
     method public void registerForContextMenu(android.view.View);
     method public final void requestPermissions(java.lang.String[], int);
     method public void setAllowEnterTransitionOverlap(boolean);
@@ -4517,6 +4518,7 @@
     method public void startActivityForResult(android.content.Intent, int);
     method public void startActivityForResult(android.content.Intent, int, android.os.Bundle);
     method public void startIntentSenderForResult(android.content.IntentSender, int, android.content.Intent, int, int, int, android.os.Bundle) throws android.content.IntentSender.SendIntentException;
+    method public void startPostponedEnterTransition();
     method public void unregisterForContextMenu(android.view.View);
   }
 
@@ -44261,8 +44263,8 @@
     field public static final int ROTATION_ANIMATION_CHANGED = 4096; // 0x1000
     field public static final int ROTATION_ANIMATION_CROSSFADE = 1; // 0x1
     field public static final int ROTATION_ANIMATION_JUMPCUT = 2; // 0x2
-    field public static final int ROTATION_ANIMATION_SEAMLESS = 3; // 0x3
     field public static final int ROTATION_ANIMATION_ROTATE = 0; // 0x0
+    field public static final int ROTATION_ANIMATION_SEAMLESS = 3; // 0x3
     field public static final int SCREEN_BRIGHTNESS_CHANGED = 2048; // 0x800
     field public static final int SCREEN_ORIENTATION_CHANGED = 1024; // 0x400
     field public static final int SOFT_INPUT_ADJUST_NOTHING = 48; // 0x30
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 0d9be5f..4066f1c 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -3045,6 +3045,7 @@
      * @hide
      */
     @SystemApi
+    @RequiresPermission(Manifest.permission.PACKAGE_USAGE_STATS)
     public int getPackageImportance(String packageName) {
         try {
             int procState = ActivityManagerNative.getDefault().getPackageProcessState(packageName,
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 4de5b42..4df1325 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -1235,6 +1235,16 @@
             return true;
         }
 
+        case UPDATE_DISPLAY_OVERRIDE_CONFIGURATION_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            final Configuration config = Configuration.CREATOR.createFromParcel(data);
+            final int displayId = data.readInt();
+            final boolean updated = updateDisplayOverrideConfiguration(config, displayId);
+            reply.writeNoException();
+            reply.writeInt(updated ? 1 : 0);
+            return true;
+        }
+
         case SET_REQUESTED_ORIENTATION_TRANSACTION: {
             data.enforceInterface(IActivityManager.descriptor);
             IBinder token = data.readStrongBinder();
@@ -4608,8 +4618,7 @@
         data.recycle();
         return res;
     }
-    public boolean updateConfiguration(Configuration values) throws RemoteException
-    {
+    public boolean updateConfiguration(Configuration values) throws RemoteException {
         Parcel data = Parcel.obtain();
         Parcel reply = Parcel.obtain();
         data.writeInterfaceToken(IActivityManager.descriptor);
@@ -4621,6 +4630,20 @@
         reply.recycle();
         return updated;
     }
+    public boolean updateDisplayOverrideConfiguration(Configuration values, int displayId)
+            throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        values.writeToParcel(data, 0);
+        data.writeInt(displayId);
+        mRemote.transact(UPDATE_DISPLAY_OVERRIDE_CONFIGURATION_TRANSACTION, data, reply, 0);
+        reply.readException();
+        boolean updated = reply.readInt() == 1;
+        data.recycle();
+        reply.recycle();
+        return updated;
+    }
     public void setRequestedOrientation(IBinder token, int requestedOrientation)
             throws RemoteException {
         Parcel data = Parcel.obtain();
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index fbbfec3..e9a200f 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -978,6 +978,10 @@
             sendMessage(H.DUMP_HEAP, dhd, managed ? 1 : 0, 0, true /*async*/);
         }
 
+        public void attachAgent(String agent) {
+            sendMessage(H.ATTACH_AGENT, agent);
+        }
+
         public void setSchedulingGroup(int group) {
             // Note: do this immediately, since going into the foreground
             // should happen regardless of what pending work we have to do
@@ -1429,6 +1433,7 @@
         public static final int MULTI_WINDOW_MODE_CHANGED = 152;
         public static final int PICTURE_IN_PICTURE_MODE_CHANGED = 153;
         public static final int LOCAL_VOICE_INTERACTION_STARTED = 154;
+        public static final int ATTACH_AGENT = 155;
 
         String codeToString(int code) {
             if (DEBUG_MESSAGES) {
@@ -1485,6 +1490,7 @@
                     case MULTI_WINDOW_MODE_CHANGED: return "MULTI_WINDOW_MODE_CHANGED";
                     case PICTURE_IN_PICTURE_MODE_CHANGED: return "PICTURE_IN_PICTURE_MODE_CHANGED";
                     case LOCAL_VOICE_INTERACTION_STARTED: return "LOCAL_VOICE_INTERACTION_STARTED";
+                    case ATTACH_AGENT: return "ATTACH_AGENT";
                 }
             }
             return Integer.toString(code);
@@ -1739,6 +1745,8 @@
                 case LOCAL_VOICE_INTERACTION_STARTED:
                     handleLocalVoiceInteractionStarted((IBinder) ((SomeArgs) msg.obj).arg1,
                             (IVoiceInteractor) ((SomeArgs) msg.obj).arg2);
+                case ATTACH_AGENT:
+                    handleAttachAgent((String) msg.obj);
                     break;
             }
             Object obj = msg.obj;
@@ -3008,6 +3016,14 @@
         }
     }
 
+    static final void handleAttachAgent(String agent) {
+        try {
+            VMDebug.attachAgent(agent);
+        } catch (IOException e) {
+            Slog.e(TAG, "Attaching agent failed: " + agent);
+        }
+    }
+
     private static final ThreadLocal<Intent> sCurrentBroadcastIntent = new ThreadLocal<Intent>();
 
     /**
diff --git a/core/java/android/app/BackStackRecord.java b/core/java/android/app/BackStackRecord.java
index c589466..cf794c5 100644
--- a/core/java/android/app/BackStackRecord.java
+++ b/core/java/android/app/BackStackRecord.java
@@ -16,22 +16,13 @@
 
 package android.app;
 
-import android.content.Context;
-import android.graphics.Rect;
 import android.os.Build;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
-import android.transition.Transition;
-import android.transition.TransitionManager;
-import android.transition.TransitionSet;
-import android.util.ArrayMap;
 import android.util.Log;
 import android.util.LogWriter;
-import android.util.SparseArray;
 import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewTreeObserver;
 
 import com.android.internal.util.FastPrintWriter;
 
@@ -39,7 +30,6 @@
 import java.io.PrintWriter;
 import java.lang.reflect.Modifier;
 import java.util.ArrayList;
-import java.util.List;
 
 final class BackStackState implements Parcelable {
     final int[] mOps;
@@ -669,7 +659,7 @@
     }
 
     /**
-     * Implementation of {@link FragmentManagerImpl.android.app.FragmentManagerImpl.OpGenerator}.
+     * Implementation of {@link android.app.FragmentManagerImpl.OpGenerator}.
      * This operation is added to the list of pending actions during {@link #commit()}, and
      * will be executed on the UI thread to run this FragmentTransaction.
      *
@@ -691,6 +681,43 @@
         return true;
     }
 
+    boolean interactsWith(int containerId) {
+        final int numOps = mOps.size();
+        for (int opNum = 0; opNum < numOps; opNum++) {
+            final Op op = mOps.get(opNum);
+            if (op.fragment.mContainerId == containerId) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    boolean interactsWith(ArrayList<BackStackRecord> records, int startIndex, int endIndex) {
+        if (endIndex == startIndex) {
+            return false;
+        }
+        final int numOps = mOps.size();
+        int lastContainer = -1;
+        for (int opNum = 0; opNum < numOps; opNum++) {
+            final Op op = mOps.get(opNum);
+            final int container = op.fragment.mContainerId;
+            if (container != 0 && container != lastContainer) {
+                lastContainer = container;
+                for (int i = startIndex; i < endIndex; i++) {
+                    BackStackRecord record = records.get(i);
+                    final int numThoseOps = record.mOps.size();
+                    for (int thoseOpIndex = 0; thoseOpIndex < numThoseOps; thoseOpIndex++) {
+                        final Op thatOp = record.mOps.get(thoseOpIndex);
+                        if (thatOp.fragment.mContainerId == container) {
+                            return true;
+                        }
+                    }
+                }
+            }
+        }
+        return false;
+    }
+
     /**
      * Executes the operations contained within this transaction. The Fragment states will only
      * be modified if optimizations are not allowed.
@@ -700,31 +727,30 @@
         for (int opNum = 0; opNum < numOps; opNum++) {
             final Op op = mOps.get(opNum);
             final Fragment f = op.fragment;
-            f.mNextTransition = mTransition;
-            f.mNextTransitionStyle = mTransitionStyle;
+            f.setNextTransition(mTransition, mTransitionStyle);
             switch (op.cmd) {
                 case OP_ADD:
-                    f.mNextAnim = op.enterAnim;
+                    f.setNextAnim(op.enterAnim);
                     mManager.addFragment(f, false);
                     break;
                 case OP_REMOVE:
-                    f.mNextAnim = op.exitAnim;
+                    f.setNextAnim(op.exitAnim);
                     mManager.removeFragment(f);
                     break;
                 case OP_HIDE:
-                    f.mNextAnim = op.exitAnim;
+                    f.setNextAnim(op.exitAnim);
                     mManager.hideFragment(f);
                     break;
                 case OP_SHOW:
-                    f.mNextAnim = op.enterAnim;
+                    f.setNextAnim(op.enterAnim);
                     mManager.showFragment(f);
                     break;
                 case OP_DETACH:
-                    f.mNextAnim = op.exitAnim;
+                    f.setNextAnim(op.exitAnim);
                     mManager.detachFragment(f);
                     break;
                 case OP_ATTACH:
-                    f.mNextAnim = op.enterAnim;
+                    f.setNextAnim(op.enterAnim);
                     mManager.attachFragment(f);
                     break;
                 default:
@@ -748,31 +774,30 @@
         for (int opNum = mOps.size() - 1; opNum >= 0; opNum--) {
             final Op op = mOps.get(opNum);
             Fragment f = op.fragment;
-            f.mNextTransition = FragmentManagerImpl.reverseTransit(mTransition);
-            f.mNextTransitionStyle = mTransitionStyle;
+            f.setNextTransition(FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle);
             switch (op.cmd) {
                 case OP_ADD:
-                    f.mNextAnim = op.popExitAnim;
+                    f.setNextAnim(op.popExitAnim);
                     mManager.removeFragment(f);
                     break;
                 case OP_REMOVE:
-                    f.mNextAnim = op.popEnterAnim;
+                    f.setNextAnim(op.popEnterAnim);
                     mManager.addFragment(f, false);
                     break;
                 case OP_HIDE:
-                    f.mNextAnim = op.popEnterAnim;
+                    f.setNextAnim(op.popEnterAnim);
                     mManager.showFragment(f);
                     break;
                 case OP_SHOW:
-                    f.mNextAnim = op.popExitAnim;
+                    f.setNextAnim(op.popExitAnim);
                     mManager.hideFragment(f);
                     break;
                 case OP_DETACH:
-                    f.mNextAnim = op.popEnterAnim;
+                    f.setNextAnim(op.popEnterAnim);
                     mManager.attachFragment(f);
                     break;
                 case OP_ATTACH:
-                    f.mNextAnim = op.popExitAnim;
+                    f.setNextAnim(op.popExitAnim);
                     mManager.detachFragment(f);
                     break;
                 default:
@@ -803,7 +828,7 @@
                 case OP_ADD:
                 case OP_ATTACH:
                     added.add(op.fragment);
-                break;
+                    break;
                 case OP_REMOVE:
                 case OP_DETACH:
                     added.remove(op.fragment);
@@ -844,846 +869,29 @@
         }
     }
 
-    private static void setFirstOut(SparseArray<FragmentContainerTransition> transitioningFragments,
-                            Fragment fragment, boolean isPop) {
-        if (fragment != null) {
-            int containerId = fragment.mContainerId;
-            if (containerId != 0 && !fragment.isHidden()) {
-                FragmentContainerTransition fragments = transitioningFragments.get(containerId);
-                if (fragment.isAdded() && fragment.getView() != null && (fragments == null ||
-                        fragments.firstOut == null)) {
-                    if (fragments == null) {
-                        fragments = new FragmentContainerTransition();
-                        transitioningFragments.put(containerId, fragments);
-                    }
-                    fragments.firstOut = fragment;
-                    fragments.firstOutIsPop = isPop;
-                }
-                if (fragments != null && fragments.lastIn == fragment) {
-                    fragments.lastIn = null;
-                }
-            }
-        }
-    }
-
-    private void setLastIn(SparseArray<FragmentContainerTransition> transitioningFragments,
-            Fragment fragment, boolean isPop) {
-        if (fragment != null) {
-            int containerId = fragment.mContainerId;
-            if (containerId != 0) {
-                FragmentContainerTransition fragments = transitioningFragments.get(containerId);
-                if (!fragment.isAdded()) {
-                    if (fragments == null) {
-                        fragments = new FragmentContainerTransition();
-                        transitioningFragments.put(containerId, fragments);
-                    }
-                    fragments.lastIn = fragment;
-                    fragments.lastInIsPop = isPop;
-                }
-                if (fragments != null && fragments.firstOut == fragment) {
-                    fragments.firstOut = null;
-                }
-            }
-
-            /**
-             * Ensure that fragments that are entering are at least at the CREATED state
-             * so that they may load Transitions using TransitionInflater.
-             */
-            if (fragment.mState < Fragment.CREATED && mManager.mCurState >= Fragment.CREATED &&
-                    mManager.mHost.getContext().getApplicationInfo().targetSdkVersion >=
-                            Build.VERSION_CODES.N && !mAllowOptimization) {
-                mManager.makeActive(fragment);
-                mManager.moveToState(fragment, Fragment.CREATED, 0, 0, false);
-            }
-        }
-    }
-
-    /**
-     * Finds the first removed fragment and last added fragments when going forward.
-     * If none of the fragments have transitions, then both lists will be empty.
-     *
-     * @param transitioningFragments Keyed on the container ID, the first fragments to be removed,
-     *                               and last fragments to be added. This will be modified by
-     *                               this method.
-     */
-    public void calculateFragments(
-            SparseArray<FragmentContainerTransition> transitioningFragments) {
-        if (!mManager.mContainer.onHasView()) {
-            return; // nothing to see, so no transitions
-        }
-        final int numOps = mOps.size();
-        for (int opNum = 0; opNum < numOps; opNum++) {
+    boolean isPostponed() {
+        for (int opNum = 0; opNum < mOps.size(); opNum++) {
             final Op op = mOps.get(opNum);
-            switch (op.cmd) {
-                case OP_ADD:
-                case OP_SHOW:
-                case OP_ATTACH:
-                    setLastIn(transitioningFragments, op.fragment, false);
-                    break;
-                case OP_REMOVE:
-                case OP_HIDE:
-                case OP_DETACH:
-                    setFirstOut(transitioningFragments, op.fragment, false);
-                    break;
-            }
-        }
-    }
-
-    /**
-     * Finds the first removed fragment and last added fragments when popping the back stack.
-     * If none of the fragments have transitions, then both lists will be empty.
-     *
-     * @param transitioningFragments Keyed on the container ID, the first fragments to be removed,
-     *                               and last fragments to be added. This will be modified by
-     *                               this method.
-     */
-    public void calculatePopFragments(
-            SparseArray<FragmentContainerTransition> transitioningFragments) {
-        if (!mManager.mContainer.onHasView()) {
-            return; // nothing to see, so no transitions
-        }
-        final int numOps = mOps.size();
-        for (int opNum = numOps - 1; opNum >= 0; opNum--) {
-            final Op op = mOps.get(opNum);
-            switch (op.cmd) {
-                case OP_ADD:
-                case OP_SHOW:
-                case OP_ATTACH:
-                    setFirstOut(transitioningFragments, op.fragment, true);
-                    break;
-                case OP_REMOVE:
-                case OP_HIDE:
-                case OP_DETACH:
-                    setLastIn(transitioningFragments, op.fragment, true);
-                    break;
-            }
-        }
-    }
-
-    /**
-     * When custom fragment transitions are used, this sets up the state for each transition
-     * and begins the transition. A different transition is started for each fragment container
-     * and consists of up to 3 different transitions: the exit transition, a shared element
-     * transition and an enter transition.
-     *
-     * <p>The exit transition operates against the leaf nodes of the first fragment
-     * with a view that was removed. If no such fragment was removed, then no exit
-     * transition is executed. The exit transition comes from the outgoing fragment.</p>
-     *
-     * <p>The enter transition operates against the last fragment that was added. If
-     * that fragment does not have a view or no fragment was added, then no enter
-     * transition is executed. The enter transition comes from the incoming fragment.</p>
-     *
-     * <p>The shared element transition operates against all views and comes either
-     * from the outgoing fragment or the incoming fragment, depending on whether this
-     * is going forward or popping the back stack. When going forward, the incoming
-     * fragment's enter shared element transition is used, but when going back, the
-     * outgoing fragment's return shared element transition is used. Shared element
-     * transitions only operate if there is both an incoming and outgoing fragment.</p>
-     *
-     * @param containers The first in and last out fragments that are transitioning.
-     * @return The TransitionState used to complete the operation of the transition
-     * in {@link #setNameOverrides(android.app.BackStackRecord.TransitionState, java.util.ArrayList,
-     * java.util.ArrayList)}.
-     */
-    TransitionState beginTransition(SparseArray<FragmentContainerTransition> containers) {
-        TransitionState state = new TransitionState();
-
-        // Adding a non-existent target view makes sure that the transitions don't target
-        // any views by default. They'll only target the views we tell add. If we don't
-        // add any, then no views will be targeted.
-        state.nonExistentView = new View(mManager.mHost.getContext());
-
-        final int numContainers = containers.size();
-        for (int i = 0; i < numContainers; i++) {
-            int containerId = containers.keyAt(i);
-            FragmentContainerTransition containerTransition = containers.valueAt(i);
-            configureTransitions(containerId, state, containerTransition);
-        }
-        return state;
-    }
-
-    private static Transition cloneTransition(Transition transition) {
-        if (transition != null) {
-            transition = transition.clone();
-        }
-        return transition;
-    }
-
-    private static Transition getEnterTransition(Fragment inFragment, boolean isPop) {
-        if (inFragment == null) {
-            return null;
-        }
-        return cloneTransition(isPop ? inFragment.getReenterTransition() :
-                inFragment.getEnterTransition());
-    }
-
-    private static Transition getExitTransition(Fragment outFragment, boolean isPop) {
-        if (outFragment == null) {
-            return null;
-        }
-        return cloneTransition(isPop ? outFragment.getReturnTransition() :
-                outFragment.getExitTransition());
-    }
-
-    private static TransitionSet getSharedElementTransition(Fragment inFragment,
-            Fragment outFragment, boolean isPop) {
-        if (inFragment == null || outFragment == null) {
-            return null;
-        }
-        Transition transition = cloneTransition(isPop
-                ? outFragment.getSharedElementReturnTransition()
-                : inFragment.getSharedElementEnterTransition());
-        if (transition == null) {
-            return null;
-        }
-        TransitionSet transitionSet = new TransitionSet();
-        transitionSet.addTransition(transition);
-        return transitionSet;
-    }
-
-    private static ArrayList<View> captureExitingViews(Transition exitTransition,
-            Fragment outFragment, ArrayMap<String, View> namedViews, View nonExistentView) {
-        ArrayList<View> viewList = null;
-        if (exitTransition != null) {
-            viewList = new ArrayList<View>();
-            View root = outFragment.getView();
-            root.captureTransitioningViews(viewList);
-            if (namedViews != null) {
-                viewList.removeAll(namedViews.values());
-            }
-            if (!viewList.isEmpty()) {
-                viewList.add(nonExistentView);
-                addTargets(exitTransition, viewList);
-            }
-        }
-        return viewList;
-    }
-
-    private ArrayMap<String, View> remapSharedElements(TransitionState state, Fragment outFragment,
-            boolean isPop) {
-        ArrayMap<String, View> namedViews = new ArrayMap<String, View>();
-        if (mSharedElementSourceNames != null) {
-            outFragment.getView().findNamedViews(namedViews);
-            if (isPop) {
-                namedViews.retainAll(mSharedElementTargetNames);
-            } else {
-                namedViews = remapNames(mSharedElementSourceNames, mSharedElementTargetNames,
-                        namedViews);
-            }
-        }
-
-        if (isPop) {
-            outFragment.mEnterTransitionCallback.onMapSharedElements(
-                    mSharedElementTargetNames, namedViews);
-            setBackNameOverrides(state, namedViews, false);
-        } else {
-            outFragment.mExitTransitionCallback.onMapSharedElements(
-                    mSharedElementTargetNames, namedViews);
-            setNameOverrides(state, namedViews, false);
-        }
-
-        return namedViews;
-    }
-
-    /**
-     * Prepares the enter transition by adding a non-existent view to the transition's target list
-     * and setting it epicenter callback. By adding a non-existent view to the target list,
-     * we can prevent any view from being targeted at the beginning of the transition.
-     * We will add to the views before the end state of the transition is captured so that the
-     * views will appear. At the start of the transition, we clear the list of targets so that
-     * we can restore the state of the transition and use it again.
-     *
-     * <p>The shared element transition maps its shared elements immediately prior to
-     * capturing the final state of the Transition.</p>
-     */
-    private ArrayList<View> addTransitionTargets(final TransitionState state,
-            final Transition enterTransition, final TransitionSet sharedElementTransition,
-            final Transition exitTransition, final Transition overallTransition,
-            final View container, final Fragment inFragment, final Fragment outFragment,
-            final ArrayList<View> hiddenFragmentViews, final boolean isPop,
-            final ArrayList<View> sharedElementTargets) {
-        if (enterTransition == null && sharedElementTransition == null &&
-                overallTransition == null) {
-            return null;
-        }
-        final ArrayList<View> enteringViews = new ArrayList<View>();
-        container.getViewTreeObserver().addOnPreDrawListener(
-                new ViewTreeObserver.OnPreDrawListener() {
-                    @Override
-                    public boolean onPreDraw() {
-                        container.getViewTreeObserver().removeOnPreDrawListener(this);
-
-                        // Don't include any newly-hidden fragments in the transition.
-                        if (inFragment != null) {
-                            excludeHiddenFragments(hiddenFragmentViews, inFragment.mContainerId,
-                                    overallTransition);
-                        }
-
-                        ArrayMap<String, View> namedViews = null;
-                        if (sharedElementTransition != null) {
-                            namedViews = mapSharedElementsIn(state, isPop, inFragment);
-                            removeTargets(sharedElementTransition, sharedElementTargets);
-                            // keep the nonExistentView as excluded so the list doesn't get emptied
-                            sharedElementTargets.remove(state.nonExistentView);
-                            excludeViews(exitTransition, sharedElementTransition,
-                                    sharedElementTargets, false);
-                            excludeViews(enterTransition, sharedElementTransition,
-                                    sharedElementTargets, false);
-
-                            setSharedElementTargets(sharedElementTransition,
-                                    state.nonExistentView, namedViews, sharedElementTargets);
-
-                            setEpicenterIn(namedViews, state);
-
-                            callSharedElementEnd(state, inFragment, outFragment, isPop,
-                                    namedViews);
-                        }
-
-                        if (enterTransition != null) {
-                            enterTransition.removeTarget(state.nonExistentView);
-                            View view = inFragment.getView();
-                            if (view != null) {
-                                view.captureTransitioningViews(enteringViews);
-                                if (namedViews != null) {
-                                    enteringViews.removeAll(namedViews.values());
-                                }
-                                enteringViews.add(state.nonExistentView);
-                                // We added this earlier to prevent any views being targeted.
-                                addTargets(enterTransition, enteringViews);
-                            }
-                            setSharedElementEpicenter(enterTransition, state);
-                        }
-
-                        excludeViews(exitTransition, enterTransition, enteringViews, true);
-                        excludeViews(exitTransition, sharedElementTransition, sharedElementTargets,
-                                true);
-                        excludeViews(enterTransition, sharedElementTransition, sharedElementTargets,
-                                true);
-                        return true;
-                    }
-                });
-        return enteringViews;
-    }
-
-    private void callSharedElementEnd(TransitionState state, Fragment inFragment,
-            Fragment outFragment, boolean isPop, ArrayMap<String, View> namedViews) {
-        SharedElementCallback sharedElementCallback = isPop ?
-                outFragment.mEnterTransitionCallback :
-                inFragment.mEnterTransitionCallback;
-        ArrayList<String> names = new ArrayList<String>(namedViews.keySet());
-        ArrayList<View> views = new ArrayList<View>(namedViews.values());
-        sharedElementCallback.onSharedElementEnd(names, views, null);
-    }
-
-    private void setEpicenterIn(ArrayMap<String, View> namedViews, TransitionState state) {
-        if (mSharedElementTargetNames != null && !namedViews.isEmpty()) {
-            // now we know the epicenter of the entering transition.
-            View epicenter = namedViews
-                    .get(mSharedElementTargetNames.get(0));
-            if (epicenter != null) {
-                state.enteringEpicenterView = epicenter;
-            }
-        }
-    }
-
-    private ArrayMap<String, View> mapSharedElementsIn(TransitionState state,
-            boolean isPop, Fragment inFragment) {
-        // Now map the shared elements in the incoming fragment
-        ArrayMap<String, View> namedViews = mapEnteringSharedElements(state, inFragment, isPop);
-
-        // remap shared elements and set the name mapping used
-        // in the shared element transition.
-        if (isPop) {
-            inFragment.mExitTransitionCallback.onMapSharedElements(
-                    mSharedElementTargetNames, namedViews);
-            setBackNameOverrides(state, namedViews, true);
-        } else {
-            inFragment.mEnterTransitionCallback.onMapSharedElements(
-                    mSharedElementTargetNames, namedViews);
-            setNameOverrides(state, namedViews, true);
-        }
-        return namedViews;
-    }
-
-    private static Transition mergeTransitions(Transition enterTransition,
-            Transition exitTransition, Transition sharedElementTransition, Fragment inFragment,
-            boolean isPop) {
-        boolean overlap = true;
-        if (enterTransition != null && exitTransition != null && inFragment != null) {
-            overlap = isPop ? inFragment.getAllowReturnTransitionOverlap() :
-                    inFragment.getAllowEnterTransitionOverlap();
-        }
-
-        // Wrap the transitions. Explicit targets like in enter and exit will cause the
-        // views to be targeted regardless of excluded views. If that happens, then the
-        // excluded fragments views (hidden fragments) will still be in the transition.
-
-        Transition transition;
-        if (overlap) {
-            // Regular transition -- do it all together
-            TransitionSet transitionSet = new TransitionSet();
-            if (enterTransition != null) {
-                transitionSet.addTransition(enterTransition);
-            }
-            if (exitTransition != null) {
-                transitionSet.addTransition(exitTransition);
-            }
-            if (sharedElementTransition != null) {
-                transitionSet.addTransition(sharedElementTransition);
-            }
-            transition = transitionSet;
-        } else {
-            // First do exit, then enter, but allow shared element transition to happen
-            // during both.
-            Transition staggered = null;
-            if (exitTransition != null && enterTransition != null) {
-                staggered = new TransitionSet()
-                        .addTransition(exitTransition)
-                        .addTransition(enterTransition)
-                        .setOrdering(TransitionSet.ORDERING_SEQUENTIAL);
-            } else if (exitTransition != null) {
-                staggered = exitTransition;
-            } else if (enterTransition != null) {
-                staggered = enterTransition;
-            }
-            if (sharedElementTransition != null) {
-                TransitionSet together = new TransitionSet();
-                if (staggered != null) {
-                    together.addTransition(staggered);
-                }
-                together.addTransition(sharedElementTransition);
-                transition = together;
-            } else {
-                transition = staggered;
-            }
-        }
-        return transition;
-    }
-
-    /**
-     * Configures custom transitions for a specific fragment container.
-     *
-     * @param containerId The container ID of the fragments to configure the transition for.
-     * @param state The Transition State keeping track of the executing transitions.
-     * @param transitioningFragments The first out and last in fragments for the fragment container.
-     */
-    private void configureTransitions(int containerId, TransitionState state,
-            FragmentContainerTransition transitioningFragments) {
-        ViewGroup sceneRoot = (ViewGroup) mManager.mContainer.onFindViewById(containerId);
-        if (sceneRoot != null) {
-            final Fragment inFragment = transitioningFragments.lastIn;
-            final Fragment outFragment = transitioningFragments.firstOut;
-
-            Transition enterTransition =
-                    getEnterTransition(inFragment, transitioningFragments.lastInIsPop);
-            TransitionSet sharedElementTransition = getSharedElementTransition(inFragment,
-                    outFragment, transitioningFragments.lastInIsPop);
-            Transition exitTransition =
-                    getExitTransition(outFragment, transitioningFragments.firstOutIsPop);
-
-            if (enterTransition == null && sharedElementTransition == null &&
-                    exitTransition == null) {
-                return; // no transitions!
-            }
-            if (enterTransition != null) {
-                enterTransition.addTarget(state.nonExistentView);
-            }
-            ArrayMap<String, View> namedViews = null;
-            ArrayList<View> sharedElementTargets = new ArrayList<View>();
-            if (sharedElementTransition != null) {
-                namedViews = remapSharedElements(state, outFragment,
-                        transitioningFragments.firstOutIsPop);
-                setSharedElementTargets(sharedElementTransition,
-                        state.nonExistentView, namedViews, sharedElementTargets);
-
-                // Notify the start of the transition.
-                SharedElementCallback callback = transitioningFragments.lastInIsPop ?
-                        outFragment.mEnterTransitionCallback :
-                        inFragment.mEnterTransitionCallback;
-                ArrayList<String> names = new ArrayList<String>(namedViews.keySet());
-                ArrayList<View> views = new ArrayList<View>(namedViews.values());
-                callback.onSharedElementStart(names, views, null);
-            }
-
-            ArrayList<View> exitingViews = captureExitingViews(exitTransition, outFragment,
-                    namedViews, state.nonExistentView);
-            if (exitingViews == null || exitingViews.isEmpty()) {
-                exitTransition = null;
-            }
-            excludeViews(enterTransition, exitTransition, exitingViews, true);
-            excludeViews(enterTransition, sharedElementTransition, sharedElementTargets, true);
-            excludeViews(exitTransition, sharedElementTransition, sharedElementTargets, true);
-
-            // Set the epicenter of the exit transition
-            if (mSharedElementTargetNames != null && namedViews != null) {
-                View epicenterView = namedViews.get(mSharedElementTargetNames.get(0));
-                if (epicenterView != null) {
-                    if (exitTransition != null) {
-                        setEpicenter(exitTransition, epicenterView);
-                    }
-                    if (sharedElementTransition != null) {
-                        setEpicenter(sharedElementTransition, epicenterView);
-                    }
-                }
-            }
-
-            Transition transition = mergeTransitions(enterTransition, exitTransition,
-                    sharedElementTransition, inFragment, transitioningFragments.lastInIsPop);
-
-            if (transition != null) {
-                ArrayList<View> hiddenFragments = new ArrayList<View>();
-                ArrayList<View> enteringViews = addTransitionTargets(state, enterTransition,
-                        sharedElementTransition, exitTransition, transition, sceneRoot, inFragment,
-                        outFragment, hiddenFragments, transitioningFragments.lastInIsPop,
-                        sharedElementTargets);
-
-                transition.setNameOverrides(state.nameOverrides);
-                // We want to exclude hidden views later, so we need a non-null list in the
-                // transition now.
-                transition.excludeTarget(state.nonExistentView, true);
-                // Now exclude all currently hidden fragments.
-                excludeHiddenFragments(hiddenFragments, containerId, transition);
-                TransitionManager.beginDelayedTransition(sceneRoot, transition);
-                // Remove the view targeting after the transition starts
-                removeTargetedViewsFromTransitions(sceneRoot, state.nonExistentView,
-                        enterTransition, enteringViews, exitTransition, exitingViews,
-                        sharedElementTransition, sharedElementTargets, transition,
-                        hiddenFragments);
-            }
-        }
-    }
-
-    /**
-     * Finds all children of the shared elements and sets the wrapping TransitionSet
-     * targets to point to those. It also limits transitions that have no targets to the
-     * specific shared elements. This allows developers to target child views of the
-     * shared elements specifically, but this doesn't happen by default.
-     */
-    private static void setSharedElementTargets(TransitionSet transition,
-            View nonExistentView, ArrayMap<String, View> namedViews,
-            ArrayList<View> sharedElementTargets) {
-        sharedElementTargets.clear();
-        sharedElementTargets.addAll(namedViews.values());
-
-        final List<View> views = transition.getTargets();
-        views.clear();
-        final int count = sharedElementTargets.size();
-        for (int i = 0; i < count; i++) {
-            final View view = sharedElementTargets.get(i);
-            bfsAddViewChildren(views, view);
-        }
-        sharedElementTargets.add(nonExistentView);
-        addTargets(transition, sharedElementTargets);
-    }
-
-    /**
-     * Uses a breadth-first scheme to add startView and all of its children to views.
-     * It won't add a child if it is already in views.
-     */
-    private static void bfsAddViewChildren(final List<View> views, final View startView) {
-        final int startIndex = views.size();
-        if (containedBeforeIndex(views, startView, startIndex)) {
-            return; // This child is already in the list, so all its children are also.
-        }
-        views.add(startView);
-        for (int index = startIndex; index < views.size(); index++) {
-            final View view = views.get(index);
-            if (view instanceof ViewGroup) {
-                ViewGroup viewGroup = (ViewGroup) view;
-                final int childCount =  viewGroup.getChildCount();
-                for (int childIndex = 0; childIndex < childCount; childIndex++) {
-                    final View child = viewGroup.getChildAt(childIndex);
-                    if (!containedBeforeIndex(views, child, startIndex)) {
-                        views.add(child);
-                    }
-                }
-            }
-        }
-    }
-
-    /**
-     * Does a linear search through views for view, limited to maxIndex.
-     */
-    private static boolean containedBeforeIndex(final List<View> views, final View view,
-            final int maxIndex) {
-        for (int i = 0; i < maxIndex; i++) {
-            if (views.get(i) == view) {
+            if (isFragmentPostponed(op)) {
                 return true;
             }
         }
         return false;
     }
 
-    private static void excludeViews(Transition transition, Transition fromTransition,
-            ArrayList<View> views, boolean exclude) {
-        if (transition != null) {
-            final int viewCount = fromTransition == null ? 0 : views.size();
-            for (int i = 0; i < viewCount; i++) {
-                transition.excludeTarget(views.get(i), exclude);
+    void setOnStartPostponedListener(Fragment.OnStartEnterTransitionListener listener) {
+        for (int opNum = 0; opNum < mOps.size(); opNum++) {
+            final Op op = mOps.get(opNum);
+            if (isFragmentPostponed(op)) {
+                op.fragment.setOnStartEnterTransitionListener(listener);
             }
         }
     }
 
-    /**
-     * After the transition has started, remove all targets that we added to the transitions
-     * so that the transitions are left in a clean state.
-     */
-    private void removeTargetedViewsFromTransitions(
-            final ViewGroup sceneRoot, final View nonExistingView,
-            final Transition enterTransition, final ArrayList<View> enteringViews,
-            final Transition exitTransition, final ArrayList<View> exitingViews,
-            final Transition sharedElementTransition, final ArrayList<View> sharedElementTargets,
-            final Transition overallTransition, final ArrayList<View> hiddenViews) {
-        if (overallTransition != null) {
-            sceneRoot.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
-                @Override
-                public boolean onPreDraw() {
-                    sceneRoot.getViewTreeObserver().removeOnPreDrawListener(this);
-                    if (enterTransition != null) {
-                        removeTargets(enterTransition, enteringViews);
-                        excludeViews(enterTransition, exitTransition, exitingViews, false);
-                        excludeViews(enterTransition, sharedElementTransition, sharedElementTargets,
-                                false);
-                    }
-                    if (exitTransition != null) {
-                        removeTargets(exitTransition, exitingViews);
-                        excludeViews(exitTransition, enterTransition, enteringViews, false);
-                        excludeViews(exitTransition, sharedElementTransition, sharedElementTargets,
-                                false);
-                    }
-                    if (sharedElementTransition != null) {
-                        removeTargets(sharedElementTransition, sharedElementTargets);
-                    }
-                    int numViews = hiddenViews.size();
-                    for (int i = 0; i < numViews; i++) {
-                        overallTransition.excludeTarget(hiddenViews.get(i), false);
-                    }
-                    overallTransition.excludeTarget(nonExistingView, false);
-                    return true;
-                }
-            });
-        }
-    }
-
-    /**
-     * This method removes the views from transitions that target ONLY those views.
-     * The views list should match those added in addTargets and should contain
-     * one view that is not in the view hierarchy (state.nonExistentView).
-     */
-    public static void removeTargets(Transition transition, ArrayList<View> views) {
-        if (transition instanceof TransitionSet) {
-            TransitionSet set = (TransitionSet) transition;
-            int numTransitions = set.getTransitionCount();
-            for (int i = 0; i < numTransitions; i++) {
-                Transition child = set.getTransitionAt(i);
-                removeTargets(child, views);
-            }
-        } else if (!hasSimpleTarget(transition)) {
-            List<View> targets = transition.getTargets();
-            if (targets != null && targets.size() == views.size() &&
-                    targets.containsAll(views)) {
-                // We have an exact match. We must have added these earlier in addTargets
-                for (int i = views.size() - 1; i >= 0; i--) {
-                    transition.removeTarget(views.get(i));
-                }
-            }
-        }
-    }
-
-    /**
-     * This method adds views as targets to the transition, but only if the transition
-     * doesn't already have a target. It is best for views to contain one View object
-     * that does not exist in the view hierarchy (state.nonExistentView) so that
-     * when they are removed later, a list match will suffice to remove the targets.
-     * Otherwise, if you happened to have targeted the exact views for the transition,
-     * the removeTargets call will remove them unexpectedly.
-     */
-    public static void addTargets(Transition transition, ArrayList<View> views) {
-        if (transition instanceof TransitionSet) {
-            TransitionSet set = (TransitionSet) transition;
-            int numTransitions = set.getTransitionCount();
-            for (int i = 0; i < numTransitions; i++) {
-                Transition child = set.getTransitionAt(i);
-                addTargets(child, views);
-            }
-        } else if (!hasSimpleTarget(transition)) {
-            List<View> targets = transition.getTargets();
-            if (isNullOrEmpty(targets)) {
-                // We can just add the target views
-                int numViews = views.size();
-                for (int i = 0; i < numViews; i++) {
-                    transition.addTarget(views.get(i));
-                }
-            }
-        }
-    }
-
-    private static boolean hasSimpleTarget(Transition transition) {
-        return !isNullOrEmpty(transition.getTargetIds()) ||
-                !isNullOrEmpty(transition.getTargetNames()) ||
-                !isNullOrEmpty(transition.getTargetTypes());
-    }
-
-    private static boolean isNullOrEmpty(List list) {
-        return list == null || list.isEmpty();
-    }
-
-    /**
-     * Remaps a name-to-View map, substituting different names for keys.
-     *
-     * @param inMap A list of keys found in the map, in the order in toGoInMap
-     * @param toGoInMap A list of keys to use for the new map, in the order of inMap
-     * @param namedViews The current mapping
-     * @return a new Map after it has been mapped with the new names as keys.
-     */
-    private static ArrayMap<String, View> remapNames(ArrayList<String> inMap,
-            ArrayList<String> toGoInMap, ArrayMap<String, View> namedViews) {
-        ArrayMap<String, View> remappedViews = new ArrayMap<String, View>();
-        if (!namedViews.isEmpty()) {
-            int numKeys = inMap.size();
-            for (int i = 0; i < numKeys; i++) {
-                View view = namedViews.get(inMap.get(i));
-
-                if (view != null) {
-                    remappedViews.put(toGoInMap.get(i), view);
-                }
-            }
-        }
-        return remappedViews;
-    }
-
-    /**
-     * Maps shared elements to views in the entering fragment.
-     *
-     * @param state The transition State as returned from {@link #beginTransition(
-     * android.util.SparseArray, android.util.SparseArray, boolean)}.
-     * @param inFragment The last fragment to be added.
-     * @param isPop true if this is popping the back stack or false if this is a
-     *               forward operation.
-     */
-    private ArrayMap<String, View> mapEnteringSharedElements(TransitionState state,
-            Fragment inFragment, boolean isPop) {
-        ArrayMap<String, View> namedViews = new ArrayMap<String, View>();
-        View root = inFragment.getView();
-        if (root != null) {
-            if (mSharedElementSourceNames != null) {
-                root.findNamedViews(namedViews);
-                if (isPop) {
-                    namedViews = remapNames(mSharedElementSourceNames,
-                            mSharedElementTargetNames, namedViews);
-                } else {
-                    namedViews.retainAll(mSharedElementTargetNames);
-                }
-            }
-        }
-        return namedViews;
-    }
-
-    private void excludeHiddenFragments(final ArrayList<View> hiddenFragmentViews, int containerId,
-            Transition transition) {
-        if (mManager.mAdded != null) {
-            for (int i = 0; i < mManager.mAdded.size(); i++) {
-                Fragment fragment = mManager.mAdded.get(i);
-                if (fragment.mView != null && fragment.mContainer != null &&
-                        fragment.mContainerId == containerId) {
-                    if (fragment.mHidden) {
-                        if (!hiddenFragmentViews.contains(fragment.mView)) {
-                            transition.excludeTarget(fragment.mView, true);
-                            hiddenFragmentViews.add(fragment.mView);
-                        }
-                    } else {
-                        transition.excludeTarget(fragment.mView, false);
-                        hiddenFragmentViews.remove(fragment.mView);
-                    }
-                }
-            }
-        }
-    }
-
-    private static void setEpicenter(Transition transition, View view) {
-        final Rect epicenter = new Rect();
-        view.getBoundsOnScreen(epicenter);
-
-        transition.setEpicenterCallback(new Transition.EpicenterCallback() {
-            @Override
-            public Rect onGetEpicenter(Transition transition) {
-                return epicenter;
-            }
-        });
-    }
-
-    private void setSharedElementEpicenter(Transition transition, final TransitionState state) {
-        transition.setEpicenterCallback(new Transition.EpicenterCallback() {
-            private Rect mEpicenter;
-
-            @Override
-            public Rect onGetEpicenter(Transition transition) {
-                if (mEpicenter == null && state.enteringEpicenterView != null) {
-                    mEpicenter = new Rect();
-                    state.enteringEpicenterView.getBoundsOnScreen(mEpicenter);
-                }
-                return mEpicenter;
-            }
-        });
-    }
-
-    private static void setNameOverride(ArrayMap<String, String> overrides,
-            String source, String target) {
-        if (source != null && target != null && !source.equals(target)) {
-            for (int index = 0; index < overrides.size(); index++) {
-                if (source.equals(overrides.valueAt(index))) {
-                    overrides.setValueAt(index, target);
-                    return;
-                }
-            }
-            overrides.put(source, target);
-        }
-    }
-
-    static void setNameOverrides(TransitionState state, ArrayList<String> sourceNames,
-            ArrayList<String> targetNames) {
-        if (sourceNames != null && targetNames != null) {
-            for (int i = 0; i < sourceNames.size(); i++) {
-                String source = sourceNames.get(i);
-                String target = targetNames.get(i);
-                setNameOverride(state.nameOverrides, source, target);
-            }
-        }
-    }
-
-    private void setBackNameOverrides(TransitionState state, ArrayMap<String, View> namedViews,
-            boolean isEnd) {
-        int targetCount = mSharedElementTargetNames == null ? 0 : mSharedElementTargetNames.size();
-        int sourceCount = mSharedElementSourceNames == null ? 0 : mSharedElementSourceNames.size();
-        final int count = Math.min(targetCount, sourceCount);
-        for (int i = 0; i < count; i++) {
-            String source = mSharedElementSourceNames.get(i);
-            String originalTarget = mSharedElementTargetNames.get(i);
-            View view = namedViews.get(originalTarget);
-            if (view != null) {
-                String target = view.getTransitionName();
-                if (isEnd) {
-                    setNameOverride(state.nameOverrides, source, target);
-                } else {
-                    setNameOverride(state.nameOverrides, target, source);
-                }
-            }
-        }
-    }
-
-    private void setNameOverrides(TransitionState state, ArrayMap<String, View> namedViews,
-            boolean isEnd) {
-        int count = namedViews == null ? 0 : namedViews.size();
-        for (int i = 0; i < count; i++) {
-            String source = namedViews.keyAt(i);
-            String target = namedViews.valueAt(i).getTransitionName();
-            if (isEnd) {
-                setNameOverride(state.nameOverrides, source, target);
-            } else {
-                setNameOverride(state.nameOverrides, target, source);
-            }
-        }
+    private static boolean isFragmentPostponed(Op op) {
+        final Fragment fragment = op.fragment;
+        return (fragment.mAdded && fragment.mView != null && !fragment.mDetached &&
+                !fragment.mHidden && fragment.isPostponed());
     }
 
     public String getName() {
@@ -1701,36 +909,4 @@
     public boolean isEmpty() {
         return mOps.isEmpty();
     }
-
-    public class TransitionState {
-        public ArrayMap<String, String> nameOverrides = new ArrayMap<String, String>();
-        public View enteringEpicenterView;
-        public View nonExistentView;
-    }
-
-    /**
-     * Tracks the last fragment added and first fragment removed for fragment transitions.
-     * This also tracks which fragments are changed by push or pop transactions.
-     */
-    public static class FragmentContainerTransition {
-        /**
-         * The last fragment added/attached/shown in its container
-         */
-        public Fragment lastIn;
-
-        /**
-         * true when lastIn was added during a pop transaction or false if added with a push
-         */
-        public boolean lastInIsPop;
-
-        /**
-         * The first fragment with a View that was removed/detached/hidden in its container.
-         */
-        public Fragment firstOut;
-
-        /**
-         * true when firstOut was removed during a pop transaction or false otherwise
-         */
-        public boolean firstOutIsPop;
-    }
 }
diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java
index b72b960..5d1cd3b 100644
--- a/core/java/android/app/Fragment.java
+++ b/core/java/android/app/Fragment.java
@@ -31,6 +31,8 @@
 import android.os.Build;
 import android.os.Build.VERSION_CODES;
 import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.transition.Transition;
@@ -375,15 +377,6 @@
 
     int mState = INITIALIZING;
 
-    // Non-null if the fragment's view hierarchy is currently animating away,
-    // meaning we need to wait a bit on completely destroying it.  This is the
-    // animation that is running.
-    Animator mAnimatingAway;
-
-    // If mAnimatingAway != null, this is the state we should move to once the
-    // animation is done.
-    int mStateAfterAnimating;
-
     // When instantiated from saved state, this is the saved state.
     Bundle mSavedFragmentState;
     SparseArray<Parcelable> mSavedViewState;
@@ -478,15 +471,6 @@
     // Used to verify that subclasses call through to super class.
     boolean mCalled;
 
-    // If app has requested a specific animation, this is the one to use.
-    int mNextAnim;
-
-    // If app has requested a specific transition, this is the one to use.
-    int mNextTransition;
-
-    // If app has requested a specific transition style, this is the one to use.
-    int mNextTransitionStyle;
-
     // The parent container of the fragment after dynamically added to UI.
     ViewGroup mContainer;
 
@@ -504,17 +488,15 @@
     boolean mLoadersStarted;
     boolean mCheckedForLoaderManager;
 
-    private Transition mEnterTransition = null;
-    private Transition mReturnTransition = USE_DEFAULT_TRANSITION;
-    private Transition mExitTransition = null;
-    private Transition mReenterTransition = USE_DEFAULT_TRANSITION;
-    private Transition mSharedElementEnterTransition = null;
-    private Transition mSharedElementReturnTransition = USE_DEFAULT_TRANSITION;
-    private Boolean mAllowReturnTransitionOverlap;
-    private Boolean mAllowEnterTransitionOverlap;
+    // The animation and transition information for the fragment. This will be null
+    // unless the elements are explicitly accessed and should remain null for Fragments
+    // without Views.
+    AnimationInfo mAnimationInfo;
 
-    SharedElementCallback mEnterTransitionCallback = SharedElementCallback.NULL_CALLBACK;
-    SharedElementCallback mExitTransitionCallback = SharedElementCallback.NULL_CALLBACK;
+    // True if the View was added, and its animation has yet to be run. This could
+    // also indicate that the fragment view hasn't been made visible, even if there is no
+    // animation for this fragment.
+    boolean mIsNewlyAdded;
 
     // True if mHidden has been changed and the animation should be scheduled.
     boolean mHiddenChanged;
@@ -1399,26 +1381,41 @@
 
         TypedArray a = context.obtainStyledAttributes(attrs,
                 com.android.internal.R.styleable.Fragment);
-        mEnterTransition = loadTransition(context, a, mEnterTransition, null,
-                com.android.internal.R.styleable.Fragment_fragmentEnterTransition);
-        mReturnTransition = loadTransition(context, a, mReturnTransition, USE_DEFAULT_TRANSITION,
-                com.android.internal.R.styleable.Fragment_fragmentReturnTransition);
-        mExitTransition = loadTransition(context, a, mExitTransition, null,
-                com.android.internal.R.styleable.Fragment_fragmentExitTransition);
-        mReenterTransition = loadTransition(context, a, mReenterTransition, USE_DEFAULT_TRANSITION,
-                com.android.internal.R.styleable.Fragment_fragmentReenterTransition);
-        mSharedElementEnterTransition = loadTransition(context, a, mSharedElementEnterTransition,
-                null, com.android.internal.R.styleable.Fragment_fragmentSharedElementEnterTransition);
-        mSharedElementReturnTransition = loadTransition(context, a, mSharedElementReturnTransition,
+        setEnterTransition(loadTransition(context, a, getEnterTransition(), null,
+                com.android.internal.R.styleable.Fragment_fragmentEnterTransition));
+        setReturnTransition(loadTransition(context, a, getReturnTransition(),
                 USE_DEFAULT_TRANSITION,
-                com.android.internal.R.styleable.Fragment_fragmentSharedElementReturnTransition);
-        if (mAllowEnterTransitionOverlap == null) {
-            mAllowEnterTransitionOverlap = a.getBoolean(
-                    com.android.internal.R.styleable.Fragment_fragmentAllowEnterTransitionOverlap, true);
+                com.android.internal.R.styleable.Fragment_fragmentReturnTransition));
+        setExitTransition(loadTransition(context, a, getExitTransition(), null,
+                com.android.internal.R.styleable.Fragment_fragmentExitTransition));
+
+        setReenterTransition(loadTransition(context, a, getReenterTransition(),
+                USE_DEFAULT_TRANSITION,
+                com.android.internal.R.styleable.Fragment_fragmentReenterTransition));
+        setSharedElementEnterTransition(loadTransition(context, a,
+                getSharedElementEnterTransition(), null,
+                com.android.internal.R.styleable.Fragment_fragmentSharedElementEnterTransition));
+        setSharedElementReturnTransition(loadTransition(context, a,
+                getSharedElementReturnTransition(), USE_DEFAULT_TRANSITION,
+                com.android.internal.R.styleable.Fragment_fragmentSharedElementReturnTransition));
+        boolean isEnterSet;
+        boolean isReturnSet;
+        if (mAnimationInfo == null) {
+            isEnterSet = false;
+            isReturnSet = false;
+        } else {
+            isEnterSet = mAnimationInfo.mAllowEnterTransitionOverlap != null;
+            isReturnSet = mAnimationInfo.mAllowReturnTransitionOverlap != null;
         }
-        if (mAllowReturnTransitionOverlap == null) {
-            mAllowReturnTransitionOverlap = a.getBoolean(
-                    com.android.internal.R.styleable.Fragment_fragmentAllowReturnTransitionOverlap, true);
+        if (!isEnterSet) {
+            setAllowEnterTransitionOverlap(a.getBoolean(
+                    com.android.internal.R.styleable.Fragment_fragmentAllowEnterTransitionOverlap,
+                    true));
+        }
+        if (!isReturnSet) {
+            setAllowReturnTransitionOverlap(a.getBoolean(
+                    com.android.internal.R.styleable.Fragment_fragmentAllowReturnTransitionOverlap,
+                    true));
         }
         a.recycle();
 
@@ -1943,16 +1940,12 @@
      */
     public void setEnterSharedElementCallback(SharedElementCallback callback) {
         if (callback == null) {
+            if (mAnimationInfo == null) {
+                return; // already a null callback
+            }
             callback = SharedElementCallback.NULL_CALLBACK;
         }
-        mEnterTransitionCallback = callback;
-    }
-
-    /**
-     * @hide
-     */
-    public void setEnterSharedElementTransitionCallback(SharedElementCallback callback) {
-        setEnterSharedElementCallback(callback);
+        ensureAnimationInfo().mEnterTransitionCallback = callback;
     }
 
     /**
@@ -1964,16 +1957,12 @@
      */
     public void setExitSharedElementCallback(SharedElementCallback callback) {
         if (callback == null) {
+            if (mAnimationInfo == null) {
+                return; // already a null callback
+            }
             callback = SharedElementCallback.NULL_CALLBACK;
         }
-        mExitTransitionCallback = callback;
-    }
-
-    /**
-     * @hide
-     */
-    public void setExitSharedElementTransitionCallback(SharedElementCallback callback) {
-        setExitSharedElementCallback(callback);
+        ensureAnimationInfo().mExitTransitionCallback = callback;
     }
 
     /**
@@ -1988,7 +1977,9 @@
      * @attr ref android.R.styleable#Fragment_fragmentEnterTransition
      */
     public void setEnterTransition(Transition transition) {
-        mEnterTransition = transition;
+        if (shouldChangeTransition(transition, null)) {
+            ensureAnimationInfo().mEnterTransition = transition;
+        }
     }
 
     /**
@@ -2002,7 +1993,10 @@
      * @attr ref android.R.styleable#Fragment_fragmentEnterTransition
      */
     public Transition getEnterTransition() {
-        return mEnterTransition;
+        if (mAnimationInfo == null) {
+            return null;
+        }
+        return mAnimationInfo.mEnterTransition;
     }
 
     /**
@@ -2020,7 +2014,9 @@
      * @attr ref android.R.styleable#Fragment_fragmentExitTransition
      */
     public void setReturnTransition(Transition transition) {
-        mReturnTransition = transition;
+        if (shouldChangeTransition(transition, USE_DEFAULT_TRANSITION)) {
+            ensureAnimationInfo().mReturnTransition = transition;
+        }
     }
 
     /**
@@ -2037,8 +2033,11 @@
      * @attr ref android.R.styleable#Fragment_fragmentExitTransition
      */
     public Transition getReturnTransition() {
-        return mReturnTransition == USE_DEFAULT_TRANSITION ? getEnterTransition()
-                : mReturnTransition;
+        if (mAnimationInfo == null) {
+            return null;
+        }
+        return mAnimationInfo.mReturnTransition == USE_DEFAULT_TRANSITION ? getEnterTransition()
+                : mAnimationInfo.mReturnTransition;
     }
 
     /**
@@ -2055,7 +2054,9 @@
      * @attr ref android.R.styleable#Fragment_fragmentExitTransition
      */
     public void setExitTransition(Transition transition) {
-        mExitTransition = transition;
+        if (shouldChangeTransition(transition, null)) {
+            ensureAnimationInfo().mExitTransition = transition;
+        }
     }
 
     /**
@@ -2072,7 +2073,10 @@
      * @attr ref android.R.styleable#Fragment_fragmentExitTransition
      */
     public Transition getExitTransition() {
-        return mExitTransition;
+        if (mAnimationInfo == null) {
+            return null;
+        }
+        return mAnimationInfo.mExitTransition;
     }
 
     /**
@@ -2089,7 +2093,9 @@
      * @attr ref android.R.styleable#Fragment_fragmentReenterTransition
      */
     public void setReenterTransition(Transition transition) {
-        mReenterTransition = transition;
+        if (shouldChangeTransition(transition, USE_DEFAULT_TRANSITION)) {
+            ensureAnimationInfo().mReenterTransition = transition;
+        }
     }
 
     /**
@@ -2106,8 +2112,11 @@
      * @attr ref android.R.styleable#Fragment_fragmentReenterTransition
      */
     public Transition getReenterTransition() {
-        return mReenterTransition == USE_DEFAULT_TRANSITION ? getExitTransition()
-                : mReenterTransition;
+        if (mAnimationInfo == null) {
+            return null;
+        }
+        return mAnimationInfo.mReenterTransition == USE_DEFAULT_TRANSITION ? getExitTransition()
+                : mAnimationInfo.mReenterTransition;
     }
 
     /**
@@ -2121,7 +2130,9 @@
      * @attr ref android.R.styleable#Fragment_fragmentSharedElementEnterTransition
      */
     public void setSharedElementEnterTransition(Transition transition) {
-        mSharedElementEnterTransition = transition;
+        if (shouldChangeTransition(transition, null)) {
+            ensureAnimationInfo().mSharedElementEnterTransition = transition;
+        }
     }
 
     /**
@@ -2135,7 +2146,10 @@
      * @attr ref android.R.styleable#Fragment_fragmentSharedElementEnterTransition
      */
     public Transition getSharedElementEnterTransition() {
-        return mSharedElementEnterTransition;
+        if (mAnimationInfo == null) {
+            return null;
+        }
+        return mAnimationInfo.mSharedElementEnterTransition;
     }
 
     /**
@@ -2152,7 +2166,9 @@
      * @attr ref android.R.styleable#Fragment_fragmentSharedElementReturnTransition
      */
     public void setSharedElementReturnTransition(Transition transition) {
-        mSharedElementReturnTransition = transition;
+        if (shouldChangeTransition(transition, USE_DEFAULT_TRANSITION)) {
+            ensureAnimationInfo().mSharedElementReturnTransition = transition;
+        }
     }
 
     /**
@@ -2169,8 +2185,12 @@
      * @attr ref android.R.styleable#Fragment_fragmentSharedElementReturnTransition
      */
     public Transition getSharedElementReturnTransition() {
-        return mSharedElementReturnTransition == USE_DEFAULT_TRANSITION ?
-                getSharedElementEnterTransition() : mSharedElementReturnTransition;
+        if (mAnimationInfo == null) {
+            return null;
+        }
+        return mAnimationInfo.mSharedElementReturnTransition == USE_DEFAULT_TRANSITION
+                ? getSharedElementEnterTransition()
+                : mAnimationInfo.mSharedElementReturnTransition;
     }
 
     /**
@@ -2183,7 +2203,7 @@
      * @attr ref android.R.styleable#Fragment_fragmentAllowEnterTransitionOverlap
      */
     public void setAllowEnterTransitionOverlap(boolean allow) {
-        mAllowEnterTransitionOverlap = allow;
+        ensureAnimationInfo().mAllowEnterTransitionOverlap = allow;
     }
 
     /**
@@ -2196,7 +2216,8 @@
      * @attr ref android.R.styleable#Fragment_fragmentAllowEnterTransitionOverlap
      */
     public boolean getAllowEnterTransitionOverlap() {
-        return (mAllowEnterTransitionOverlap == null) ? true : mAllowEnterTransitionOverlap;
+        return (mAnimationInfo == null || mAnimationInfo.mAllowEnterTransitionOverlap == null)
+                ? true : mAnimationInfo.mAllowEnterTransitionOverlap;
     }
 
     /**
@@ -2209,7 +2230,7 @@
      * @attr ref android.R.styleable#Fragment_fragmentAllowReturnTransitionOverlap
      */
     public void setAllowReturnTransitionOverlap(boolean allow) {
-        mAllowReturnTransitionOverlap = allow;
+        ensureAnimationInfo().mAllowReturnTransitionOverlap = allow;
     }
 
     /**
@@ -2222,7 +2243,90 @@
      * @attr ref android.R.styleable#Fragment_fragmentAllowReturnTransitionOverlap
      */
     public boolean getAllowReturnTransitionOverlap() {
-        return (mAllowReturnTransitionOverlap == null) ? true : mAllowReturnTransitionOverlap;
+        return (mAnimationInfo == null || mAnimationInfo.mAllowReturnTransitionOverlap == null)
+                ? true : mAnimationInfo.mAllowReturnTransitionOverlap;
+    }
+
+    /**
+     * Postpone the entering Fragment transition until {@link #startPostponedEnterTransition()}
+     * or {@link FragmentManager#executePendingTransactions()} has been called.
+     * <p>
+     * This method gives the Fragment the ability to delay Fragment animations
+     * until all data is loaded. Until then, the added, shown, and
+     * attached Fragments will be INVISIBLE and removed, hidden, and detached Fragments won't
+     * be have their Views removed. The transaction runs when all postponed added Fragments in the
+     * transaction have called {@link #startPostponedEnterTransition()}.
+     * <p>
+     * This method should be called before being added to the FragmentTransaction or
+     * in {@link #onCreate(Bundle), {@link #onAttach(Context)}, or
+     * {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)}}.
+     * {@link #startPostponedEnterTransition()} must be called to allow the Fragment to
+     * start the transitions.
+     * <p>
+     * When a FragmentTransaction is started that may affect a postponed FragmentTransaction,
+     * based on which containers are in their operations, the postponed FragmentTransaction
+     * will have its start triggered. The early triggering may result in faulty or nonexistent
+     * animations in the postponed transaction. FragmentTransactions that operate only on
+     * independent containers will not interfere with each other's postponement.
+     * <p>
+     * Calling postponeEnterTransition on Fragments with a null View will not postpone the
+     * transition. Likewise, postponement only works if FragmentTransaction optimizations are
+     * enabled.
+     *
+     * @see Activity#postponeEnterTransition()
+     * @see FragmentTransaction#setAllowOptimization(boolean)
+     */
+    public void postponeEnterTransition() {
+        ensureAnimationInfo().mEnterTransitionPostponed = true;
+    }
+
+    /**
+     * Begin postponed transitions after {@link #postponeEnterTransition()} was called.
+     * If postponeEnterTransition() was called, you must call startPostponedEnterTransition()
+     * or {@link FragmentManager#executePendingTransactions()} to complete the FragmentTransaction.
+     * If postponement was interrupted with {@link FragmentManager#executePendingTransactions()},
+     * before {@code startPostponedEnterTransition()}, animations may not run or may execute
+     * improperly.
+     *
+     * @see Activity#startPostponedEnterTransition()
+     */
+    public void startPostponedEnterTransition() {
+        if (mFragmentManager == null || mFragmentManager.mHost == null) {
+                ensureAnimationInfo().mEnterTransitionPostponed = false;
+        } else if (Looper.myLooper() != mFragmentManager.mHost.getHandler().getLooper()) {
+            mFragmentManager.mHost.getHandler().
+                    postAtFrontOfQueue(this::callStartTransitionListener);
+        } else {
+            callStartTransitionListener();
+        }
+    }
+
+    /**
+     * Calls the start transition listener. This must be called on the UI thread.
+     */
+    private void callStartTransitionListener() {
+        final OnStartEnterTransitionListener listener;
+        if (mAnimationInfo == null) {
+            listener = null;
+        } else {
+            mAnimationInfo.mEnterTransitionPostponed = false;
+            listener = mAnimationInfo.mStartEnterTransitionListener;
+            mAnimationInfo.mStartEnterTransitionListener = null;
+        }
+        if (listener != null) {
+            listener.onStartEnterTransition();
+        }
+    }
+
+    /**
+     * Returns true if mAnimationInfo is not null or the transition differs from the default value.
+     * This is broken out to ensure mAnimationInfo is properly locked when checking.
+     */
+    private boolean shouldChangeTransition(Transition transition, Transition defaultValue) {
+        if (transition == defaultValue) {
+            return mAnimationInfo != null;
+        }
+        return true;
     }
 
     /**
@@ -2283,8 +2387,8 @@
             writer.print(" mTargetRequestCode=");
             writer.println(mTargetRequestCode);
         }
-        if (mNextAnim != 0) {
-            writer.print(prefix); writer.print("mNextAnim="); writer.println(mNextAnim);
+        if (getNextAnim() != 0) {
+            writer.print(prefix); writer.print("mNextAnim="); writer.println(getNextAnim());
         }
         if (mContainer != null) {
             writer.print(prefix); writer.print("mContainer="); writer.println(mContainer);
@@ -2292,10 +2396,11 @@
         if (mView != null) {
             writer.print(prefix); writer.print("mView="); writer.println(mView);
         }
-        if (mAnimatingAway != null) {
-            writer.print(prefix); writer.print("mAnimatingAway="); writer.println(mAnimatingAway);
+        if (getAnimatingAway() != null) {
+            writer.print(prefix); writer.print("mAnimatingAway=");
+            writer.println(getAnimatingAway());
             writer.print(prefix); writer.print("mStateAfterAnimating=");
-            writer.println(mStateAfterAnimating);
+            writer.println(getStateAfterAnimating());
         }
         if (mLoaderManager != null) {
             writer.print(prefix); writer.println("Loader Manager:");
@@ -2622,6 +2727,23 @@
         }
     }
 
+    void setOnStartEnterTransitionListener(OnStartEnterTransitionListener listener) {
+        ensureAnimationInfo();
+        if (listener == mAnimationInfo.mStartEnterTransitionListener) {
+            return;
+        }
+        if (listener != null && mAnimationInfo.mStartEnterTransitionListener != null) {
+            throw new IllegalStateException("Trying to set a replacement " +
+                    "startPostponedEnterTransition on " + this);
+        }
+        if (mAnimationInfo.mEnterTransitionPostponed) {
+            mAnimationInfo.mStartEnterTransitionListener = listener;
+        }
+        if (listener != null) {
+            listener.startListening();
+        }
+    }
+
     private static Transition loadTransition(Context context, TypedArray typedArray,
             Transition currentValue, Transition defaultValue, int id) {
         if (currentValue != defaultValue) {
@@ -2640,4 +2762,147 @@
         return transition;
     }
 
+    private AnimationInfo ensureAnimationInfo() {
+        if (mAnimationInfo == null) {
+            mAnimationInfo = new AnimationInfo();
+        }
+        return mAnimationInfo;
+    }
+
+    int getNextAnim() {
+        if (mAnimationInfo == null) {
+            return 0;
+        }
+        return mAnimationInfo.mNextAnim;
+    }
+
+    void setNextAnim(int animResourceId) {
+        if (mAnimationInfo == null && animResourceId == 0) {
+            return; // no change!
+        }
+        ensureAnimationInfo().mNextAnim = animResourceId;
+    }
+
+    int getNextTransition() {
+        if (mAnimationInfo == null) {
+            return 0;
+        }
+        return mAnimationInfo.mNextTransition;
+    }
+
+    void setNextTransition(int nextTransition, int nextTransitionStyle) {
+        if (mAnimationInfo == null && nextTransition == 0 && nextTransitionStyle == 0) {
+            return; // no change!
+        }
+        ensureAnimationInfo();
+        mAnimationInfo.mNextTransition = nextTransition;
+        mAnimationInfo.mNextTransitionStyle = nextTransitionStyle;
+    }
+
+    int getNextTransitionStyle() {
+        if (mAnimationInfo == null) {
+            return 0;
+        }
+        return mAnimationInfo.mNextTransitionStyle;
+    }
+
+    SharedElementCallback getEnterTransitionCallback() {
+        if (mAnimationInfo == null) {
+            return SharedElementCallback.NULL_CALLBACK;
+        }
+        return mAnimationInfo.mEnterTransitionCallback;
+    }
+
+    SharedElementCallback getExitTransitionCallback() {
+        if (mAnimationInfo == null) {
+            return SharedElementCallback.NULL_CALLBACK;
+        }
+        return mAnimationInfo.mExitTransitionCallback;
+    }
+
+    Animator getAnimatingAway() {
+        if (mAnimationInfo == null) {
+            return null;
+        }
+        return mAnimationInfo.mAnimatingAway;
+    }
+
+    void setAnimatingAway(Animator animator) {
+        ensureAnimationInfo().mAnimatingAway = animator;
+    }
+
+    int getStateAfterAnimating() {
+        if (mAnimationInfo == null) {
+            return 0;
+        }
+        return mAnimationInfo.mStateAfterAnimating;
+    }
+
+    void setStateAfterAnimating(int state) {
+        ensureAnimationInfo().mStateAfterAnimating = state;
+    }
+
+    boolean isPostponed() {
+        if (mAnimationInfo == null) {
+            return false;
+        }
+        return mAnimationInfo.mEnterTransitionPostponed;
+    }
+
+    /**
+     * Used internally to be notified when {@link #startPostponedEnterTransition()} has
+     * been called. This listener will only be called once and then be removed from the
+     * listeners.
+     */
+    interface OnStartEnterTransitionListener {
+        void onStartEnterTransition();
+        void startListening();
+    }
+
+    /**
+     * Contains all the animation and transition information for a fragment. This will only
+     * be instantiated for Fragments that have Views.
+     */
+    static class AnimationInfo {
+        // Non-null if the fragment's view hierarchy is currently animating away,
+        // meaning we need to wait a bit on completely destroying it.  This is the
+        // animation that is running.
+        Animator mAnimatingAway;
+
+        // If mAnimatingAway != null, this is the state we should move to once the
+        // animation is done.
+        int mStateAfterAnimating;
+
+        // If app has requested a specific animation, this is the one to use.
+        int mNextAnim;
+
+        // If app has requested a specific transition, this is the one to use.
+        int mNextTransition;
+
+        // If app has requested a specific transition style, this is the one to use.
+        int mNextTransitionStyle;
+
+        private Transition mEnterTransition = null;
+        private Transition mReturnTransition = USE_DEFAULT_TRANSITION;
+        private Transition mExitTransition = null;
+        private Transition mReenterTransition = USE_DEFAULT_TRANSITION;
+        private Transition mSharedElementEnterTransition = null;
+        private Transition mSharedElementReturnTransition = USE_DEFAULT_TRANSITION;
+        private Boolean mAllowReturnTransitionOverlap;
+        private Boolean mAllowEnterTransitionOverlap;
+
+        SharedElementCallback mEnterTransitionCallback = SharedElementCallback.NULL_CALLBACK;
+        SharedElementCallback mExitTransitionCallback = SharedElementCallback.NULL_CALLBACK;
+
+        // True when postponeEnterTransition has been called and startPostponeEnterTransition
+        // hasn't been called yet.
+        boolean mEnterTransitionPostponed;
+
+        // Listener to wait for startPostponeEnterTransition. After being called, it will
+        // be set to null
+        OnStartEnterTransitionListener mStartEnterTransitionListener;
+
+        // True if the View was added, and its animation has yet to be run.
+        boolean mIsNewlyAdded;
+    }
 }
diff --git a/core/java/android/app/FragmentHostCallback.java b/core/java/android/app/FragmentHostCallback.java
index d869168..7e415e9 100644
--- a/core/java/android/app/FragmentHostCallback.java
+++ b/core/java/android/app/FragmentHostCallback.java
@@ -54,7 +54,8 @@
     private boolean mLoadersStarted;
 
     public FragmentHostCallback(Context context, Handler handler, int windowAnimations) {
-        this(null /*activity*/, context, handler, windowAnimations);
+        this((context instanceof Activity) ? (Activity)context : null, context,
+                chooseHandler(context, handler), windowAnimations);
     }
 
     FragmentHostCallback(Activity activity) {
@@ -70,6 +71,19 @@
     }
 
     /**
+     * Used internally in {@link #FragmentHostCallback(Context, Handler, int)} to choose
+     * the Activity's handler or the provided handler.
+     */
+    private static Handler chooseHandler(Context context, Handler handler) {
+        if (handler == null && context instanceof Activity) {
+            Activity activity = (Activity) context;
+            return activity.mHandler;
+        } else {
+            return handler;
+        }
+    }
+
+    /**
      * Print internal state into the given stream.
      *
      * @param prefix Desired prefix to prepend at each line of output.
diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java
index b2df1ac..9345a03 100644
--- a/core/java/android/app/FragmentManager.java
+++ b/core/java/android/app/FragmentManager.java
@@ -31,7 +31,6 @@
 import android.os.Looper;
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.transition.Transition;
 import android.util.AttributeSet;
 import android.util.DebugUtils;
 import android.util.Log;
@@ -44,6 +43,7 @@
 import android.view.MenuItem;
 import android.view.View;
 import android.view.ViewGroup;
+
 import com.android.internal.util.FastPrintWriter;
 
 import java.io.FileDescriptor;
@@ -160,6 +160,9 @@
      * can call this function (only from the main thread) to do so.  Note that
      * all callbacks and other related behavior will be done from within this
      * call, so be careful about where this is called from.
+     * <p>
+     * This also forces the start of any postponed Transactions where
+     * {@link Fragment#postponeEnterTransition()} has been called.
      *
      * @return Returns true if there were any pending transactions to be
      * executed.
@@ -206,7 +209,7 @@
     /**
      * Like {@link #popBackStack()}, but performs the operation immediately
      * inside of the call.  This is like calling {@link #executePendingTransactions()}
-     * afterwards.
+     * afterwards without forcing the start of postponed Transactions.
      * @return Returns true if there was something popped, else false.
      */
     public abstract boolean popBackStackImmediate();
@@ -229,7 +232,7 @@
     /**
      * Like {@link #popBackStack(String, int)}, but performs the operation immediately
      * inside of the call.  This is like calling {@link #executePendingTransactions()}
-     * afterwards.
+     * afterwards without forcing the start of postponed Transactions.
      * @return Returns true if there was something popped, else false.
      */
     public abstract boolean popBackStackImmediate(String name, int flags);
@@ -253,7 +256,7 @@
     /**
      * Like {@link #popBackStack(int, int)}, but performs the operation immediately
      * inside of the call.  This is like calling {@link #executePendingTransactions()}
-     * afterwards.
+     * afterwards without forcing the start of postponed Transactions.
      * @return Returns true if there was something popped, else false.
      */
     public abstract boolean popBackStackImmediate(int id, int flags);
@@ -474,13 +477,15 @@
     // Temporary vars for optimizing execution of BackStackRecords:
     ArrayList<BackStackRecord> mTmpRecords;
     ArrayList<Boolean> mTmpIsPop;
-    SparseArray<BackStackRecord.FragmentContainerTransition> mTmpFragmentsContainerTransitions;
     ArrayList<Fragment> mTmpAddedFragments;
 
     // Temporary vars for state save and restore.
     Bundle mStateBundle = null;
     SparseArray<Parcelable> mStateArray = null;
-    
+
+    // Postponed transactions.
+    ArrayList<StartEnterTransitionListener> mPostponedTransactions;
+
     Runnable mExecCommit = new Runnable() {
         @Override
         public void run() {
@@ -564,7 +569,9 @@
 
     @Override
     public boolean executePendingTransactions() {
-        return execPendingActions();
+        boolean updates = execPendingActions();
+        forcePostponedTransactions();
+        return updates;
     }
 
     @Override
@@ -614,7 +621,7 @@
      * @return true if the pop operation did anything or false otherwise.
      */
     private boolean popBackStackImmediate(String name, int id, int flags) {
-        executePendingTransactions();
+        execPendingActions();
         ensureExecReady(true);
 
         boolean executePop = popBackStackState(mTmpRecords, mTmpIsPop, name, id, flags);
@@ -831,14 +838,14 @@
 
     Animator loadAnimator(Fragment fragment, int transit, boolean enter,
             int transitionStyle) {
-        Animator animObj = fragment.onCreateAnimator(transit, enter,
-                fragment.mNextAnim);
+        Animator animObj = fragment.onCreateAnimator(transit, enter, fragment.getNextAnim());
         if (animObj != null) {
             return animObj;
         }
         
-        if (fragment.mNextAnim != 0) {
-            Animator anim = AnimatorInflater.loadAnimator(mHost.getContext(), fragment.mNextAnim);
+        if (fragment.getNextAnim() != 0) {
+            Animator anim = AnimatorInflater.loadAnimator(mHost.getContext(),
+                    fragment.getNextAnim());
             if (anim != null) {
                 return anim;
             }
@@ -914,13 +921,13 @@
             if (f.mFromLayout && !f.mInLayout) {
                 return;
             }
-            if (f.mAnimatingAway != null) {
+            if (f.getAnimatingAway() != null) {
                 // The fragment is currently being animated...  but!  Now we
                 // want to move our state back up.  Give up on waiting for the
                 // animation, move to whatever the final state should be once
                 // the animation is done, and then we can proceed from there.
-                f.mAnimatingAway = null;
-                moveToState(f, f.mStateAfterAnimating, 0, 0, true);
+                f.setAnimatingAway(null);
+                moveToState(f, f.getStateAfterAnimating(), 0, 0, true);
             }
             switch (f.mState) {
                 case Fragment.INITIALIZING:
@@ -1011,16 +1018,13 @@
                             if (f.mView != null) {
                                 f.mView.setSaveFromParentEnabled(false);
                                 if (container != null) {
-                                    Animator anim = loadAnimator(f, transit, true,
-                                            transitionStyle);
-                                    if (anim != null) {
-                                        anim.setTarget(f.mView);
-                                        setHWLayerAnimListenerIfAlpha(f.mView, anim);
-                                        anim.start();
-                                    }
                                     container.addView(f.mView);
+                                    f.mIsNewlyAdded = true;
                                 }
-                                if (f.mHidden) f.mView.setVisibility(View.GONE);
+                                if (f.mHidden) {
+                                    f.mView.setVisibility(View.GONE);
+                                    f.mIsNewlyAdded = false; // No animation required
+                                }
                                 f.onViewCreated(f.mView, f.mSavedFragmentState);
                             }
                         }
@@ -1075,7 +1079,8 @@
                         f.performDestroyView();
                         if (f.mView != null && f.mContainer != null) {
                             Animator anim = null;
-                            if (mCurState > Fragment.INITIALIZING && !mDestroyed) {
+                            if (mCurState > Fragment.INITIALIZING && !mDestroyed &&
+                                    f.mView.getVisibility() == View.VISIBLE) {
                                 anim = loadAnimator(f, transit, false,
                                         transitionStyle);
                             }
@@ -1084,15 +1089,15 @@
                                 final View view = f.mView;
                                 final Fragment fragment = f;
                                 container.startViewTransition(view);
-                                f.mAnimatingAway = anim;
-                                f.mStateAfterAnimating = newState;
+                                f.setAnimatingAway(anim);
+                                f.setStateAfterAnimating(newState);
                                 anim.addListener(new AnimatorListenerAdapter() {
                                     @Override
                                     public void onAnimationEnd(Animator anim) {
                                         container.endViewTransition(view);
-                                        if (fragment.mAnimatingAway != null) {
-                                            fragment.mAnimatingAway = null;
-                                            moveToState(fragment, fragment.mStateAfterAnimating,
+                                        if (fragment.getAnimatingAway() != null) {
+                                            fragment.setAnimatingAway(null);
+                                            moveToState(fragment, fragment.getStateAfterAnimating(),
                                                     0, 0, false);
                                         }
                                     }
@@ -1110,24 +1115,24 @@
                 case Fragment.CREATED:
                     if (newState < Fragment.CREATED) {
                         if (mDestroyed) {
-                            if (f.mAnimatingAway != null) {
+                            if (f.getAnimatingAway() != null) {
                                 // The fragment's containing activity is
                                 // being destroyed, but this fragment is
                                 // currently animating away.  Stop the
                                 // animation right now -- it is not needed,
                                 // and we can't wait any more on destroying
                                 // the fragment.
-                                Animator anim = f.mAnimatingAway;
-                                f.mAnimatingAway = null;
+                                Animator anim = f.getAnimatingAway();
+                                f.setAnimatingAway(null);
                                 anim.cancel();
                             }
                         }
-                        if (f.mAnimatingAway != null) {
+                        if (f.getAnimatingAway() != null) {
                             // We are waiting for the fragment's view to finish
                             // animating away.  Just make a note of the state
                             // the fragment now should move to once the animation
                             // is done.
-                            f.mStateAfterAnimating = newState;
+                            f.setStateAfterAnimating(newState);
                             newState = Fragment.CREATED;
                         } else {
                             if (DEBUG) Log.v(TAG, "movefrom CREATED: " + f);
@@ -1175,8 +1180,8 @@
      */
     void completeShowHideFragment(final Fragment fragment) {
         if (fragment.mView != null) {
-            Animator anim = loadAnimator(fragment, fragment.mNextTransition, !fragment.mHidden,
-                    fragment.mNextTransitionStyle);
+            Animator anim = loadAnimator(fragment, fragment.getNextTransition(), !fragment.mHidden,
+                    fragment.getNextTransitionStyle());
             if (anim != null) {
                 anim.setTarget(fragment.mView);
                 if (fragment.mHidden) {
@@ -1212,7 +1217,7 @@
      *
      * @param f The fragment to change.
      */
-    void moveFragmentToExpectedState(Fragment f) {
+    void moveFragmentToExpectedState(final Fragment f) {
         if (f == null) {
             return;
         }
@@ -1224,9 +1229,11 @@
                 nextState = Fragment.INITIALIZING;
             }
         }
-        moveToState(f, nextState, f.mNextTransition, f.mNextTransitionStyle, false);
+
+        moveToState(f, nextState, f.getNextTransition(), f.getNextTransitionStyle(), false);
 
         if (f.mView != null) {
+            // Move the view if it is out of order
             Fragment underFragment = findFragmentUnder(f);
             if (underFragment != null) {
                 final View underView = underFragment.mView;
@@ -1239,6 +1246,18 @@
                     container.addView(f.mView, underIndex);
                 }
             }
+            if (f.mIsNewlyAdded && f.mContainer != null) {
+                // Make it visible and run the animations
+                f.mView.setVisibility(View.VISIBLE);
+                f.mIsNewlyAdded = false;
+                // run animations:
+                Animator anim = loadAnimator(f, f.getNextTransition(), true, f.getNextTransitionStyle());
+                if (anim != null) {
+                    anim.setTarget(f.mView);
+                    setHWLayerAnimListenerIfAlpha(f.mView, anim);
+                    anim.start();
+                }
+            }
         }
         if (f.mHiddenChanged) {
             completeShowHideFragment(f);
@@ -1270,7 +1289,7 @@
             final int numActive = mActive.size();
             for (int i = 0; i < numActive; i++) {
                 Fragment f = mActive.get(i);
-                if (f != null && (f.mRemoving || f.mDetached)) {
+                if (f != null && (f.mRemoving || f.mDetached) && !f.mIsNewlyAdded) {
                     moveFragmentToExpectedState(f);
                     if (f.mLoaderManager != null) {
                         loadersRunning |= f.mLoaderManager.hasRunningLoaders();
@@ -1288,7 +1307,7 @@
             }
         }
     }
-    
+
     void startPendingDeferredFragments() {
         if (mActive == null) return;
 
@@ -1539,7 +1558,22 @@
                 mPendingActions = new ArrayList<>();
             }
             mPendingActions.add(action);
-            if (mPendingActions.size() == 1) {
+            scheduleCommit();
+        }
+    }
+
+    /**
+     * Schedules the execution when one hasn't been scheduled already. This should happen
+     * the first time {@link #enqueueAction(OpGenerator, boolean)} is called or when
+     * a postponed transaction has been started with
+     * {@link Fragment#startPostponedEnterTransition()}
+     */
+    private void scheduleCommit() {
+        synchronized (this) {
+            boolean postponeReady =
+                    mPostponedTransactions != null && !mPostponedTransactions.isEmpty();
+            boolean pendingReady = mPendingActions != null && mPendingActions.size() == 1;
+            if (postponeReady || pendingReady) {
                 mHost.getHandler().removeCallbacks(mExecCommit);
                 mHost.getHandler().post(mExecCommit);
             }
@@ -1625,6 +1659,7 @@
             mTmpRecords = new ArrayList<>();
             mTmpIsPop = new ArrayList<>();
         }
+        executePostponedTransaction(null, null);
     }
 
     public void execSingleAction(OpGenerator action, boolean allowStateLoss) {
@@ -1674,6 +1709,40 @@
     }
 
     /**
+     * Complete the execution of transactions that have previously been postponed, but are
+     * now ready.
+     */
+    private void executePostponedTransaction(ArrayList<BackStackRecord> records,
+            ArrayList<Boolean> isRecordPop) {
+        int numPostponed = mPostponedTransactions == null ? 0 : mPostponedTransactions.size();
+        for (int i = 0; i < numPostponed; i++) {
+            StartEnterTransitionListener listener = mPostponedTransactions.get(i);
+            if (records != null && !listener.mIsBack) {
+                int index = records.indexOf(listener.mRecord);
+                if (index != -1 && isRecordPop.get(index)) {
+                    listener.cancelTransaction();
+                    continue;
+                }
+            }
+            if (listener.isReady() || (records != null &&
+                    listener.mRecord.interactsWith(records, 0, records.size()))) {
+                mPostponedTransactions.remove(i);
+                i--;
+                numPostponed--;
+                int index;
+                if (records != null && !listener.mIsBack &&
+                        (index = records.indexOf(listener.mRecord)) != -1 &&
+                        isRecordPop.get(index)) {
+                    // This is popping a postponed transaction
+                    listener.cancelTransaction();
+                } else {
+                    listener.completeTransaction();
+                }
+            }
+        }
+    }
+
+    /**
      * Optimizes BackStackRecord operations. This method merges operations of proximate records
      * that allow optimization. See {@link FragmentTransaction#setAllowOptimization(boolean)}.
      * <p>
@@ -1696,6 +1765,9 @@
             throw new IllegalStateException("Internal error with the back stack records");
         }
 
+        // Force start of any postponed transactions that interact with scheduled transactions:
+        executePostponedTransaction(records, isRecordPop);
+
         final int numRecords = records.size();
         int startIndex = 0;
         for (int recordNum = 0; recordNum < numRecords; recordNum++) {
@@ -1736,10 +1808,8 @@
         boolean addToBackStack = false;
         if (mTmpAddedFragments == null) {
             mTmpAddedFragments = new ArrayList<>();
-            mTmpFragmentsContainerTransitions = new SparseArray<>();
         } else {
             mTmpAddedFragments.clear();
-            mTmpFragmentsContainerTransitions.clear();
         }
         if (mAdded != null) {
             mTmpAddedFragments.addAll(mAdded);
@@ -1753,25 +1823,26 @@
             final int bumpAmount = isPop ? -1 : 1;
             record.bumpBackStackNesting(bumpAmount);
             addToBackStack = addToBackStack || record.mAddToBackStack;
-
-            if (mCurState >= Fragment.CREATED) {
-                if (isPop) {
-                    record.calculatePopFragments(mTmpFragmentsContainerTransitions);
-                } else {
-                    record.calculateFragments(mTmpFragmentsContainerTransitions);
-                }
-            }
         }
         mTmpAddedFragments.clear();
 
         if (!allowOptimization) {
-            startTransitions(records, isRecordPop, startIndex, endIndex);
+            FragmentTransition.startTransitions(this, records, isRecordPop, startIndex, endIndex,
+                    false);
         }
         executeOps(records, isRecordPop, startIndex, endIndex);
 
+        int postponeIndex = endIndex;
         if (allowOptimization) {
-            moveFragmentsToAtLeastCreated();
-            startTransitions(records, isRecordPop, startIndex, endIndex);
+            moveFragmentsToInvisible();
+            postponeIndex = postponePostponableTransactions(records, isRecordPop,
+                    startIndex, endIndex);
+        }
+
+        if (postponeIndex != startIndex && allowOptimization) {
+            // need to run something now
+            FragmentTransition.startTransitions(this, records, isRecordPop, startIndex,
+                    postponeIndex, true);
             moveToState(mCurState);
         }
 
@@ -1783,12 +1854,104 @@
                 record.mIndex = -1;
             }
         }
+
         if (addToBackStack) {
             reportBackStackChanged();
         }
     }
 
     /**
+     * Examine all transactions and determine which ones are marked as postponed. Those will
+     * have their operations rolled back and moved to the end of the record list (up to endIndex).
+     * It will also add the postponed transaction to the queue.
+     *
+     * @param records A list of BackStackRecords that should be checked.
+     * @param isRecordPop The direction that these records are being run.
+     * @param startIndex The index of the first record in <code>records</code> to be checked
+     * @param endIndex One more than the final record index in <code>records</code> to be checked.
+     * @return The index of the first postponed transaction or endIndex if no transaction was
+     * postponed.
+     */
+    private int postponePostponableTransactions(ArrayList<BackStackRecord> records,
+            ArrayList<Boolean> isRecordPop, int startIndex, int endIndex) {
+        int postponeIndex = endIndex;
+        for (int i = endIndex - 1; i >= startIndex; i--) {
+            final BackStackRecord record = records.get(i);
+            final boolean isPop = isRecordPop.get(i);
+            boolean isPostponed = record.isPostponed() &&
+                    !record.interactsWith(records, i + 1, endIndex);
+            if (isPostponed) {
+                if (mPostponedTransactions == null) {
+                    mPostponedTransactions = new ArrayList<>();
+                }
+                StartEnterTransitionListener listener =
+                        new StartEnterTransitionListener(record, isPop);
+                mPostponedTransactions.add(listener);
+                record.setOnStartPostponedListener(listener);
+
+                // roll back the transaction
+                if (isPop) {
+                    record.executeOps();
+                } else {
+                    record.executePopOps();
+                }
+
+                // move to the end
+                postponeIndex--;
+                if (i != postponeIndex) {
+                    records.remove(i);
+                    records.add(postponeIndex, record);
+                }
+
+                // different views may be visible now
+                moveFragmentsToInvisible();
+            }
+        }
+        return postponeIndex;
+    }
+
+    /**
+     * When a postponed transaction is ready to be started, this completes the transaction,
+     * removing, hiding, or showing views as well as starting the animations and transitions.
+     * <p>
+     * {@code runtransitions} is set to false when the transaction postponement was interrupted
+     * abnormally -- normally by a new transaction being started that affects the postponed
+     * transaction.
+     *
+     * @param record The transaction to run
+     * @param isPop true if record is popping or false if it is adding
+     * @param runTransitions true if the fragment transition should be run or false otherwise.
+     * @param moveToState true if the state should be changed after executing the operations.
+     *                    This is false when the transaction is canceled when a postponed
+     *                    transaction is popped.
+     */
+    private void completeExecute(BackStackRecord record, boolean isPop, boolean runTransitions,
+            boolean moveToState) {
+        ArrayList<BackStackRecord> records = new ArrayList<>(1);
+        ArrayList<Boolean> isRecordPop = new ArrayList<>(1);
+        records.add(record);
+        isRecordPop.add(isPop);
+        executeOps(records, isRecordPop, 0, 1);
+        if (runTransitions) {
+            FragmentTransition.startTransitions(this, records, isRecordPop, 0, 1, true);
+        }
+        if (moveToState) {
+            moveToState(mCurState);
+        } else if (mActive != null) {
+            final int numActive = mActive.size();
+            for (int i = 0; i < numActive; i++) {
+                // Allow added fragments to be removed during the pop since we aren't going
+                // to move them to the final state with moveToState(mCurState).
+                Fragment fragment = mActive.get(i);
+                if (fragment.mView != null && fragment.mIsNewlyAdded &&
+                        record.interactsWith(fragment.mContainerId)) {
+                    fragment.mIsNewlyAdded = false;
+                }
+            }
+        }
+    }
+
+    /**
      * Find a fragment within the fragment's container whose View should be below the passed
      * fragment. {@code null} is returned when the fragment has no View or if there should be
      * no fragment with a View below the given fragment.
@@ -1845,61 +2008,50 @@
     }
 
     /**
-     * Prepares the fragments for Transitions and starts it. If the FragmentManager is not
-     * at Fragment.CREATED, no transition will be run.
-     *
-     * This is explicitly for {@link Fragment#setEnterTransition(Transition)} and its siblings,
-     * not for {@link FragmentTransaction#setTransition(int)}.
-     *
-     * @param records The entries to examine for transitions.
-     * @param isRecordPop The direction that these records are being run.
-     * @param startIndex The index of the first entry in records to run transitions for.
-     * @param endIndex One past the index of the final entry in records to run transitions for.
+     * Ensure that fragments that are added are moved to at least the CREATED state.
+     * Any newly-added Views are made INVISIBLE so that the Transaction can be postponed
+     * with {@link Fragment#postponeEnterTransition()}.
      */
-    private void startTransitions(ArrayList<BackStackRecord> records,
-            ArrayList<Boolean> isRecordPop, int startIndex, int endIndex) {
-        if (mCurState >= Fragment.CREATED) {
-            if (mTmpFragmentsContainerTransitions.size() != 0) {
-                BackStackRecord record = records.get(0);
-                BackStackRecord.TransitionState state =
-                        record.beginTransition(mTmpFragmentsContainerTransitions);
-                if (state != null) {
-                    for (int i = startIndex + 1; i < endIndex - 1; i++) {
-                        final BackStackRecord nameRecord = records.get(i);
-                        final boolean isPop = isRecordPop.get(i);
-                        ArrayList<String> sourceNames = isPop
-                                ? nameRecord.mSharedElementTargetNames
-                                : record.mSharedElementSourceNames;
-                        ArrayList<String> targetNames = isPop
-                                ? nameRecord.mSharedElementSourceNames
-                                : record.mSharedElementTargetNames;
-                        BackStackRecord.setNameOverrides(state, sourceNames, targetNames);
-                    }
+    private void moveFragmentsToInvisible() {
+        if (mCurState < Fragment.CREATED) {
+            return;
+        }
+        // We want to leave the fragment in the started state
+        final int state = Math.min(mCurState, Fragment.STARTED);
+        final int numAdded = mAdded == null ? 0 : mAdded.size();
+        for (int i = 0; i < numAdded; i++) {
+            Fragment fragment = mAdded.get(i);
+            if (fragment.mState < state) {
+                moveToState(fragment, state, fragment.getNextAnim(), fragment.getNextTransition(), false);
+                if (fragment.mView != null && !fragment.mHidden && fragment.mIsNewlyAdded) {
+                    fragment.mView.setVisibility(View.INVISIBLE);
                 }
-                mTmpFragmentsContainerTransitions.clear();
             }
         }
     }
 
     /**
-     * Ensure that fragments that are added are at least at the CREATED state
-     * so that they may load Transitions using TransitionInflater. When the transaction
-     * cannot be optimized, this is executed in
-     * {@link BackStackRecord#setLastIn(SparseArray, SparseArray, Fragment)} instead. Prior to
-     * N, this wasn't supported, so no out-of-order creation can be done for compatibility.
-     * <p>
-     * This won't change the state of the fragment manager, nor will it change the fragment's
-     * state if the fragment manager isn't at least at the CREATED state.
+     * Starts all postponed transactions regardless of whether they are ready or not.
      */
-    private void moveFragmentsToAtLeastCreated() {
-        if (mCurState < Fragment.CREATED) {
-            return;
+    private void forcePostponedTransactions() {
+        if (mPostponedTransactions != null) {
+            while (!mPostponedTransactions.isEmpty()) {
+                mPostponedTransactions.remove(0).completeTransaction();
+            }
         }
-        final int numAdded = mAdded == null ? 0 : mAdded.size();
-        for (int i = 0; i < numAdded; i++) {
-            Fragment fragment = mAdded.get(i);
-            if (fragment.mState < Fragment.CREATED) {
-                moveToState(fragment, Fragment.CREATED, 0, 0, false);
+    }
+
+    /**
+     * Ends the animations of fragments so that they immediately reach the end state.
+     * This is used prior to saving the state so that the correct state is saved.
+     */
+    private void endAnimatingAwayFragments() {
+        final int numFragments = mActive == null ? 0 : mActive.size();
+        for (int i = 0; i < numFragments; i++) {
+            Fragment fragment = mActive.get(i);
+            if (fragment != null && fragment.getAnimatingAway() != null) {
+                // Give up waiting for the animation and just end it.
+                fragment.getAnimatingAway().end();
             }
         }
     }
@@ -2114,6 +2266,8 @@
     Parcelable saveAllState() {
         // Make sure all pending operations have now been executed to get
         // our state update-to-date.
+        forcePostponedTransactions();
+        endAnimatingAwayFragments();
         execPendingActions();
 
         mStateSaved = true;
@@ -2723,4 +2877,80 @@
             return popBackStackState(records, isRecordPop, mName, mId, mFlags);
         }
     }
+
+    /**
+     * A listener for a postponed transaction. This waits until
+     * {@link Fragment#startPostponedEnterTransition()} is called or a transaction is started
+     * that interacts with this one, based on interactions with the fragment container.
+     */
+    static class StartEnterTransitionListener
+            implements Fragment.OnStartEnterTransitionListener {
+        private final boolean mIsBack;
+        private final BackStackRecord mRecord;
+        private int mNumPostponed;
+
+        public StartEnterTransitionListener(BackStackRecord record, boolean isBack) {
+            mIsBack = isBack;
+            mRecord = record;
+        }
+
+        /**
+         * Called from {@link Fragment#startPostponedEnterTransition()}, this decreases the
+         * number of Fragments that are postponed. This may cause the transaction to schedule
+         * to finish running and run transitions and animations.
+         */
+        @Override
+        public void onStartEnterTransition() {
+            mNumPostponed--;
+            if (mNumPostponed != 0) {
+                return;
+            }
+            mRecord.mManager.scheduleCommit();
+        }
+
+        /**
+         * Called from {@link Fragment#
+         * setOnStartEnterTransitionListener(Fragment.OnStartEnterTransitionListener)}, this
+         * increases the number of fragments that are postponed as part of this transaction.
+         */
+        @Override
+        public void startListening() {
+            mNumPostponed++;
+        }
+
+        /**
+         * @return true if there are no more postponed fragments as part of the transaction.
+         */
+        public boolean isReady() {
+            return mNumPostponed == 0;
+        }
+
+        /**
+         * Completes the transaction and start the animations and transitions. This may skip
+         * the transitions if this is called before all fragments have called
+         * {@link Fragment#startPostponedEnterTransition()}.
+         */
+        public void completeTransaction() {
+            final boolean canceled;
+            canceled = mNumPostponed > 0;
+            FragmentManagerImpl manager = mRecord.mManager;
+            final int numAdded = manager.mAdded.size();
+            for (int i = 0; i < numAdded; i++) {
+                final Fragment fragment = manager.mAdded.get(i);
+                fragment.setOnStartEnterTransitionListener(null);
+                if (canceled && fragment.isPostponed()) {
+                    fragment.startPostponedEnterTransition();
+                }
+            }
+            mRecord.mManager.completeExecute(mRecord, mIsBack, !canceled, true);
+        }
+
+        /**
+         * Cancels this transaction instead of completing it. That means that the state isn't
+         * changed, so the pop results in no change to the state.
+         */
+        public void cancelTransaction() {
+            mRecord.mManager.completeExecute(mRecord, mIsBack, false, false);
+        }
+    }
 }
diff --git a/core/java/android/app/FragmentTransition.java b/core/java/android/app/FragmentTransition.java
new file mode 100644
index 0000000..6f52114
--- /dev/null
+++ b/core/java/android/app/FragmentTransition.java
@@ -0,0 +1,1330 @@
+/*
+ * Copyright (C) 2016 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 android.app;
+
+import android.graphics.Rect;
+import android.os.Build;
+import android.transition.Transition;
+import android.transition.TransitionManager;
+import android.transition.TransitionSet;
+import android.util.ArrayMap;
+import android.util.SparseArray;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Contains the Fragment Transition functionality for both optimized and unoptimized
+ * Fragment Transactions. With optimized fragment transactions, all Views have been
+ * added to the View hierarchy prior to calling startTransitions. With
+ */
+class FragmentTransition {
+    /**
+     * The inverse of all BackStackRecord operation commands. This assumes that
+     * REPLACE operations have already been replaced by add/remove operations.
+     */
+    private static final int[] INVERSE_OPS = {
+            BackStackRecord.OP_NULL,   // inverse of OP_NULL (error)
+            BackStackRecord.OP_REMOVE, // inverse of OP_ADD
+            BackStackRecord.OP_NULL,   // inverse of OP_REPLACE (error)
+            BackStackRecord.OP_ADD,    // inverse of OP_REMOVE
+            BackStackRecord.OP_SHOW,   // inverse of OP_HIDE
+            BackStackRecord.OP_HIDE,   // inverse of OP_SHOW
+            BackStackRecord.OP_ATTACH, // inverse of OP_DETACH
+            BackStackRecord.OP_DETACH, // inverse of OP_ATTACH
+    };
+
+    /**
+     * The main entry point for Fragment Transitions, this starts the transitions
+     * set on the leaving Fragment's {@link Fragment#getExitTransition()}, the
+     * entering Fragment's {@link Fragment#getEnterTransition()} and
+     * {@link Fragment#getSharedElementEnterTransition()}. When popping,
+     * the leaving Fragment's {@link Fragment#getReturnTransition()} and
+     * {@link Fragment#getSharedElementReturnTransition()} and the entering
+     * {@link Fragment#getReenterTransition()} will be run.
+     * <p>
+     * With optimized Fragment Transitions, all Views have been added to the
+     * View hierarchy prior to calling this method. The incoming Fragment's Views
+     * will be INVISIBLE. With unoptimized Fragment Transitions, this method
+     * is called before any change has been made to the hierarchy. That means
+     * that the added Fragments have not created their Views yet and the hierarchy
+     * is unknown.
+     *
+     * @param fragmentManager The executing FragmentManagerImpl
+     * @param records The list of transactions being executed.
+     * @param isRecordPop For each transaction, whether it is a pop transaction or not.
+     * @param startIndex The first index into records and isRecordPop to execute as
+     *                   part of this transition.
+     * @param endIndex One past the last index into records and isRecordPop to execute
+     *                 as part of this transition.
+     * @param isOptimized true if this is an optimized transaction, meaning that the
+     *                    Views of incoming fragments have been added. false if the
+     *                    transaction has yet to be run and Views haven't been created.
+     */
+    static void startTransitions(FragmentManagerImpl fragmentManager,
+            ArrayList<BackStackRecord> records, ArrayList<Boolean> isRecordPop,
+            int startIndex, int endIndex, boolean isOptimized) {
+        if (fragmentManager.mCurState < Fragment.CREATED) {
+            return;
+        }
+        SparseArray<FragmentContainerTransition> transitioningFragments =
+                new SparseArray<>();
+        for (int i = startIndex; i < endIndex; i++) {
+            final BackStackRecord record = records.get(i);
+            final boolean isPop = isRecordPop.get(i);
+            if (isPop) {
+                calculatePopFragments(record, transitioningFragments, isOptimized);
+            } else {
+                calculateFragments(record, transitioningFragments, isOptimized);
+            }
+        }
+
+        if (transitioningFragments.size() != 0) {
+            final View nonExistentView = new View(fragmentManager.mHost.getContext());
+            final int numContainers = transitioningFragments.size();
+            for (int i = 0; i < numContainers; i++) {
+                int containerId = transitioningFragments.keyAt(i);
+                ArrayMap<String, String> nameOverrides = calculateNameOverrides(containerId,
+                        records, isRecordPop, startIndex, endIndex);
+
+                FragmentContainerTransition containerTransition = transitioningFragments.valueAt(i);
+
+                if (isOptimized) {
+                    configureTransitionsOptimized(fragmentManager, containerId,
+                            containerTransition, nonExistentView, nameOverrides);
+                } else {
+                    configureTransitionsUnoptimized(fragmentManager, containerId,
+                            containerTransition, nonExistentView, nameOverrides);
+                }
+            }
+        }
+    }
+
+    /**
+     * Iterates through the transactions that affect a given fragment container
+     * and tracks the shared element names across transactions. This is most useful
+     * in pop transactions where the names of shared elements are known.
+     *
+     * @param containerId The container ID that is executing the transition.
+     * @param records The list of transactions being executed.
+     * @param isRecordPop For each transaction, whether it is a pop transaction or not.
+     * @param startIndex The first index into records and isRecordPop to execute as
+     *                   part of this transition.
+     * @param endIndex One past the last index into records and isRecordPop to execute
+     *                 as part of this transition.
+     * @return A map from the initial shared element name to the final shared element name
+     * before any onMapSharedElements is run.
+     */
+    private static ArrayMap<String, String> calculateNameOverrides(int containerId,
+            ArrayList<BackStackRecord> records, ArrayList<Boolean> isRecordPop,
+            int startIndex, int endIndex) {
+        ArrayMap<String, String> nameOverrides = new ArrayMap<>();
+        for (int recordNum = endIndex - 1; recordNum >= startIndex; recordNum--) {
+            final BackStackRecord record = records.get(recordNum);
+            if (!record.interactsWith(containerId)) {
+                continue;
+            }
+            final boolean isPop = isRecordPop.get(recordNum);
+            if (record.mSharedElementSourceNames != null) {
+                final int numSharedElements = record.mSharedElementSourceNames.size();
+                final ArrayList<String> sources;
+                final ArrayList<String> targets;
+                if (isPop) {
+                    targets = record.mSharedElementSourceNames;
+                    sources = record.mSharedElementTargetNames;
+                } else {
+                    sources = record.mSharedElementSourceNames;
+                    targets = record.mSharedElementTargetNames;
+                }
+                for (int i = 0; i < numSharedElements; i++) {
+                    String sourceName = sources.get(i);
+                    String targetName = targets.get(i);
+                    String previousTarget = nameOverrides.remove(targetName);
+                    if (previousTarget != null) {
+                        nameOverrides.put(sourceName, previousTarget);
+                    } else {
+                        nameOverrides.put(sourceName, targetName);
+                    }
+                }
+            }
+        }
+        return nameOverrides;
+    }
+
+    /**
+     * Configures a transition for a single fragment container for which the transaction was
+     * optimized. That means that all Fragment Views have been added and incoming fragment
+     * Views are marked invisible.
+     *
+     * @param fragmentManager The executing FragmentManagerImpl
+     * @param containerId The container ID that is executing the transition.
+     * @param fragments A structure holding the transitioning fragments in this container.
+     * @param nonExistentView A View that does not exist in the hierarchy. This is used to
+     *                        prevent transitions from acting on other Views when there is no
+     *                        other target.
+     * @param nameOverrides A map of the shared element names from the starting fragment to
+     *                      the final fragment's Views as given in
+     *                      {@link FragmentTransaction#addSharedElement(View, String)}.
+     */
+    private static void configureTransitionsOptimized(FragmentManagerImpl fragmentManager,
+            int containerId, FragmentContainerTransition fragments,
+            View nonExistentView, ArrayMap<String, String> nameOverrides) {
+        ViewGroup sceneRoot = (ViewGroup) fragmentManager.mContainer.onFindViewById(containerId);
+        if (sceneRoot == null) {
+            return;
+        }
+        final Fragment inFragment = fragments.lastIn;
+        final Fragment outFragment = fragments.firstOut;
+        final boolean inIsPop = fragments.lastInIsPop;
+        final boolean outIsPop = fragments.firstOutIsPop;
+
+        ArrayList<View> sharedElementsIn = new ArrayList<>();
+        ArrayList<View> sharedElementsOut = new ArrayList<>();
+        Transition enterTransition = getEnterTransition(inFragment, inIsPop);
+        Transition exitTransition = getExitTransition(outFragment, outIsPop);
+
+        TransitionSet sharedElementTransition = configureSharedElementsOptimized(sceneRoot,
+                nonExistentView, nameOverrides, fragments, sharedElementsOut, sharedElementsIn,
+                enterTransition, exitTransition);
+
+        if (enterTransition == null && sharedElementTransition == null &&
+                exitTransition == null) {
+            return; // no transitions!
+        }
+
+        ArrayList<View> exitingViews = configureEnteringExitingViews(exitTransition,
+                outFragment, sharedElementsOut, nonExistentView);
+
+        ArrayList<View> enteringViews = configureEnteringExitingViews(enterTransition,
+                inFragment, sharedElementsIn, nonExistentView);
+
+        setViewVisibility(enteringViews, View.INVISIBLE);
+
+        Transition transition = mergeTransitions(enterTransition, exitTransition,
+                sharedElementTransition, inFragment, inIsPop);
+
+        if (transition != null) {
+            transition.setNameOverrides(nameOverrides);
+            scheduleRemoveTargets(transition,
+                    enterTransition, enteringViews, exitTransition, exitingViews,
+                    sharedElementTransition, sharedElementsIn);
+            TransitionManager.beginDelayedTransition(sceneRoot, transition);
+            setViewVisibility(enteringViews, View.VISIBLE);
+            // Swap the shared element targets
+            if (sharedElementTransition != null) {
+                sharedElementTransition.getTargets().clear();
+                sharedElementTransition.getTargets().addAll(sharedElementsIn);
+                replaceTargets(sharedElementTransition, sharedElementsOut, sharedElementsIn);
+            }
+        }
+    }
+
+    /**
+     * Configures a transition for a single fragment container for which the transaction was
+     * not optimized. That means that the transaction has not been executed yet, so incoming
+     * Views are not yet known.
+     *
+     * @param fragmentManager The executing FragmentManagerImpl
+     * @param containerId The container ID that is executing the transition.
+     * @param fragments A structure holding the transitioning fragments in this container.
+     * @param nonExistentView A View that does not exist in the hierarchy. This is used to
+     *                        prevent transitions from acting on other Views when there is no
+     *                        other target.
+     * @param nameOverrides A map of the shared element names from the starting fragment to
+     *                      the final fragment's Views as given in
+     *                      {@link FragmentTransaction#addSharedElement(View, String)}.
+     */
+    private static void configureTransitionsUnoptimized(FragmentManagerImpl fragmentManager,
+            int containerId, FragmentContainerTransition fragments,
+            View nonExistentView, ArrayMap<String, String> nameOverrides) {
+        ViewGroup sceneRoot = (ViewGroup) fragmentManager.mContainer.onFindViewById(containerId);
+        if (sceneRoot == null) {
+            return;
+        }
+        final Fragment inFragment = fragments.lastIn;
+        final Fragment outFragment = fragments.firstOut;
+        final boolean inIsPop = fragments.lastInIsPop;
+        final boolean outIsPop = fragments.firstOutIsPop;
+
+        Transition enterTransition = getEnterTransition(inFragment, inIsPop);
+        Transition exitTransition = getExitTransition(outFragment, outIsPop);
+
+        ArrayList<View> sharedElementsOut = new ArrayList<>();
+        ArrayList<View> sharedElementsIn = new ArrayList<>();
+
+        TransitionSet sharedElementTransition = configureSharedElementsUnoptimized(sceneRoot,
+                nonExistentView, nameOverrides, fragments, sharedElementsOut, sharedElementsIn,
+                enterTransition, exitTransition);
+
+        if (enterTransition == null && sharedElementTransition == null &&
+                exitTransition == null) {
+            return; // no transitions!
+        }
+
+        ArrayList<View> exitingViews = configureEnteringExitingViews(exitTransition,
+                outFragment, sharedElementsOut, nonExistentView);
+
+        if (exitingViews == null || exitingViews.isEmpty()) {
+            exitTransition = null;
+        }
+
+        if (enterTransition != null) {
+            // Ensure the entering transition doesn't target anything until the views are made
+            // visible
+            enterTransition.addTarget(nonExistentView);
+        }
+
+        Transition transition = mergeTransitions(enterTransition, exitTransition,
+                sharedElementTransition, inFragment, fragments.lastInIsPop);
+
+        if (transition != null) {
+            transition.setNameOverrides(nameOverrides);
+            final ArrayList<View> enteringViews = new ArrayList<>();
+            scheduleRemoveTargets(transition,
+                    enterTransition, enteringViews, exitTransition, exitingViews,
+                    sharedElementTransition, sharedElementsIn);
+            scheduleTargetChange(sceneRoot, inFragment, nonExistentView, sharedElementsIn,
+                    enterTransition, enteringViews, exitTransition, exitingViews);
+
+            TransitionManager.beginDelayedTransition(sceneRoot, transition);
+        }
+    }
+
+    /**
+     * This method is used for fragment transitions for unoptimized transactions to change the
+     * enter and exit transition targets after the call to
+     * {@link TransitionManager#beginDelayedTransition(ViewGroup, Transition)}. The exit transition
+     * must ensure that it does not target any Views and the enter transition must start targeting
+     * the Views of the incoming Fragment.
+     *
+     * @param sceneRoot The fragment container View
+     * @param inFragment The last fragment that is entering
+     * @param nonExistentView A view that does not exist in the hierarchy that is used as a
+     *                        transition target to ensure no View is targeted.
+     * @param sharedElementsIn The shared element Views of the incoming fragment
+     * @param enterTransition The enter transition of the incoming fragment
+     * @param enteringViews The entering Views of the incoming fragment
+     * @param exitTransition The exit transition of the outgoing fragment
+     * @param exitingViews The exiting views of the outgoing fragment
+     */
+    private static void scheduleTargetChange(final ViewGroup sceneRoot,
+            final Fragment inFragment, final View nonExistentView,
+            final ArrayList<View> sharedElementsIn,
+            final Transition enterTransition, final ArrayList<View> enteringViews,
+            final Transition exitTransition, final ArrayList<View> exitingViews) {
+
+        sceneRoot.getViewTreeObserver().addOnPreDrawListener(
+                new ViewTreeObserver.OnPreDrawListener() {
+                    @Override
+                    public boolean onPreDraw() {
+                        sceneRoot.getViewTreeObserver().removeOnPreDrawListener(this);
+
+                        if (enterTransition != null) {
+                            enterTransition.removeTarget(nonExistentView);
+                            ArrayList<View> views = configureEnteringExitingViews(
+                                    enterTransition, inFragment, sharedElementsIn, nonExistentView);
+                            enteringViews.addAll(views);
+                        }
+
+                        if (exitingViews != null) {
+                            ArrayList<View> tempExiting = new ArrayList<>();
+                            tempExiting.add(nonExistentView);
+                            replaceTargets(exitTransition, exitingViews, tempExiting);
+                            exitingViews.clear();
+                            exitingViews.add(nonExistentView);
+                        }
+
+                        return true;
+                    }
+                });
+    }
+
+    /**
+     * Returns a TransitionSet containing the shared element transition. The wrapping TransitionSet
+     * targets all shared elements to ensure that no other Views are targeted. The shared element
+     * transition can then target any or all shared elements without worrying about accidentally
+     * targeting entering or exiting Views.
+     *
+     * @param inFragment The incoming fragment
+     * @param outFragment the outgoing fragment
+     * @param isPop True if this is a pop transaction or false if it is a normal (add) transaction.
+     * @return A TransitionSet wrapping the shared element transition or null if no such transition
+     * exists.
+     */
+    private static TransitionSet getSharedElementTransition(Fragment inFragment,
+            Fragment outFragment, boolean isPop) {
+        if (inFragment == null || outFragment == null) {
+            return null;
+        }
+        Transition transition = cloneTransition(isPop
+                ? outFragment.getSharedElementReturnTransition()
+                : inFragment.getSharedElementEnterTransition());
+        if (transition == null) {
+            return null;
+        }
+        TransitionSet transitionSet = new TransitionSet();
+        transitionSet.addTransition(transition);
+        return transitionSet;
+    }
+
+    /**
+     * Returns a clone of the enter transition or null if no such transition exists.
+     */
+    private static Transition getEnterTransition(Fragment inFragment, boolean isPop) {
+        if (inFragment == null) {
+            return null;
+        }
+        return cloneTransition(isPop ? inFragment.getReenterTransition() :
+                inFragment.getEnterTransition());
+    }
+
+    /**
+     * Returns a clone of the exit transition or null if no such transition exists.
+     */
+    private static Transition getExitTransition(Fragment outFragment, boolean isPop) {
+        if (outFragment == null) {
+            return null;
+        }
+        return cloneTransition(isPop ? outFragment.getReturnTransition() :
+                outFragment.getExitTransition());
+    }
+
+    /**
+     * Returns a clone of a transition or null if it is null
+     */
+    private static Transition cloneTransition(Transition transition) {
+        if (transition != null) {
+            transition = transition.clone();
+        }
+        return transition;
+    }
+
+    /**
+     * Configures the shared elements of an optimized fragment transaction's transition.
+     * This retrieves the shared elements of the outgoing and incoming fragments, maps the
+     * views, and sets up the epicenter on the transitions.
+     * <p>
+     * The epicenter of exit and shared element transitions is the first shared element
+     * in the outgoing fragment. The epicenter of the entering transition is the first shared
+     * element in the incoming fragment.
+     *
+     * @param sceneRoot The fragment container View
+     * @param nonExistentView A View that does not exist in the hierarchy. This is used to
+     *                        prevent transitions from acting on other Views when there is no
+     *                        other target.
+     * @param nameOverrides A map of the shared element names from the starting fragment to
+     *                      the final fragment's Views as given in
+     *                      {@link FragmentTransaction#addSharedElement(View, String)}.
+     * @param fragments A structure holding the transitioning fragments in this container.
+     * @param sharedElementsOut A list modified to contain the shared elements in the outgoing
+     *                          fragment
+     * @param sharedElementsIn A list modified to contain the shared elements in the incoming
+     *                         fragment
+     * @param enterTransition The transition used for entering Views, modified by applying the
+     *                        epicenter
+     * @param exitTransition The transition used for exiting Views, modified by applying the
+     *                       epicenter
+     * @return The shared element transition or null if no shared elements exist
+     */
+    private static TransitionSet configureSharedElementsOptimized(final ViewGroup sceneRoot,
+            final View nonExistentView, ArrayMap<String, String> nameOverrides,
+            final FragmentContainerTransition fragments,
+            final ArrayList<View> sharedElementsOut,
+            final ArrayList<View> sharedElementsIn,
+            final Transition enterTransition, final Transition exitTransition) {
+        final Fragment inFragment = fragments.lastIn;
+        final Fragment outFragment = fragments.firstOut;
+        if (inFragment != null) {
+            inFragment.getView().setVisibility(View.VISIBLE);
+        }
+        if (inFragment == null || outFragment == null) {
+            return null; // no shared element without a fragment
+        }
+
+        final boolean inIsPop = fragments.lastInIsPop;
+        TransitionSet sharedElementTransition = nameOverrides.isEmpty() ? null
+                : getSharedElementTransition(inFragment, outFragment, inIsPop);
+
+        ArrayMap<String, View> outSharedElements = captureOutSharedElements(nameOverrides,
+                sharedElementTransition, fragments);
+
+        ArrayMap<String, View> inSharedElements = captureInSharedElements(nameOverrides,
+                sharedElementTransition, fragments);
+
+        if (nameOverrides.isEmpty()) {
+            sharedElementTransition = null;
+        } else {
+            sharedElementsOut.addAll(outSharedElements.values());
+            sharedElementsIn.addAll(inSharedElements.values());
+        }
+
+        if (enterTransition == null && exitTransition == null && sharedElementTransition == null) {
+            // don't call onSharedElementStart/End since there is no transition
+            return null;
+        }
+
+        callSharedElementStartEnd(inFragment, outFragment, inIsPop, outSharedElements, true);
+
+        final Rect epicenter;
+        final View epicenterView;
+        if (sharedElementTransition != null) {
+            sharedElementsIn.add(nonExistentView);
+            setSharedElementTargets(sharedElementTransition, nonExistentView, sharedElementsOut);
+            final boolean outIsPop = fragments.firstOutIsPop;
+            final BackStackRecord outTransaction = fragments.firstOutTransaction;
+            setOutEpicenter(sharedElementTransition, exitTransition, outSharedElements, outIsPop,
+                    outTransaction);
+            epicenter = new Rect();
+            epicenterView = getInEpicenterView(inSharedElements, fragments,
+                    enterTransition, inIsPop);
+            if (epicenterView != null) {
+                enterTransition.setEpicenterCallback(new Transition.EpicenterCallback() {
+                    @Override
+                    public Rect onGetEpicenter(Transition transition) {
+                        return epicenter;
+                    }
+                });
+            }
+        } else {
+            epicenter = null;
+            epicenterView = null;
+        }
+
+        sceneRoot.getViewTreeObserver().addOnPreDrawListener(
+                new ViewTreeObserver.OnPreDrawListener() {
+                    @Override
+                    public boolean onPreDraw() {
+                        sceneRoot.getViewTreeObserver().removeOnPreDrawListener(this);
+                        callSharedElementStartEnd(inFragment, outFragment, inIsPop,
+                                inSharedElements, false);
+                        if (epicenterView != null) {
+                            epicenterView.getBoundsOnScreen(epicenter);
+                        }
+                        return true;
+                    }
+                });
+        return sharedElementTransition;
+    }
+
+    /**
+     * Configures the shared elements of an unoptimized fragment transaction's transition.
+     * This retrieves the shared elements of the incoming fragments, and schedules capturing
+     * the incoming fragment's shared elements. It also maps the views, and sets up the epicenter
+     * on the transitions.
+     * <p>
+     * The epicenter of exit and shared element transitions is the first shared element
+     * in the outgoing fragment. The epicenter of the entering transition is the first shared
+     * element in the incoming fragment.
+     *
+     * @param sceneRoot The fragment container View
+     * @param nonExistentView A View that does not exist in the hierarchy. This is used to
+     *                        prevent transitions from acting on other Views when there is no
+     *                        other target.
+     * @param nameOverrides A map of the shared element names from the starting fragment to
+     *                      the final fragment's Views as given in
+     *                      {@link FragmentTransaction#addSharedElement(View, String)}.
+     * @param fragments A structure holding the transitioning fragments in this container.
+     * @param sharedElementsOut A list modified to contain the shared elements in the outgoing
+     *                          fragment
+     * @param sharedElementsIn A list modified to contain the shared elements in the incoming
+     *                         fragment
+     * @param enterTransition The transition used for entering Views, modified by applying the
+     *                        epicenter
+     * @param exitTransition The transition used for exiting Views, modified by applying the
+     *                       epicenter
+     * @return The shared element transition or null if no shared elements exist
+     */
+    private static TransitionSet configureSharedElementsUnoptimized(final ViewGroup sceneRoot,
+            final View nonExistentView, ArrayMap<String, String> nameOverrides,
+            final FragmentContainerTransition fragments,
+            final ArrayList<View> sharedElementsOut,
+            final ArrayList<View> sharedElementsIn,
+            final Transition enterTransition, final Transition exitTransition) {
+        final Fragment inFragment = fragments.lastIn;
+        final Fragment outFragment = fragments.firstOut;
+
+        if (inFragment == null || outFragment == null) {
+            return null; // no transition
+        }
+
+        final boolean inIsPop = fragments.lastInIsPop;
+        TransitionSet sharedElementTransition = nameOverrides.isEmpty() ? null
+                : getSharedElementTransition(inFragment, outFragment, inIsPop);
+
+        ArrayMap<String, View> outSharedElements = captureOutSharedElements(nameOverrides,
+                sharedElementTransition, fragments);
+
+        if (nameOverrides.isEmpty()) {
+            sharedElementTransition = null;
+        } else {
+            sharedElementsOut.addAll(outSharedElements.values());
+        }
+
+        if (enterTransition == null && exitTransition == null && sharedElementTransition == null) {
+            // don't call onSharedElementStart/End since there is no transition
+            return null;
+        }
+
+        callSharedElementStartEnd(inFragment, outFragment, inIsPop, outSharedElements, true);
+
+        final Rect inEpicenter;
+        if (sharedElementTransition != null) {
+            inEpicenter = new Rect();
+            setSharedElementTargets(sharedElementTransition, nonExistentView, sharedElementsOut);
+            final boolean outIsPop = fragments.firstOutIsPop;
+            final BackStackRecord outTransaction = fragments.firstOutTransaction;
+            setOutEpicenter(sharedElementTransition, exitTransition, outSharedElements, outIsPop,
+                    outTransaction);
+            if (enterTransition != null) {
+                enterTransition.setEpicenterCallback(new Transition.EpicenterCallback() {
+                    @Override
+                    public Rect onGetEpicenter(Transition transition) {
+                        if (inEpicenter.isEmpty()) {
+                            return null;
+                        }
+                        return inEpicenter;
+                    }
+                });
+            }
+        } else {
+            inEpicenter = null;
+        }
+
+        TransitionSet finalSharedElementTransition = sharedElementTransition;
+
+        sceneRoot.getViewTreeObserver().addOnPreDrawListener(
+                new ViewTreeObserver.OnPreDrawListener() {
+                    @Override
+                    public boolean onPreDraw() {
+                        sceneRoot.getViewTreeObserver().removeOnPreDrawListener(this);
+                        ArrayMap<String, View> inSharedElements = captureInSharedElements(
+                                nameOverrides, finalSharedElementTransition, fragments);
+
+                        if (inSharedElements != null) {
+                            sharedElementsIn.addAll(inSharedElements.values());
+                            sharedElementsIn.add(nonExistentView);
+                        }
+
+                        callSharedElementStartEnd(inFragment, outFragment, inIsPop,
+                                inSharedElements, false);
+                        if (finalSharedElementTransition != null) {
+                            finalSharedElementTransition.getTargets().clear();
+                            finalSharedElementTransition.getTargets().addAll(sharedElementsIn);
+                            replaceTargets(finalSharedElementTransition, sharedElementsOut,
+                                    sharedElementsIn);
+
+                            final View inEpicenterView = getInEpicenterView(inSharedElements,
+                                    fragments, enterTransition, inIsPop);
+                            if (inEpicenterView != null) {
+                                inEpicenterView.getBoundsOnScreen(inEpicenter);
+                            }
+                        }
+                        return true;
+                    }
+                });
+        return sharedElementTransition;
+    }
+
+    /**
+     * Finds the shared elements in the outgoing fragment. It also calls
+     * {@link SharedElementCallback#onMapSharedElements(List, Map)} to allow more control
+     * of the shared element mapping. {@code nameOverrides} is updated to match the
+     * actual transition name of the mapped shared elements.
+     *
+     * @param nameOverrides A map of the shared element names from the starting fragment to
+     *                      the final fragment's Views as given in
+     *                      {@link FragmentTransaction#addSharedElement(View, String)}.
+     * @param sharedElementTransition The shared element transition
+     * @param fragments A structure holding the transitioning fragments in this container.
+     * @return The mapping of shared element names to the Views in the hierarchy or null
+     * if there is no shared element transition.
+     */
+    private static ArrayMap<String, View> captureOutSharedElements(
+            ArrayMap<String, String> nameOverrides, TransitionSet sharedElementTransition,
+            FragmentContainerTransition fragments) {
+        if (nameOverrides.isEmpty() || sharedElementTransition == null) {
+            nameOverrides.clear();
+            return null;
+        }
+        final Fragment outFragment = fragments.firstOut;
+        final ArrayMap<String, View> outSharedElements = new ArrayMap<>();
+        outFragment.getView().findNamedViews(outSharedElements);
+
+        final SharedElementCallback sharedElementCallback;
+        final ArrayList<String> names;
+        final BackStackRecord outTransaction = fragments.firstOutTransaction;
+        if (fragments.firstOutIsPop) {
+            sharedElementCallback = outFragment.getEnterTransitionCallback();
+            names = outTransaction.mSharedElementTargetNames;
+        } else {
+            sharedElementCallback = outFragment.getExitTransitionCallback();
+            names = outTransaction.mSharedElementSourceNames;
+        }
+
+        outSharedElements.retainAll(names);
+        if (sharedElementCallback != null) {
+            sharedElementCallback.onMapSharedElements(names, outSharedElements);
+            for (int i = names.size() - 1; i >= 0; i--) {
+                String name = names.get(i);
+                View view = outSharedElements.get(name);
+                if (view == null) {
+                    nameOverrides.remove(name);
+                } else if (!name.equals(view.getTransitionName())) {
+                    String targetValue = nameOverrides.remove(name);
+                    nameOverrides.put(view.getTransitionName(), targetValue);
+                }
+            }
+        } else {
+            nameOverrides.retainAll(outSharedElements.keySet());
+        }
+        return outSharedElements;
+    }
+
+    /**
+     * Finds the shared elements in the incoming fragment. It also calls
+     * {@link SharedElementCallback#onMapSharedElements(List, Map)} to allow more control
+     * of the shared element mapping. {@code nameOverrides} is updated to match the
+     * actual transition name of the mapped shared elements.
+     *
+     * @param nameOverrides A map of the shared element names from the starting fragment to
+     *                      the final fragment's Views as given in
+     *                      {@link FragmentTransaction#addSharedElement(View, String)}.
+     * @param sharedElementTransition The shared element transition
+     * @param fragments A structure holding the transitioning fragments in this container.
+     * @return The mapping of shared element names to the Views in the hierarchy or null
+     * if there is no shared element transition.
+     */
+    private static ArrayMap<String, View> captureInSharedElements(
+            ArrayMap<String, String> nameOverrides, TransitionSet sharedElementTransition,
+            FragmentContainerTransition fragments) {
+        Fragment inFragment = fragments.lastIn;
+        final View fragmentView = inFragment.getView();
+        if (nameOverrides.isEmpty() || sharedElementTransition == null || fragmentView == null) {
+            nameOverrides.clear();
+            return null;
+        }
+        final ArrayMap<String, View> inSharedElements = new ArrayMap<>();
+        fragmentView.findNamedViews(inSharedElements);
+
+        final SharedElementCallback sharedElementCallback;
+        final ArrayList<String> names;
+        final BackStackRecord inTransaction = fragments.lastInTransaction;
+        if (fragments.lastInIsPop) {
+            sharedElementCallback = inFragment.getExitTransitionCallback();
+            names = inTransaction.mSharedElementSourceNames;
+        } else {
+            sharedElementCallback = inFragment.getEnterTransitionCallback();
+            names = inTransaction.mSharedElementTargetNames;
+        }
+
+        inSharedElements.retainAll(names);
+        if (sharedElementCallback != null) {
+            sharedElementCallback.onMapSharedElements(names, inSharedElements);
+            for (int i = names.size() - 1; i >= 0; i--) {
+                String name = names.get(i);
+                View view = inSharedElements.get(name);
+                if (view == null) {
+                    String key = findKeyForValue(nameOverrides, name);
+                    if (key != null) {
+                        nameOverrides.remove(key);
+                    }
+                } else if (!name.equals(view.getTransitionName())) {
+                    String key = findKeyForValue(nameOverrides, name);
+                    if (key != null) {
+                        nameOverrides.put(key, view.getTransitionName());
+                    }
+                }
+            }
+        } else {
+            retainValues(nameOverrides, inSharedElements);
+        }
+        return inSharedElements;
+    }
+
+    /**
+     * Utility to find the String key in {@code map} that maps to {@code value}.
+     */
+    private static String findKeyForValue(ArrayMap<String, String> map, String value) {
+        final int numElements = map.size();
+        for (int i = 0; i < numElements; i++) {
+            if (value.equals(map.valueAt(i))) {
+                return map.keyAt(i);
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns the View in the incoming Fragment that should be used as the epicenter.
+     *
+     * @param inSharedElements The mapping of shared element names to Views in the
+     *                         incoming fragment.
+     * @param fragments A structure holding the transitioning fragments in this container.
+     * @param enterTransition The transition used for the incoming Fragment's views
+     * @param inIsPop Is the incoming fragment being added as a pop transaction?
+     */
+    private static View getInEpicenterView(ArrayMap<String, View> inSharedElements,
+            FragmentContainerTransition fragments,
+            Transition enterTransition, boolean inIsPop) {
+        BackStackRecord inTransaction = fragments.lastInTransaction;
+        if (enterTransition != null && inTransaction.mSharedElementSourceNames != null &&
+                !inTransaction.mSharedElementSourceNames.isEmpty()) {
+            final String targetName = inIsPop
+                    ? inTransaction.mSharedElementSourceNames.get(0)
+                    : inTransaction.mSharedElementTargetNames.get(0);
+            return inSharedElements.get(targetName);
+        }
+        return null;
+    }
+
+    /**
+     * Sets the epicenter for the exit transition.
+     *
+     * @param sharedElementTransition The shared element transition
+     * @param exitTransition The transition for the outgoing fragment's views
+     * @param outSharedElements Shared elements in the outgoing fragment
+     * @param outIsPop Is the outgoing fragment being removed as a pop transaction?
+     * @param outTransaction The transaction that caused the fragment to be removed.
+     */
+    private static void setOutEpicenter(TransitionSet sharedElementTransition,
+            Transition exitTransition, ArrayMap<String, View> outSharedElements, boolean outIsPop,
+            BackStackRecord outTransaction) {
+        if (outTransaction.mSharedElementSourceNames != null &&
+                !outTransaction.mSharedElementSourceNames.isEmpty()) {
+            final String sourceName = outIsPop
+                    ? outTransaction.mSharedElementTargetNames.get(0)
+                    : outTransaction.mSharedElementSourceNames.get(0);
+            final View outEpicenterView = outSharedElements.get(sourceName);
+            setEpicenter(sharedElementTransition, outEpicenterView);
+
+            if (exitTransition != null) {
+                setEpicenter(exitTransition, outEpicenterView);
+            }
+        }
+    }
+
+    /**
+     * Sets a transition epicenter to the rectangle of a given View.
+     */
+    private static void setEpicenter(Transition transition, View view) {
+        if (view != null) {
+            final Rect epicenter = new Rect();
+            view.getBoundsOnScreen(epicenter);
+
+            transition.setEpicenterCallback(new Transition.EpicenterCallback() {
+                @Override
+                public Rect onGetEpicenter(Transition transition) {
+                    return epicenter;
+                }
+            });
+        }
+    }
+
+    /**
+     * A utility to retain only the mappings in {@code nameOverrides} that have a value
+     * that has a key in {@code namedViews}. This is a useful equivalent to
+     * {@link ArrayMap#retainAll(Collection)} for values.
+     */
+    private static void retainValues(ArrayMap<String, String> nameOverrides,
+            ArrayMap<String, View> namedViews) {
+        for (int i = nameOverrides.size() - 1; i >= 0; i--) {
+            final String targetName = nameOverrides.valueAt(i);
+            if (!namedViews.containsKey(targetName)) {
+                nameOverrides.removeAt(i);
+            }
+        }
+    }
+
+    /**
+     * Calls the {@link SharedElementCallback#onSharedElementStart(List, List, List)} or
+     * {@link SharedElementCallback#onSharedElementEnd(List, List, List)} on the appropriate
+     * incoming or outgoing fragment.
+     *
+     * @param inFragment The incoming fragment
+     * @param outFragment The outgoing fragment
+     * @param isPop Is the incoming fragment part of a pop transaction?
+     * @param sharedElements The shared element Views
+     * @param isStart Call the start or end call on the SharedElementCallback
+     */
+    private static void callSharedElementStartEnd(Fragment inFragment, Fragment outFragment,
+            boolean isPop, ArrayMap<String, View> sharedElements, boolean isStart) {
+        SharedElementCallback sharedElementCallback = isPop
+                ? outFragment.getEnterTransitionCallback()
+                : inFragment.getEnterTransitionCallback();
+        if (sharedElementCallback != null) {
+            ArrayList<View> views = new ArrayList<>();
+            ArrayList<String> names = new ArrayList<>();
+            final int count = sharedElements == null ? 0 : sharedElements.size();
+            for (int i = 0; i < count; i++) {
+                names.add(sharedElements.keyAt(i));
+                views.add(sharedElements.valueAt(i));
+            }
+            if (isStart) {
+                sharedElementCallback.onSharedElementStart(names, views, null);
+            } else {
+                sharedElementCallback.onSharedElementEnd(names, views, null);
+            }
+        }
+    }
+
+    /**
+     * Finds all children of the shared elements and sets the wrapping TransitionSet
+     * targets to point to those. It also limits transitions that have no targets to the
+     * specific shared elements. This allows developers to target child views of the
+     * shared elements specifically, but this doesn't happen by default.
+     */
+    private static void setSharedElementTargets(TransitionSet transition,
+            View nonExistentView, ArrayList<View> sharedViews) {
+        final List<View> views = transition.getTargets();
+        views.clear();
+        final int count = sharedViews.size();
+        for (int i = 0; i < count; i++) {
+            final View view = sharedViews.get(i);
+            bfsAddViewChildren(views, view);
+        }
+        views.add(nonExistentView);
+        sharedViews.add(nonExistentView);
+        addTargets(transition, sharedViews);
+    }
+
+    /**
+     * Uses a breadth-first scheme to add startView and all of its children to views.
+     * It won't add a child if it is already in views.
+     */
+    private static void bfsAddViewChildren(final List<View> views, final View startView) {
+        final int startIndex = views.size();
+        if (containedBeforeIndex(views, startView, startIndex)) {
+            return; // This child is already in the list, so all its children are also.
+        }
+        views.add(startView);
+        for (int index = startIndex; index < views.size(); index++) {
+            final View view = views.get(index);
+            if (view instanceof ViewGroup) {
+                ViewGroup viewGroup = (ViewGroup) view;
+                final int childCount =  viewGroup.getChildCount();
+                for (int childIndex = 0; childIndex < childCount; childIndex++) {
+                    final View child = viewGroup.getChildAt(childIndex);
+                    if (!containedBeforeIndex(views, child, startIndex)) {
+                        views.add(child);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Does a linear search through views for view, limited to maxIndex.
+     */
+    private static boolean containedBeforeIndex(final List<View> views, final View view,
+            final int maxIndex) {
+        for (int i = 0; i < maxIndex; i++) {
+            if (views.get(i) == view) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * After the transition has started, remove all targets that we added to the transitions
+     * so that the transitions are left in a clean state.
+     */
+    private static void scheduleRemoveTargets(final Transition overalTransition,
+            final Transition enterTransition, final ArrayList<View> enteringViews,
+            final Transition exitTransition, final ArrayList<View> exitingViews,
+            final TransitionSet sharedElementTransition, final ArrayList<View> sharedElementsIn) {
+        overalTransition.addListener(new Transition.TransitionListenerAdapter() {
+            @Override
+            public void onTransitionStart(Transition transition) {
+                if (enterTransition != null) {
+                    replaceTargets(enterTransition, enteringViews, null);
+                }
+                if (exitTransition != null) {
+                    replaceTargets(exitTransition, exitingViews, null);
+                }
+                if (sharedElementTransition != null) {
+                    replaceTargets(sharedElementTransition, sharedElementsIn, null);
+                }
+            }
+        });
+    }
+
+    /**
+     * This method removes the views from transitions that target ONLY those views and
+     * replaces them with the new targets list.
+     * The views list should match those added in addTargets and should contain
+     * one view that is not in the view hierarchy (state.nonExistentView).
+     */
+    public static void replaceTargets(Transition transition, ArrayList<View> oldTargets,
+            ArrayList<View> newTargets) {
+        if (transition instanceof TransitionSet) {
+            TransitionSet set = (TransitionSet) transition;
+            int numTransitions = set.getTransitionCount();
+            for (int i = 0; i < numTransitions; i++) {
+                Transition child = set.getTransitionAt(i);
+                replaceTargets(child, oldTargets, newTargets);
+            }
+        } else if (!hasSimpleTarget(transition)) {
+            List<View> targets = transition.getTargets();
+            if (targets != null && targets.size() == oldTargets.size() &&
+                    targets.containsAll(oldTargets)) {
+                // We have an exact match. We must have added these earlier in addTargets
+                final int targetCount = newTargets == null ? 0 : newTargets.size();
+                for (int i = 0; i < targetCount; i++) {
+                    transition.addTarget(newTargets.get(i));
+                }
+                for (int i = oldTargets.size() - 1; i >= 0; i--) {
+                    transition.removeTarget(oldTargets.get(i));
+                }
+            }
+        }
+    }
+
+    /**
+     * This method adds views as targets to the transition, but only if the transition
+     * doesn't already have a target. It is best for views to contain one View object
+     * that does not exist in the view hierarchy (state.nonExistentView) so that
+     * when they are removed later, a list match will suffice to remove the targets.
+     * Otherwise, if you happened to have targeted the exact views for the transition,
+     * the replaceTargets call will remove them unexpectedly.
+     */
+    public static void addTargets(Transition transition, ArrayList<View> views) {
+        if (transition == null) {
+            return;
+        }
+        if (transition instanceof TransitionSet) {
+            TransitionSet set = (TransitionSet) transition;
+            int numTransitions = set.getTransitionCount();
+            for (int i = 0; i < numTransitions; i++) {
+                Transition child = set.getTransitionAt(i);
+                addTargets(child, views);
+            }
+        } else if (!hasSimpleTarget(transition)) {
+            List<View> targets = transition.getTargets();
+            if (isNullOrEmpty(targets)) {
+                // We can just add the target views
+                int numViews = views.size();
+                for (int i = 0; i < numViews; i++) {
+                    transition.addTarget(views.get(i));
+                }
+            }
+        }
+    }
+
+    /**
+     * Returns true if there are any targets based on ID, transition or type.
+     */
+    private static boolean hasSimpleTarget(Transition transition) {
+        return !isNullOrEmpty(transition.getTargetIds()) ||
+                !isNullOrEmpty(transition.getTargetNames()) ||
+                !isNullOrEmpty(transition.getTargetTypes());
+    }
+
+    /**
+     * Simple utility to detect if a list is null or has no elements.
+     */
+    private static boolean isNullOrEmpty(List list) {
+        return list == null || list.isEmpty();
+    }
+
+    private static ArrayList<View> configureEnteringExitingViews(Transition transition,
+            Fragment fragment, ArrayList<View> sharedElements, View nonExistentView) {
+        ArrayList<View> viewList = null;
+        if (transition != null) {
+            viewList = new ArrayList<>();
+            View root = fragment.getView();
+            root.captureTransitioningViews(viewList);
+            if (sharedElements != null) {
+                viewList.removeAll(sharedElements);
+            }
+            if (!viewList.isEmpty()) {
+                viewList.add(nonExistentView);
+                addTargets(transition, viewList);
+            }
+        }
+        return viewList;
+    }
+
+    /**
+     * Sets the visibility of all Views in {@code views} to {@code visibility}.
+     */
+    private static void setViewVisibility(ArrayList<View> views, @View.Visibility int visibility) {
+        if (views == null) {
+            return;
+        }
+        for (int i = views.size() - 1; i >= 0; i--) {
+            final View view = views.get(i);
+            view.setVisibility(visibility);
+        }
+    }
+
+    /**
+     * Merges exit, shared element, and enter transitions so that they act together or
+     * sequentially as defined in the fragments.
+     */
+    private static Transition mergeTransitions(Transition enterTransition,
+            Transition exitTransition, Transition sharedElementTransition, Fragment inFragment,
+            boolean isPop) {
+        boolean overlap = true;
+        if (enterTransition != null && exitTransition != null && inFragment != null) {
+            overlap = isPop ? inFragment.getAllowReturnTransitionOverlap() :
+                    inFragment.getAllowEnterTransitionOverlap();
+        }
+
+        // Wrap the transitions. Explicit targets like in enter and exit will cause the
+        // views to be targeted regardless of excluded views. If that happens, then the
+        // excluded fragments views (hidden fragments) will still be in the transition.
+
+        Transition transition;
+        if (overlap) {
+            // Regular transition -- do it all together
+            TransitionSet transitionSet = new TransitionSet();
+            if (enterTransition != null) {
+                transitionSet.addTransition(enterTransition);
+            }
+            if (exitTransition != null) {
+                transitionSet.addTransition(exitTransition);
+            }
+            if (sharedElementTransition != null) {
+                transitionSet.addTransition(sharedElementTransition);
+            }
+            transition = transitionSet;
+        } else {
+            // First do exit, then enter, but allow shared element transition to happen
+            // during both.
+            Transition staggered = null;
+            if (exitTransition != null && enterTransition != null) {
+                staggered = new TransitionSet()
+                        .addTransition(exitTransition)
+                        .addTransition(enterTransition)
+                        .setOrdering(TransitionSet.ORDERING_SEQUENTIAL);
+            } else if (exitTransition != null) {
+                staggered = exitTransition;
+            } else if (enterTransition != null) {
+                staggered = enterTransition;
+            }
+            if (sharedElementTransition != null) {
+                TransitionSet together = new TransitionSet();
+                if (staggered != null) {
+                    together.addTransition(staggered);
+                }
+                together.addTransition(sharedElementTransition);
+                transition = together;
+            } else {
+                transition = staggered;
+            }
+        }
+        return transition;
+    }
+
+    /**
+     * Finds the first removed fragment and last added fragments when going forward.
+     * If none of the fragments have transitions, then both lists will be empty.
+     *
+     * @param transitioningFragments Keyed on the container ID, the first fragments to be removed,
+     *                               and last fragments to be added. This will be modified by
+     *                               this method.
+     */
+    public static void calculateFragments(BackStackRecord transaction,
+            SparseArray<FragmentContainerTransition> transitioningFragments,
+            boolean isOptimized) {
+        final int numOps = transaction.mOps.size();
+        for (int opNum = 0; opNum < numOps; opNum++) {
+            final BackStackRecord.Op op = transaction.mOps.get(opNum);
+            addToFirstInLastOut(transaction, op, transitioningFragments, false, isOptimized);
+        }
+    }
+
+    /**
+     * Finds the first removed fragment and last added fragments when popping the back stack.
+     * If none of the fragments have transitions, then both lists will be empty.
+     *
+     * @param transitioningFragments Keyed on the container ID, the first fragments to be removed,
+     *                               and last fragments to be added. This will be modified by
+     *                               this method.
+     */
+    public static void calculatePopFragments(BackStackRecord transaction,
+            SparseArray<FragmentContainerTransition> transitioningFragments, boolean isOptimized) {
+        if (!transaction.mManager.mContainer.onHasView()) {
+            return; // nothing to see, so no transitions
+        }
+        final int numOps = transaction.mOps.size();
+        for (int opNum = numOps - 1; opNum >= 0; opNum--) {
+            final BackStackRecord.Op op = transaction.mOps.get(opNum);
+            addToFirstInLastOut(transaction, op, transitioningFragments, true, isOptimized);
+        }
+    }
+
+    /**
+     * Examines the {@code command} and may set the first out or last in fragment for the fragment's
+     * container.
+     *
+     * @param transaction The executing transaction
+     * @param op The operation being run.
+     * @param transitioningFragments A structure holding the first in and last out fragments
+     *                               for each fragment container.
+     * @param isPop Is the operation a pop?
+     * @param isOptimizedTransaction True if the operations have been partially executed and the
+     *                               added fragments have Views in the hierarchy or false if the
+     *                               operations haven't been executed yet.
+     */
+    private static void addToFirstInLastOut(BackStackRecord transaction, BackStackRecord.Op op,
+            SparseArray<FragmentContainerTransition> transitioningFragments, boolean isPop,
+            boolean isOptimizedTransaction) {
+        final Fragment fragment = op.fragment;
+        final int containerId = fragment.mContainerId;
+        if (containerId == 0) {
+            return; // no container, no transition
+        }
+        final int command = isPop ? INVERSE_OPS[op.cmd] : op.cmd;
+        boolean setLastIn = false;
+        boolean wasRemoved = false;
+        boolean setFirstOut = false;
+        boolean wasAdded = false;
+        switch (command) {
+            case BackStackRecord.OP_SHOW:
+                if (isOptimizedTransaction) {
+                    setLastIn = fragment.mHiddenChanged && !fragment.mHidden &&
+                            fragment.mAdded;
+                } else {
+                    setLastIn = fragment.mHidden;
+                }
+                wasAdded = true;
+                break;
+            case BackStackRecord.OP_ADD:
+            case BackStackRecord.OP_ATTACH:
+                if (isOptimizedTransaction) {
+                    setLastIn = fragment.mIsNewlyAdded;
+                } else {
+                    setLastIn = !fragment.mAdded && !fragment.mHidden;
+                }
+                wasAdded = true;
+                break;
+            case BackStackRecord.OP_HIDE:
+                if (isOptimizedTransaction) {
+                    setFirstOut = fragment.mHiddenChanged && fragment.mAdded &&
+                            fragment.mHidden;
+                } else {
+                    setFirstOut = fragment.mAdded && !fragment.mHidden;
+                }
+                wasRemoved = true;
+                break;
+            case BackStackRecord.OP_REMOVE:
+            case BackStackRecord.OP_DETACH:
+                if (isOptimizedTransaction) {
+                    setFirstOut = !fragment.mAdded && fragment.mView != null &&
+                            fragment.mView.getVisibility() == View.VISIBLE;
+                } else {
+                    setFirstOut = fragment.mAdded && !fragment.mHidden;
+                }
+                wasRemoved = true;
+                break;
+        }
+        FragmentContainerTransition containerTransition = transitioningFragments.get(containerId);
+        if (setLastIn) {
+            containerTransition =
+                    ensureContainer(containerTransition, transitioningFragments, containerId);
+            containerTransition.lastIn = fragment;
+            containerTransition.lastInIsPop = isPop;
+            containerTransition.lastInTransaction = transaction;
+        }
+        if (!isOptimizedTransaction && wasAdded) {
+            if (containerTransition != null && containerTransition.firstOut == fragment) {
+                containerTransition.firstOut = null;
+            }
+
+            /**
+             * Ensure that fragments that are entering are at least at the CREATED state
+             * so that they may load Transitions using TransitionInflater.
+             */
+            FragmentManagerImpl manager = transaction.mManager;
+            if (fragment.mState < Fragment.CREATED && manager.mCurState >= Fragment.CREATED &&
+                    manager.mHost.getContext().getApplicationInfo().targetSdkVersion >=
+                            Build.VERSION_CODES.N && !transaction.mAllowOptimization) {
+                manager.makeActive(fragment);
+                manager.moveToState(fragment, Fragment.CREATED, 0, 0, false);
+            }
+        }
+        if (setFirstOut && (containerTransition == null || containerTransition.firstOut == null)) {
+            containerTransition =
+                    ensureContainer(containerTransition, transitioningFragments, containerId);
+            containerTransition.firstOut = fragment;
+            containerTransition.firstOutIsPop = isPop;
+            containerTransition.firstOutTransaction = transaction;
+        }
+
+        if (!isOptimizedTransaction && wasRemoved &&
+                (containerTransition != null && containerTransition.lastIn == fragment)) {
+            containerTransition.lastIn = null;
+        }
+    }
+
+    /**
+     * Ensures that a FragmentContainerTransition has been added to the SparseArray. If so,
+     * it returns the existing one. If not, one is created and added to the SparseArray and
+     * returned.
+     */
+    private static FragmentContainerTransition ensureContainer(
+            FragmentContainerTransition containerTransition,
+            SparseArray<FragmentContainerTransition> transitioningFragments, int containerId) {
+        if (containerTransition == null) {
+            containerTransition = new FragmentContainerTransition();
+            transitioningFragments.put(containerId, containerTransition);
+        }
+        return containerTransition;
+    }
+
+    /**
+     * Tracks the last fragment added and first fragment removed for fragment transitions.
+     * This also tracks which fragments are changed by push or pop transactions.
+     */
+    public static class FragmentContainerTransition {
+        /**
+         * The last fragment added/attached/shown in its container
+         */
+        public Fragment lastIn;
+
+        /**
+         * true when lastIn was added during a pop transaction or false if added with a push
+         */
+        public boolean lastInIsPop;
+
+        /**
+         * The transaction that included the last in fragment
+         */
+        public BackStackRecord lastInTransaction;
+
+        /**
+         * The first fragment with a View that was removed/detached/hidden in its container.
+         */
+        public Fragment firstOut;
+
+        /**
+         * true when firstOut was removed during a pop transaction or false otherwise
+         */
+        public boolean firstOutIsPop;
+
+        /**
+         * The transaction that included the first out fragment
+         */
+        public BackStackRecord firstOutTransaction;
+    }
+}
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 1253e69..5edd03f 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -274,12 +274,24 @@
 
     /**
      * Updates global configuration and applies changes to the entire system.
-     * @param values Update values for global configuration.
+     * @param values Update values for global configuration. If null is passed it will request the
+     *               Window Manager to compute new config for the default display.
      * @throws RemoteException
      * @return Returns true if the configuration was updated.
      */
     public boolean updateConfiguration(Configuration values) throws RemoteException;
 
+    /**
+     * Updates override configuration applied to specific display.
+     * @param values Update values for display configuration. If null is passed it will request the
+     *               Window Manager to compute new config for the specified display.
+     * @param displayId Id of the display to apply the config to.
+     * @throws RemoteException
+     * @return Returns true if the configuration was updated.
+     */
+    public boolean updateDisplayOverrideConfiguration(Configuration values, int displayId)
+            throws RemoteException;
+
     public void setRequestedOrientation(IBinder token,
             int requestedOrientation) throws RemoteException;
     public int getRequestedOrientation(IBinder token) throws RemoteException;
@@ -1103,4 +1115,5 @@
     int REQUEST_ACTIVITY_RELAUNCH = IBinder.FIRST_CALL_TRANSACTION+400;
     int GET_DEFAULT_PICTURE_IN_PICTURE_BOUNDS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 401;
     int GET_PICTURE_IN_PICTURE_MOVEMENT_BOUNDS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 402;
+    int UPDATE_DISPLAY_OVERRIDE_CONFIGURATION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 403;
 }
diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl
index 2dd3b1a..e2f6fb5 100644
--- a/core/java/android/app/IApplicationThread.aidl
+++ b/core/java/android/app/IApplicationThread.aidl
@@ -155,6 +155,7 @@
     void scheduleLocalVoiceInteractionStarted(IBinder token,
             IVoiceInteractor voiceInteractor) = 61;
     void handleTrustStorageUpdate() = 62;
+    void attachAgent(String path) = 63;
     /**
      * Don't change the existing transaction Ids as they could be used in the native code.
      * When adding a new method, assign the next available transaction id.
diff --git a/core/java/android/bluetooth/BluetoothUuid.java b/core/java/android/bluetooth/BluetoothUuid.java
index 2ded4c8..243579a 100644
--- a/core/java/android/bluetooth/BluetoothUuid.java
+++ b/core/java/android/bluetooth/BluetoothUuid.java
@@ -275,6 +275,48 @@
     }
 
     /**
+     * Parse UUID to bytes. The returned value is shortest representation, a 16-bit, 32-bit or 128-bit UUID,
+     * Note returned value is little endian (Bluetooth).
+     *
+     * @param uuid uuid to parse.
+     * @return shortest representation of {@code uuid} as bytes.
+     * @throws IllegalArgumentException If the {@code uuid} is null.
+     */
+    public static byte[] uuidToBytes(ParcelUuid uuid) {
+        if (uuid == null) {
+            throw new IllegalArgumentException("uuid cannot be null");
+        }
+
+        if (is16BitUuid(uuid)) {
+            byte[] uuidBytes = new byte[UUID_BYTES_16_BIT];
+            int uuidVal = getServiceIdentifierFromParcelUuid(uuid);
+            uuidBytes[0] = (byte)(uuidVal & 0xFF);
+            uuidBytes[1] = (byte)((uuidVal & 0xFF00) >> 8);
+            return uuidBytes;
+        }
+
+        if (is32BitUuid(uuid)) {
+            byte[] uuidBytes = new byte[UUID_BYTES_32_BIT];
+            int uuidVal = getServiceIdentifierFromParcelUuid(uuid);
+            uuidBytes[0] = (byte)(uuidVal & 0xFF);
+            uuidBytes[1] = (byte)((uuidVal & 0xFF00) >> 8);
+            uuidBytes[2] = (byte)((uuidVal & 0xFF0000) >> 16);
+            uuidBytes[3] = (byte)((uuidVal & 0xFF000000) >> 24);
+            return uuidBytes;
+        }
+
+        // Construct a 128 bit UUID.
+        long msb = uuid.getUuid().getMostSignificantBits();
+        long lsb = uuid.getUuid().getLeastSignificantBits();
+
+        byte[] uuidBytes = new byte[UUID_BYTES_128_BIT];
+        ByteBuffer buf = ByteBuffer.wrap(uuidBytes).order(ByteOrder.LITTLE_ENDIAN);
+        buf.putLong(8, msb);
+        buf.putLong(0, lsb);
+        return uuidBytes;
+    }
+
+    /**
      * Check whether the given parcelUuid can be converted to 16 bit bluetooth uuid.
      *
      * @param parcelUuid
diff --git a/core/java/android/os/ShellCommand.java b/core/java/android/os/ShellCommand.java
index dbb9650..e4a12e8 100644
--- a/core/java/android/os/ShellCommand.java
+++ b/core/java/android/os/ShellCommand.java
@@ -302,7 +302,7 @@
     /**
      * Implement parsing and execution of a command.  If it isn't a command you understand,
      * call {@link #handleDefaultCommands(String)} and return its result as a last resort.
-     * User {@link #getNextOption()}, {@link #getNextArg()}, and {@link #getNextArgRequired()}
+     * Use {@link #getNextOption()}, {@link #getNextArg()}, and {@link #getNextArgRequired()}
      * to process additional command line arguments.  Command output can be written to
      * {@link #getOutPrintWriter()} and errors to {@link #getErrPrintWriter()}.
      *
diff --git a/core/java/android/util/Half.java b/core/java/android/util/Half.java
new file mode 100644
index 0000000..f4eb132
--- /dev/null
+++ b/core/java/android/util/Half.java
@@ -0,0 +1,367 @@
+/*
+ * Copyright (C) 2016 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 android.util;
+
+/**
+ * <p>Half is a utility class to manipulate half-precision 16-bit
+ * <a href="https://en.wikipedia.org/wiki/Half-precision_floating-point_format">IEEE 754</a>
+ * floating point data types (also called fp16 or binary16). A half-precision
+ * float is stored in a short data type. A half-precision float can be
+ * created from or converted to single-precision floats.</p>
+ *
+ * <p>The IEEE 754 standard specifies an fp16 as having the following format:</p>
+ * <ul>
+ * <li>Sign bit: 1 bit</li>
+ * <li>Exponent width: 5 bits</li>
+ * <li>Mantissa: 10 bits</li>
+ * </ul>
+ *
+ * <p>The format is laid out thusly:</p>
+ * <pre>
+ * 1   11111   1111111111
+ * ^   --^--   -----^----
+ * sign  |          |_______ mantissa
+ *       |
+ *       -- exponent
+ * </pre>
+ *
+ * @hide
+ */
+public final class Half {
+    /**
+     * The number of bits used to represent a half-precision float value.
+     */
+    public static final int SIZE = 16;
+
+    /**
+     * Epsilon is the difference between 1.0 and the next value representable
+     * by a half-precision floating-point.
+     */
+    public static final short EPSILON            = (short) 0x1400;
+    /**
+     * Smallest negative value a half-precision float may have.
+     */
+    public static final short LOWEST_VALUE       = (short) 0xfbff;
+    /**
+     * Maximum exponent a finite half-precision float may have.
+     */
+    public static final short MAX_EXPONENT       = 15;
+    /**
+     * Maximum positive finite value a half-precision float may have.
+     */
+    public static final short MAX_VALUE          = (short) 0x7bff;
+    /**
+     * Minimum exponent a normalized half-precision float may have.
+     */
+    public static final short MIN_EXPONENT       = -14;
+    /**
+     * Smallest positive normal value a half-precision float may have.
+     */
+    public static final short MIN_NORMAL         = (short) 0x0400;
+    /**
+     * Smallest positive non-zero value a half-precision float may have.
+     */
+    public static final short MIN_VALUE          = (short) 0x0001;
+    /**
+     * A Not-a-Number representation of a half-precision float.
+     */
+    public static final short NaN                = (short) 0x7e00;
+    /**
+     * Negative infinity of type half-precision float.
+     */
+    public static final short NEGATIVE_INFINITY  = (short) 0xfc00;
+    /**
+     * Negative 0 of type half-precision float.
+     */
+    public static final short NEGATIVE_ZERO      = (short) 0x8000;
+    /**
+     * Positive infinity of type half-precision float.
+     */
+    public static final short POSITIVE_INFINITY  = (short) 0x7c00;
+    /**
+     * Positive 0 of type half-precision float.
+     */
+    public static final short POSITIVE_ZERO      = (short) 0x0000;
+
+    private static final int FP16_SIGN_SHIFT     = 15;
+    private static final int FP16_EXPONENT_SHIFT = 10;
+    private static final int FP16_EXPONENT_MASK  = 0x1f;
+    private static final int FP16_MANTISSA_MASK  = 0x3ff;
+    private static final int FP16_EXPONENT_BIAS  = 15;
+
+    private static final int FP32_SIGN_SHIFT     = 31;
+    private static final int FP32_EXPONENT_SHIFT = 23;
+    private static final int FP32_EXPONENT_MASK  = 0xff;
+    private static final int FP32_MANTISSA_MASK  = 0x7fffff;
+    private static final int FP32_EXPONENT_BIAS  = 127;
+
+    private static final int   FP32_DENORMAL_MAGIC = 126 << 23;
+    private static final float FP32_DENORMAL_FLOAT =
+            Float.intBitsToFloat(FP32_DENORMAL_MAGIC);
+
+    private Half() {
+    }
+
+    /**
+     * Returns the sign of the specified half-precision float.
+     *
+     * @param h A half-precision float value
+     * @return 1 if the value is positive, -1 if the value is negative
+     */
+    public static int getSign(short h) {
+        return (h >>> FP16_SIGN_SHIFT) == 0 ? 1 : -1;
+    }
+
+    /**
+     * Returns the unbiased exponent used in the representation of
+     * the specified  half-precision float value. if the value is NaN
+     * or infinite, this* method returns {@link #MAX_EXPONENT} + 1.
+     * If the argument is* 0 or denormal, this method returns
+     * {@link #MIN_EXPONENT} - 1.
+     *
+     * @param h A half-precision float value
+     * @return The unbiased exponent of the specified value
+     */
+    public static int getExponent(short h) {
+        return ((h >>> FP16_EXPONENT_SHIFT) & FP16_EXPONENT_MASK) - FP16_EXPONENT_BIAS;
+    }
+
+    /**
+     * Returns the mantissa, or significand, used in the representation
+     * of the specified half-precision float value.
+     *
+     * @param h A half-precision float value
+     * @return The mantissa, or significand, of the specified vlaue
+     */
+    public static int getMantissa(short h) {
+        return h & FP16_MANTISSA_MASK;
+    }
+
+    /**
+     * Returns true if the specified half-precision float value represents
+     * infinity, false otherwise.
+     *
+     * @param h A half-precision float value
+     * @return true if the value is positive infinity or negative infinity,
+     *         false otherwise
+     */
+    public static boolean isInfinite(short h) {
+        int e = (h >>> FP16_EXPONENT_SHIFT) & FP16_EXPONENT_MASK;
+        int m = (h                        ) & FP16_MANTISSA_MASK;
+        return e == 0x1f && m == 0;
+    }
+
+    /**
+     * Returns true if the specified half-precision float value represents
+     * a Not-a-Number, false otherwise.
+     *
+     * @param h A half-precision float value
+     * @return true if the value is a NaN, false otherwise
+     */
+    public static boolean isNaN(short h) {
+        int e = (h >>> FP16_EXPONENT_SHIFT) & FP16_EXPONENT_MASK;
+        int m = (h                        ) & FP16_MANTISSA_MASK;
+        return e == 0x1f && m != 0;
+    }
+
+    /**
+     * <p>Converts the specified half-precision float value into a
+     * single-precision float value with the following special cases:</p>
+     * <ul>
+     * <li>If the input is {@link #NaN}, the returned* value is {@link Float#NaN}</li>
+     * <li>If the input is {@link #POSITIVE_INFINITY} or
+     * {@link #NEGATIVE_INFINITY}, the returned value is respectively
+     * {@link Float#POSITIVE_INFINITY} or {@link Float#NEGATIVE_INFINITY}</li>
+     * <li>If the input is 0 (positive or negative), the returned value is +/-0.0f</li>
+     * <li>Otherwise, the returned value is a normalized single-precision float value</li>
+     * </ul>
+     *
+     * @param h The half-precision float value to convert to single-precision
+     * @return A normalized single-precision float value
+     */
+    public static float toFloat(short h) {
+        int bits = h & 0xffff;
+        int s = (bits >>> FP16_SIGN_SHIFT    );
+        int e = (bits >>> FP16_EXPONENT_SHIFT) & FP16_EXPONENT_MASK;
+        int m = (bits                        ) & FP16_MANTISSA_MASK;
+
+        int outE = 0;
+        int outM = 0;
+
+        if (e == 0) { // Denormal or 0
+            if (m != 0) {
+                // Convert denorm fp16 into normalized fp32
+                float o = Float.intBitsToFloat(FP32_DENORMAL_MAGIC + m);
+                o -= FP32_DENORMAL_FLOAT;
+                return s == 0 ? o : -o;
+            }
+        } else {
+            outM = m << 13;
+            if (e == 0x1f) { // Infinite or NaN
+                outE = 0xff;
+            } else {
+                outE = e - FP16_EXPONENT_BIAS + FP32_EXPONENT_BIAS;
+            }
+        }
+
+        int out = (s << FP32_SIGN_SHIFT) | (outE << FP32_EXPONENT_SHIFT) | outM;
+        return Float.intBitsToFloat(out);
+    }
+
+    /**
+     * <p>Converts the specified single-precision float value into a
+     * half-precision float value with the following special cases:</p>
+     * <ul>
+     * <li>If the input is NaN (see {@link Float#isNaN(float)}), the returned
+     * value is {@link #NaN}</li>
+     * <li>If the input is {@link Float#POSITIVE_INFINITY} or
+     * {@link Float#NEGATIVE_INFINITY}, the returned value is respectively
+     * {@link #POSITIVE_INFINITY} or {@link #NEGATIVE_INFINITY}</li>
+     * <li>If the input is 0 (positive or negative), the returned value is
+     * {@link #POSITIVE_ZERO} or {@link #NEGATIVE_ZERO}</li>
+     * <li>If the input is a less than {@link #MIN_VALUE}, the returned value
+     * is flushed to {@link #POSITIVE_ZERO} or {@link #NEGATIVE_ZERO}</li>
+     * <li>If the input is a less than {@link #MIN_NORMAL}, the returned value
+     * is a denorm half-precision float</li>
+     * <li>Otherwise, the returned value is rounded to the nearest
+     * representable half-precision float value</li>
+     * </ul>
+     *
+     * @param f The single-precision float value to convert to half-precision
+     * @return A half-precision float value
+     */
+    @SuppressWarnings("StatementWithEmptyBody")
+    public static short valueOf(float f) {
+        int bits = Float.floatToRawIntBits(f);
+        int s = (bits >>> FP32_SIGN_SHIFT    );
+        int e = (bits >>> FP32_EXPONENT_SHIFT) & FP32_EXPONENT_MASK;
+        int m = (bits                        ) & FP32_MANTISSA_MASK;
+
+        int outE = 0;
+        int outM = 0;
+
+        if (e == 0xff) { // Infinite or NaN
+            outE = 0x1f;
+            outM = m != 0 ? 0x200 : 0;
+        } else {
+            e = e - FP32_EXPONENT_BIAS + FP16_EXPONENT_BIAS;
+            if (e >= 0x1f) { // Overflow
+                outE = 0x31;
+            } else if (e <= 0) { // Underflow
+                if (e < -10) {
+                    // The absolute fp32 value is less than MIN_VALUE, flush to +/-0
+                } else {
+                    // The fp32 value is a normalized float less than MIN_NORMAL,
+                    // we convert to a denorm fp16
+                    m = (m | 0x800000) >> (1 - e);
+                    if ((m & 0x1000) != 0) m += 0x2000;
+                    outM = m >> 13;
+                }
+            } else {
+                outE = e;
+                outM = m >> 13;
+                if ((m & 0x1000) != 0) {
+                    // Round to nearest "0.5" up
+                    int out = (outE << FP16_EXPONENT_SHIFT) | outM;
+                    out++;
+                    out |= (s << FP16_SIGN_SHIFT);
+                    return (short) out;
+                }
+            }
+        }
+
+        int out = (s << FP16_SIGN_SHIFT) | (outE << FP16_EXPONENT_SHIFT) | outM;
+        return (short) out;
+    }
+
+    /**
+     * Returns a string representation of the specified half-precision
+     * float value. Calling this method is equivalent to calling
+     * <code>Float.toString(toFloat(h))</code>. See {@link Float#toString(float)}
+     * for more information on the format of the string representation.
+     *
+     * @param h A half-precision float value
+     * @return A string representation of the specified value
+     */
+    public static String toString(short h) {
+        return Float.toString(toFloat(h));
+    }
+
+    /**
+     * <p>Returns a hexadecimal string representation of the specified half-precision
+     * float value. If the value is a NaN, the result is <code>"NaN"</code>,
+     * otherwise the result follows this format:</p>
+     * <ul>
+     * <li>If the sign is positive, no sign character appears in the result</li>
+     * <li>If the sign is negative, the first character is <code>'-'</code></li>
+     * <li>If the value is inifinity, the string is <code>"Infinity"</code></li>
+     * <li>If the value is 0, the string is <code>"0x0.0p0"</code></li>
+     * <li>If the value has a normalized representation, the exponent and
+     * mantissa are represented in the string in two fields. The mantissa starts
+     * with <code>"0x1."</code> followed by its lowercase hexadecimal
+     * representation. Trailing zeroes are removed unless all digits are 0, then
+     * a single zero is used. The mantissa representation is followed by the
+     * exponent, represented by <code>"p"</code>, itself followed by a decimal
+     * string of the unbiased exponent</li>
+     * <li>If the value has a denormal representation, the mantissa starts
+     * with <code>"0x0."</code> followed by its lowercase hexadecimal
+     * representation. Trailing zeroes are removed unless all digits are 0, then
+     * a single zero is used. The mantissa representation is followed by the
+     * exponent, represented by <code>"p-14"</code></li>
+     * </ul>
+     *
+     * @param h A half-precision float value
+     * @return A hexadecimal string representation of the specified value
+     */
+    public static String toHexString(short h) {
+        StringBuilder o = new StringBuilder();
+
+        int bits = h & 0xffff;
+        int s = (bits >>> FP16_SIGN_SHIFT    );
+        int e = (bits >>> FP16_EXPONENT_SHIFT) & FP16_EXPONENT_MASK;
+        int m = (bits                        ) & FP16_MANTISSA_MASK;
+
+        if (e == 0x1f) { // Infinite or NaN
+            if (m == 0) {
+                if (s == 1) o.append('-');
+                o.append("Infinity");
+            } else {
+                o.append("NaN");
+            }
+        } else {
+            if (s == 1) o.append('-');
+            if (e == 0) {
+                if (m == 0) {
+                    o.append("0x0.0p0");
+                } else {
+                    o.append("0x0.");
+                    String mantissa = Integer.toHexString(m);
+                    o.append(mantissa.replaceFirst("0{2,}$", ""));
+                    o.append("p-14");
+                }
+            } else {
+                o.append("0x1.");
+                String mantissa = Integer.toHexString(m);
+                o.append(mantissa.replaceFirst("0{2,}$", ""));
+                o.append('p');
+                o.append(Integer.toString(e - FP16_EXPONENT_BIAS));
+            }
+        }
+
+        return o.toString();
+    }
+}
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 717b675..7917041 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -191,10 +191,10 @@
     // If there is a change, the new Configuration is returned and the
     // caller must call setNewConfiguration() sometime later.
     Configuration updateOrientationFromAppTokens(in Configuration currentConfig,
-            IBinder freezeThisOneIfNeeded);
-    // Notify window manager of the new configuration. Returns an array of stack ids that's
-    // affected by the update, ActivityManager should resize these stacks.
-    int[] setNewConfiguration(in Configuration config);
+            IBinder freezeThisOneIfNeeded, int displayId);
+    // Notify window manager of the new display override configuration. Returns an array of stack
+    // ids that were affected by the update, ActivityManager should resize these stacks.
+    int[] setNewDisplayOverrideConfiguration(in Configuration overrideConfig, int displayId);
 
     // Retrieves the new bounds after the configuration update evaluated by window manager.
     Rect getBoundsForNewConfiguration(int stackId);
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index c346849..de6f2c5 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1712,11 +1712,6 @@
     <permission android:name="android.permission.GET_PROCESS_STATE_AND_OOM_SCORE"
         android:protectionLevel="signature|privileged|development" />
 
-    <!-- @SystemApi @hide Allows an application to retrieve a package's importance.
-         This permission is not available to third party applications. -->
-    <permission android:name="android.permission.GET_PACKAGE_IMPORTANCE"
-        android:protectionLevel="signature|privileged" />
-
     <!-- Allows use of PendingIntent.getIntent().
          @hide -->
     <permission android:name="android.permission.GET_INTENT_SENDER_INTENT"
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
index bfe8c07..77a45b3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
@@ -484,7 +484,8 @@
         mMainHandler.scheduleAPCopyingAndCloseWriteLock();
     }
 
-    private AccessPoint getCachedOrCreate(ScanResult result, List<AccessPoint> cache) {
+    @VisibleForTesting
+    AccessPoint getCachedOrCreate(ScanResult result, List<AccessPoint> cache) {
         final int N = cache.size();
         for (int i = 0; i < N; i++) {
             if (cache.get(i).matches(result)) {
@@ -493,10 +494,13 @@
                 return ret;
             }
         }
-        return new AccessPoint(mContext, result);
+        final AccessPoint accessPoint = new AccessPoint(mContext, result);
+        accessPoint.setListener(mAccessPointListenerAdapter);
+        return accessPoint;
     }
 
-    private AccessPoint getCachedOrCreate(WifiConfiguration config, List<AccessPoint> cache) {
+    @VisibleForTesting
+    AccessPoint getCachedOrCreate(WifiConfiguration config, List<AccessPoint> cache) {
         final int N = cache.size();
         for (int i = 0; i < N; i++) {
             if (cache.get(i).matches(config)) {
@@ -505,7 +509,7 @@
                 return ret;
             }
         }
-        AccessPoint accessPoint = new AccessPoint(mContext, config);
+        final AccessPoint accessPoint = new AccessPoint(mContext, config);
         accessPoint.setListener(mAccessPointListenerAdapter);
         return accessPoint;
     }
diff --git a/packages/SettingsLib/tests/AndroidManifest.xml b/packages/SettingsLib/tests/AndroidManifest.xml
index 9fd5a41..00b2164 100644
--- a/packages/SettingsLib/tests/AndroidManifest.xml
+++ b/packages/SettingsLib/tests/AndroidManifest.xml
@@ -21,6 +21,8 @@
     <uses-permission android:name="android.permission.MANAGE_USERS" />
     <uses-permission android:name="android.permission.MANAGE_NETWORK_POLICY"/>
     <uses-permission android:name="android.permission.SET_TIME_ZONE" />
+    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
+
 
     <application>
         <uses-library android:name="android.test.runner" />
diff --git a/packages/SettingsLib/tests/src/com/android/settingslib/wifi/WifiTrackerTest.java b/packages/SettingsLib/tests/src/com/android/settingslib/wifi/WifiTrackerTest.java
new file mode 100644
index 0000000..c650190
--- /dev/null
+++ b/packages/SettingsLib/tests/src/com/android/settingslib/wifi/WifiTrackerTest.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2016 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.settingslib.wifi;
+
+
+import static org.junit.Assert.assertTrue;
+
+import android.net.wifi.ScanResult;
+import android.net.wifi.WifiConfiguration;
+import android.os.SystemClock;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.BitSet;
+import java.util.List;
+
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class WifiTrackerTest {
+
+    @Test
+    public void testAccessPointListenerSetWhenLookingUpUsingScanResults() {
+        ScanResult scanResult = new ScanResult();
+        scanResult.level = 123;
+        scanResult.BSSID = "bssid-" + 111;
+        scanResult.timestamp = SystemClock.elapsedRealtime() * 1000;
+        scanResult.capabilities = "";
+
+        WifiTracker tracker = new WifiTracker(
+                InstrumentationRegistry.getTargetContext(), null, true, true);
+
+        AccessPoint result = tracker.getCachedOrCreate(scanResult, new ArrayList<AccessPoint>());
+        assertTrue(result.mAccessPointListener != null);
+    }
+
+    @Test
+    public void testAccessPointListenerSetWhenLookingUpUsingWifiConfiguration() {
+        WifiConfiguration configuration = new WifiConfiguration();
+        configuration.SSID = "test123";
+        configuration.BSSID="bssid";
+        configuration.networkId = 123;
+        configuration.allowedKeyManagement = new BitSet();
+        configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
+
+        WifiTracker tracker = new WifiTracker(
+                InstrumentationRegistry.getTargetContext(), null, true, true);
+
+        AccessPoint result = tracker.getCachedOrCreate(configuration, new ArrayList<AccessPoint>());
+        assertTrue(result.mAccessPointListener != null);
+    }
+}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 3084b9b..e7f5f4f 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -2137,7 +2137,7 @@
         }
 
         private final class UpgradeController {
-            private static final int SETTINGS_VERSION = 133;
+            private static final int SETTINGS_VERSION = 134;
 
             private final int mUserId;
 
@@ -2452,6 +2452,21 @@
                 }
 
                 if (currentVersion == 130) {
+                    // Split Ambient settings
+                    final SettingsState secureSettings = getSecureSettingsLocked(userId);
+                    boolean dozeExplicitlyDisabled = "0".equals(secureSettings.
+                            getSettingLocked(Settings.Secure.DOZE_ENABLED).getValue());
+
+                    if (dozeExplicitlyDisabled) {
+                        secureSettings.insertSettingLocked(Settings.Secure.DOZE_PULSE_ON_PICK_UP,
+                                "0", SettingsState.SYSTEM_PACKAGE_NAME);
+                        secureSettings.insertSettingLocked(Settings.Secure.DOZE_PULSE_ON_DOUBLE_TAP,
+                                "0", SettingsState.SYSTEM_PACKAGE_NAME);
+                    }
+                    currentVersion = 131;
+                }
+
+                if (currentVersion == 131) {
                     // Initialize new multi-press timeout to default value
                     final SettingsState systemSecureSettings = getSecureSettingsLocked(userId);
                     final String oldValue = systemSecureSettings.getSettingLocked(
@@ -2464,11 +2479,11 @@
                                 SettingsState.SYSTEM_PACKAGE_NAME);
                     }
 
-                    currentVersion = 131;
+                    currentVersion = 132;
                 }
 
-                if (currentVersion == 131) {
-                    // Version 131: Allow managed profile to optionally use the parent's ringtones
+                if (currentVersion == 132) {
+                    // Version 132: Allow managed profile to optionally use the parent's ringtones
                     final SettingsState systemSecureSettings = getSecureSettingsLocked(userId);
                     String defaultSyncParentSounds = (getContext().getResources()
                             .getBoolean(R.bool.def_sync_parent_sounds) ? "1" : "0");
@@ -2476,11 +2491,11 @@
                             Settings.Secure.SYNC_PARENT_SOUNDS,
                             defaultSyncParentSounds,
                             SettingsState.SYSTEM_PACKAGE_NAME);
-                    currentVersion = 132;
+                    currentVersion = 133;
                 }
 
-                if (currentVersion == 132) {
-                    // Version 132: Add default end button behavior
+                if (currentVersion == 133) {
+                    // Version 133: Add default end button behavior
                     final SettingsState systemSettings = getSystemSettingsLocked(userId);
                     if (systemSettings.getSettingLocked(Settings.System.END_BUTTON_BEHAVIOR) ==
                             null) {
@@ -2489,7 +2504,7 @@
                         systemSettings.insertSettingLocked(Settings.System.END_BUTTON_BEHAVIOR,
                                 defaultEndButtonBehavior, SettingsState.SYSTEM_PACKAGE_NAME);
                     }
-                    currentVersion = 133;
+                    currentVersion = 134;
                 }
 
                 if (currentVersion != newVersion) {
diff --git a/packages/SystemUI/res/layout/car_navigation_bar.xml b/packages/SystemUI/res/layout/car_navigation_bar.xml
index f7f673d..4b6929f 100644
--- a/packages/SystemUI/res/layout/car_navigation_bar.xml
+++ b/packages/SystemUI/res/layout/car_navigation_bar.xml
@@ -28,7 +28,8 @@
     -->
     <LinearLayout
         android:layout_height="match_parent"
-        android:layout_width="match_parent"
+        android:layout_width="@dimen/car_navigation_bar_width"
+        android:layout_gravity="center"
         android:orientation="horizontal"
         android:clipChildren="false"
         android:clipToPadding="false"
diff --git a/packages/SystemUI/res/values/dimens_car.xml b/packages/SystemUI/res/values/dimens_car.xml
index 04402b7..988caf5 100644
--- a/packages/SystemUI/res/values/dimens_car.xml
+++ b/packages/SystemUI/res/values/dimens_car.xml
@@ -29,4 +29,5 @@
     <dimen name="car_fullscreen_user_pod_image_avatar_height">128dp</dimen>
     <dimen name="car_fullscreen_user_pod_text_size">24sp</dimen>
     <dimen name="car_navigation_button_width">64dp</dimen>
+    <dimen name="car_navigation_bar_width">760dp</dimen>
 </resources>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
index a7331d8..8a5a8a0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -248,6 +248,11 @@
         }
     }
 
+    @Override
+    public void updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation) {
+        // Do nothing, we don't want to display media art in the lock screen for a car.
+    }
+
     private int startActivityWithOptions(Intent intent, Bundle options) {
         int result = ActivityManager.START_CANCELED;
         try {
diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java
index bb5f62b..2825cf9 100644
--- a/services/core/java/com/android/server/UiModeManagerService.java
+++ b/services/core/java/com/android/server/UiModeManagerService.java
@@ -466,9 +466,8 @@
         if (mCarModeEnabled) {
             if (mLastBroadcastState != Intent.EXTRA_DOCK_STATE_CAR) {
                 adjustStatusBarCarModeLocked();
-
                 if (oldAction != null) {
-                    getContext().sendBroadcastAsUser(new Intent(oldAction), UserHandle.ALL);
+                    sendForegroundBroadcastToAllUsers(oldAction);
                 }
                 mLastBroadcastState = Intent.EXTRA_DOCK_STATE_CAR;
                 action = UiModeManager.ACTION_ENTER_CAR_MODE;
@@ -476,7 +475,7 @@
         } else if (isDeskDockState(mDockState)) {
             if (!isDeskDockState(mLastBroadcastState)) {
                 if (oldAction != null) {
-                    getContext().sendBroadcastAsUser(new Intent(oldAction), UserHandle.ALL);
+                    sendForegroundBroadcastToAllUsers(oldAction);
                 }
                 mLastBroadcastState = mDockState;
                 action = UiModeManager.ACTION_ENTER_DESK_MODE;
@@ -502,6 +501,7 @@
             Intent intent = new Intent(action);
             intent.putExtra("enableFlags", enableFlags);
             intent.putExtra("disableFlags", disableFlags);
+            intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
             getContext().sendOrderedBroadcastAsUser(intent, UserHandle.CURRENT, null,
                     mResultReceiver, null, Activity.RESULT_OK, null, null);
 
@@ -550,6 +550,11 @@
         }
     }
 
+    private void sendForegroundBroadcastToAllUsers(String action) {
+        getContext().sendBroadcastAsUser(new Intent(action)
+                .addFlags(Intent.FLAG_RECEIVER_FOREGROUND), UserHandle.ALL);
+    }
+
     private void updateAfterBroadcastLocked(String action, int enableFlags, int disableFlags) {
         // Launch a dock activity
         String category = null;
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 9a638a2..1edc8d2 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -219,7 +219,6 @@
 import android.util.SparseArray;
 import android.util.TimeUtils;
 import android.util.Xml;
-import android.view.Display;
 import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -256,6 +255,7 @@
 import libcore.io.IoUtils;
 import libcore.util.EmptyArray;
 
+import static android.Manifest.permission.CHANGE_CONFIGURATION;
 import static android.Manifest.permission.INTERACT_ACROSS_USERS;
 import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
 import static android.Manifest.permission.MANAGE_ACTIVITY_STACKS;
@@ -279,6 +279,7 @@
 import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.content.res.Configuration.UI_MODE_TYPE_TELEVISION;
+import static android.os.Build.VERSION_CODES.N;
 import static android.os.Process.PROC_CHAR;
 import static android.os.Process.PROC_OUT_LONG;
 import static android.os.Process.PROC_PARENS;
@@ -293,6 +294,7 @@
 import static android.provider.Settings.System.FONT_SCALE;
 import static android.util.TypedValue.COMPLEX_UNIT_DIP;
 import static android.view.Display.DEFAULT_DISPLAY;
+
 import static com.android.internal.util.XmlUtils.readBooleanAttribute;
 import static com.android.internal.util.XmlUtils.readIntAttribute;
 import static com.android.internal.util.XmlUtils.readLongAttribute;
@@ -1130,10 +1132,11 @@
     private int mConfigurationSeq;
 
     /**
-     * Temp object used when global configuration is updated. It is also sent to outer world
-     * instead of {@link #getGlobalConfiguration} because we don't trust anyone...
+     * Temp object used when global and/or display override configuration is updated. It is also
+     * sent to outer world instead of {@link #getGlobalConfiguration} because we don't trust
+     * anyone...
      */
-    private Configuration mTempGlobalConfig = new Configuration();
+    private Configuration mTempConfig = new Configuration();
 
     private final UpdateConfigurationResult mTmpUpdateConfigurationResult =
             new UpdateConfigurationResult();
@@ -2717,14 +2720,14 @@
 
         mTrackingAssociations = "1".equals(SystemProperties.get("debug.track-associations"));
 
-        mTempGlobalConfig.setToDefaults();
-        mTempGlobalConfig.setLocales(LocaleList.getDefault());
-        mConfigurationSeq = mTempGlobalConfig.seq = 1;
+        mTempConfig.setToDefaults();
+        mTempConfig.setLocales(LocaleList.getDefault());
+        mConfigurationSeq = mTempConfig.seq = 1;
 
         mProcessCpuTracker.init();
 
         mStackSupervisor = new ActivityStackSupervisor(this);
-        mStackSupervisor.onConfigurationChanged(mTempGlobalConfig);
+        mStackSupervisor.onConfigurationChanged(mTempConfig);
         mCompatModePackages = new CompatModePackages(this, systemDir, mHandler);
         mIntentFirewall = new IntentFirewall(new IntentFirewallInterface(), mHandler);
         mActivityStarter = new ActivityStarter(this, mStackSupervisor);
@@ -4069,7 +4072,7 @@
     @Override
     public int getPackageProcessState(String packageName, String callingPackage) {
         if (!hasUsageStatsPermission(callingPackage)) {
-            enforceCallingPermission(android.Manifest.permission.GET_PACKAGE_IMPORTANCE,
+            enforceCallingPermission(android.Manifest.permission.PACKAGE_USAGE_STATS,
                     "getPackageProcessState");
         }
 
@@ -4079,20 +4082,12 @@
                 final ProcessRecord proc = mLruProcesses.get(i);
                 if (procState == ActivityManager.PROCESS_STATE_NONEXISTENT
                         || procState > proc.setProcState) {
-                    boolean found = false;
-                    for (int j=proc.pkgList.size()-1; j>=0 && !found; j--) {
-                        if (proc.pkgList.keyAt(j).equals(packageName)) {
-                            procState = proc.setProcState;
-                            found = true;
-                        }
+                    if (proc.pkgList.containsKey(packageName)) {
+                        procState = proc.setProcState;
+                        break;
                     }
-                    if (proc.pkgDeps != null && !found) {
-                        for (int j=proc.pkgDeps.size()-1; j>=0; j--) {
-                            if (proc.pkgDeps.valueAt(j).equals(packageName)) {
-                                procState = proc.setProcState;
-                                break;
-                            }
-                        }
+                    if (proc.pkgDeps != null && proc.pkgDeps.contains(packageName)) {
+                        procState = proc.setProcState;
                     }
                 }
             }
@@ -4762,23 +4757,12 @@
             if (r == null) {
                 return;
             }
-            TaskRecord task = r.task;
-            if (task != null && (!task.mFullscreen || !task.getStack().mFullscreen)) {
-                // Fixed screen orientation isn't supported when activities aren't in full screen
-                // mode.
-                return;
-            }
             final long origId = Binder.clearCallingIdentity();
-            mWindowManager.setAppOrientation(r.appToken, requestedOrientation);
-            Configuration config = mWindowManager.updateOrientationFromAppTokens(
-                    getGlobalConfiguration(), r.mayFreezeScreenLocked(r.app) ? r.appToken : null);
-            if (config != null) {
-                r.frozenBeforeDestroy = true;
-                if (!updateConfigurationLocked(config, r, false)) {
-                    mStackSupervisor.resumeFocusedStackTopActivityLocked();
-                }
+            try {
+                r.setRequestedOrientation(requestedOrientation);
+            } finally {
+                Binder.restoreCallingIdentity(origId);
             }
-            Binder.restoreCallingIdentity(origId);
         }
     }
 
@@ -14877,6 +14861,7 @@
         }
         if (dumpPackage == null) {
             pw.println("  mGlobalConfiguration: " + getGlobalConfiguration());
+            mStackSupervisor.dumpDisplayConfigs(pw, "  ");
         }
         if (dumpAll) {
             pw.println("  mConfigWillChange: " + getFocusedStack().mConfigWillChange);
@@ -18939,8 +18924,7 @@
 
     @Override
     public void updatePersistentConfiguration(Configuration values) {
-        enforceCallingPermission(android.Manifest.permission.CHANGE_CONFIGURATION,
-                "updatePersistentConfiguration()");
+        enforceCallingPermission(CHANGE_CONFIGURATION, "updatePersistentConfiguration()");
         enforceWriteSettingsPermission("updatePersistentConfiguration()");
         if (values == null) {
             throw new NullPointerException("Configuration must not be null");
@@ -18965,12 +18949,16 @@
     private void updateFontScaleIfNeeded(@UserIdInt int userId) {
         final float scaleFactor = Settings.System.getFloatForUser(mContext.getContentResolver(),
                 FONT_SCALE, 1.0f, userId);
-        if (getGlobalConfiguration().fontScale != scaleFactor) {
-            final Configuration configuration = mWindowManager.computeNewConfiguration();
-            configuration.fontScale = scaleFactor;
-            synchronized (this) {
-                updatePersistentConfigurationLocked(configuration, userId);
+
+        synchronized (this) {
+            if (getGlobalConfiguration().fontScale == scaleFactor) {
+                return;
             }
+
+            final Configuration configuration
+                    = mWindowManager.computeNewConfiguration(DEFAULT_DISPLAY);
+            configuration.fontScale = scaleFactor;
+            updatePersistentConfigurationLocked(configuration, userId);
         }
     }
 
@@ -18995,21 +18983,20 @@
 
     @Override
     public boolean updateConfiguration(Configuration values) {
-        enforceCallingPermission(android.Manifest.permission.CHANGE_CONFIGURATION,
-                "updateConfiguration()");
+        enforceCallingPermission(CHANGE_CONFIGURATION, "updateConfiguration()");
 
         synchronized(this) {
             if (values == null && mWindowManager != null) {
                 // sentinel: fetch the current configuration from the window manager
-                values = mWindowManager.computeNewConfiguration();
+                values = mWindowManager.computeNewConfiguration(DEFAULT_DISPLAY);
             }
 
             if (mWindowManager != null) {
+                // Update OOM levels based on display size.
                 mProcessList.applyDisplaySize(mWindowManager);
             }
 
             final long origId = Binder.clearCallingIdentity();
-
             try {
                 if (values != null) {
                     Settings.System.clearConfiguration(values);
@@ -19096,8 +19083,8 @@
     /** Update default (global) configuration and notify listeners about changes. */
     private int updateGlobalConfiguration(@NonNull Configuration values, boolean initLocale,
             boolean persistent, int userId, boolean deferResume) {
-        mTempGlobalConfig.setTo(getGlobalConfiguration());
-        final int changes = mTempGlobalConfig.updateFrom(values);
+        mTempConfig.setTo(getGlobalConfiguration());
+        final int changes = mTempConfig.updateFrom(values);
         if (changes == 0) {
             return 0;
         }
@@ -19124,33 +19111,33 @@
         }
 
         mConfigurationSeq = Math.max(++mConfigurationSeq, 1);
-        mTempGlobalConfig.seq = mConfigurationSeq;
+        mTempConfig.seq = mConfigurationSeq;
 
         // Update stored global config and notify everyone about the change.
-        mStackSupervisor.onConfigurationChanged(mTempGlobalConfig);
+        mStackSupervisor.onConfigurationChanged(mTempConfig);
 
-        Slog.i(TAG, "Config changes=" + Integer.toHexString(changes) + " " + mTempGlobalConfig);
+        Slog.i(TAG, "Config changes=" + Integer.toHexString(changes) + " " + mTempConfig);
         // TODO(multi-display): Update UsageEvents#Event to include displayId.
-        mUsageStatsService.reportConfigurationChange(mTempGlobalConfig,
+        mUsageStatsService.reportConfigurationChange(mTempConfig,
                 mUserController.getCurrentUserIdLocked());
 
         // TODO: If our config changes, should we auto dismiss any currently showing dialogs?
-        mShowDialogs = shouldShowDialogs(mTempGlobalConfig, mInVrMode);
+        mShowDialogs = shouldShowDialogs(mTempConfig, mInVrMode);
 
         AttributeCache ac = AttributeCache.instance();
         if (ac != null) {
-            ac.updateConfiguration(mTempGlobalConfig);
+            ac.updateConfiguration(mTempConfig);
         }
 
         // Make sure all resources in our process are updated right now, so that anyone who is going
         // to retrieve resource values after we return will be sure to get the new ones. This is
         // especially important during boot, where the first config change needs to guarantee all
         // resources have that config before following boot code is executed.
-        mSystemThread.applyConfigurationToResources(mTempGlobalConfig);
+        mSystemThread.applyConfigurationToResources(mTempConfig);
 
         // We need another copy of global config because we're scheduling some calls instead of
         // running them in place. We need to be sure that object we send will be handled unchanged.
-        final Configuration configCopy = new Configuration(mTempGlobalConfig);
+        final Configuration configCopy = new Configuration(mTempConfig);
         if (persistent && Settings.System.hasInterestingConfigurationChanges(changes)) {
             Message msg = mHandler.obtainMessage(UPDATE_CONFIGURATION_MSG);
             msg.obj = configCopy;
@@ -19158,16 +19145,6 @@
             mHandler.sendMessage(msg);
         }
 
-        // TODO(multi-display): Clear also on secondary display density change?
-        final boolean isDensityChange = (changes & ActivityInfo.CONFIG_DENSITY) != 0;
-        if (isDensityChange) {
-            // Reset the unsupported display size dialog.
-            mUiHandler.sendEmptyMessage(SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG_MSG);
-
-            killAllBackgroundProcessesExcept(Build.VERSION_CODES.N,
-                    ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
-        }
-
         for (int i = mLruProcesses.size() - 1; i >= 0; i--) {
             ProcessRecord app = mLruProcesses.get(i);
             try {
@@ -19197,13 +19174,116 @@
                     UserHandle.USER_ALL);
         }
 
+        // Override configuration of the default display duplicates global config, so we need to
+        // update it also. This will also notify WindowManager about changes.
+        performDisplayOverrideConfigUpdate(mStackSupervisor.getConfiguration(), deferResume,
+                DEFAULT_DISPLAY);
+
+        return changes;
+    }
+
+    @Override
+    public boolean updateDisplayOverrideConfiguration(Configuration values, int displayId) {
+        enforceCallingPermission(CHANGE_CONFIGURATION, "updateDisplayOverrideConfiguration()");
+
+        synchronized (this) {
+            if (values == null && mWindowManager != null) {
+                // sentinel: fetch the current configuration from the window manager
+                values = mWindowManager.computeNewConfiguration(displayId);
+            }
+
+            if (mWindowManager != null) {
+                // Update OOM levels based on display size.
+                mProcessList.applyDisplaySize(mWindowManager);
+            }
+
+            final long origId = Binder.clearCallingIdentity();
+            try {
+                if (values != null) {
+                    Settings.System.clearConfiguration(values);
+                }
+                updateDisplayOverrideConfigurationLocked(values, null /* starting */,
+                        false /* deferResume */, displayId, mTmpUpdateConfigurationResult);
+                return mTmpUpdateConfigurationResult.changes != 0;
+            } finally {
+                Binder.restoreCallingIdentity(origId);
+            }
+        }
+    }
+
+    boolean updateDisplayOverrideConfigurationLocked(Configuration values, ActivityRecord starting,
+            boolean deferResume, int displayId) {
+        return updateDisplayOverrideConfigurationLocked(values, starting, deferResume /* deferResume */,
+                displayId, null /* result */);
+    }
+
+    /**
+     * Updates override configuration specific for the selected display. If no config is provided,
+     * new one will be computed in WM based on current display info.
+     */
+    private boolean updateDisplayOverrideConfigurationLocked(Configuration values,
+            ActivityRecord starting, boolean deferResume, int displayId,
+            UpdateConfigurationResult result) {
+        int changes = 0;
+        boolean kept = true;
+
+        if (mWindowManager != null) {
+            mWindowManager.deferSurfaceLayout();
+        }
+        try {
+            if (values != null) {
+                if (displayId == DEFAULT_DISPLAY) {
+                    // Override configuration of the default display duplicates global config, so
+                    // we're calling global config update instead for default display. It will also
+                    // apply the correct override config.
+                    changes = updateGlobalConfiguration(values, false /* initLocale */,
+                            false /* persistent */, UserHandle.USER_NULL /* userId */, deferResume);
+                } else {
+                    changes = performDisplayOverrideConfigUpdate(values, deferResume, displayId);
+                }
+            }
+
+            kept = ensureConfigAndVisibilityAfterUpdate(starting, changes);
+        } finally {
+            if (mWindowManager != null) {
+                mWindowManager.continueSurfaceLayout();
+            }
+        }
+
+        if (result != null) {
+            result.changes = changes;
+            result.activityRelaunched = !kept;
+        }
+        return kept;
+    }
+
+    private int performDisplayOverrideConfigUpdate(Configuration values, boolean deferResume,
+            int displayId) {
+        mTempConfig.setTo(mStackSupervisor.getDisplayOverrideConfiguration(displayId));
+        final int changes = mTempConfig.updateFrom(values);
+        if (changes == 0) {
+            return 0;
+        }
+
+        Slog.i(TAG, "Override config changes=" + Integer.toHexString(changes) + " " + mTempConfig
+                + " for displayId=" + displayId);
+        mStackSupervisor.setDisplayOverrideConfiguration(mTempConfig, displayId);
+
+        final boolean isDensityChange = (changes & ActivityInfo.CONFIG_DENSITY) != 0;
+        if (isDensityChange) {
+            // Reset the unsupported display size dialog.
+            mUiHandler.sendEmptyMessage(SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG_MSG);
+
+            killAllBackgroundProcessesExcept(N, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+        }
+
         // Update the configuration with WM first and check if any of the stacks need to be resized
         // due to the configuration change. If so, resize the stacks now and do any relaunches if
         // necessary. This way we don't need to relaunch again afterwards in
         // ensureActivityConfigurationLocked().
         if (mWindowManager != null) {
             final int[] resizedStacks =
-                    mWindowManager.setNewConfiguration(mTempGlobalConfig);
+                    mWindowManager.setNewDisplayOverrideConfiguration(mTempConfig, displayId);
             if (resizedStacks != null) {
                 for (int stackId : resizedStacks) {
                     resizeStackWithBoundsFromWindowManager(stackId, deferResume);
@@ -22403,4 +22483,29 @@
         // before the profile user is unlocked.
         return rInfo != null && rInfo.activityInfo != null;
     }
+
+    /**
+     * Attach an agent to the specified process (proces name or PID)
+     */
+    public void attachAgent(String process, String path) {
+        try {
+            synchronized (this) {
+                ProcessRecord proc = findProcessLocked(process, UserHandle.USER_SYSTEM, "attachAgent");
+                if (proc == null || proc.thread == null) {
+                    throw new IllegalArgumentException("Unknown process: " + process);
+                }
+
+                boolean isDebuggable = "1".equals(SystemProperties.get(SYSTEM_DEBUGGABLE, "0"));
+                if (!isDebuggable) {
+                    if ((proc.info.flags & ApplicationInfo.FLAG_DEBUGGABLE) == 0) {
+                        throw new SecurityException("Process not debuggable: " + proc);
+                    }
+                }
+
+                proc.thread.attachAgent(path);
+            }
+        } catch (RemoteException e) {
+            throw new IllegalStateException("Process disappeared");
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index c7a04c1..7a692b6 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -220,6 +220,8 @@
                     return runTask(pw);
                 case "write":
                     return runWrite(pw);
+                case "attach-agent":
+                    return runAttachAgent(pw);
                 default:
                     return handleDefaultCommands(cmd);
             }
@@ -2462,6 +2464,21 @@
         return 0;
     }
 
+    int runAttachAgent(PrintWriter pw) {
+        // TODO: revisit the permissions required for attaching agents
+        mInternal.enforceCallingPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER,
+                "attach-agent");
+        String process = getNextArgRequired();
+        String agent = getNextArgRequired();
+        String opt;
+        if ((opt = getNextArg()) != null) {
+            pw.println("Error: Unknown option: " + opt);
+            return -1;
+        }
+        mInternal.attachAgent(process, agent);
+        return 0;
+    }
+
     @Override
     public void onHelp() {
         PrintWriter pw = getOutPrintWriter();
@@ -2619,6 +2636,8 @@
             pw.println("      Optionally controls lenient background check mode, returns current mode.");
             pw.println("  get-uid-state <UID>");
             pw.println("      Gets the process state of an app given its <UID>.");
+            pw.println("  attach-agent <PROCESS> <FILE>");
+            pw.println("    Attach an agent to the specified <PROCESS>, which may be either a process name or a PID.");
             pw.println("  get-config");
             pw.println("      Rtrieve the configuration and any recent configurations of the device.");
             pw.println("  suppress-resize-config-changes <true|false>");
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index b7618a1..abe4f30 100644
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -1646,6 +1646,17 @@
         return null;
     }
 
+    /**
+     * @return display id to which this record is attached, -1 if not attached.
+     */
+    int getDisplayId() {
+        final ActivityStack stack = getStack();
+        if (stack == null) {
+            return -1;
+        }
+        return stack.mDisplayId;
+    }
+
     final boolean isDestroyable() {
         if (finishing || app == null || state == ActivityState.DESTROYING
                 || state == ActivityState.DESTROYED) {
@@ -1704,6 +1715,26 @@
         }
     }
 
+    void setRequestedOrientation(int requestedOrientation) {
+        if (task != null && (!task.mFullscreen || !task.getStack().mFullscreen)) {
+            // Fixed screen orientation isn't supported when activities aren't in full screen mode.
+            return;
+        }
+
+        service.mWindowManager.setAppOrientation(appToken, requestedOrientation);
+        final int displayId = getDisplayId();
+        final Configuration config = service.mWindowManager.updateOrientationFromAppTokens(
+                mStackSupervisor.getDisplayOverrideConfiguration(displayId),
+                mayFreezeScreenLocked(app) ? appToken : null, displayId);
+        if (config != null) {
+            frozenBeforeDestroy = true;
+            if (!service.updateDisplayOverrideConfigurationLocked(config, this,
+                    false /* deferResume */, displayId)) {
+                mStackSupervisor.resumeFocusedStackTopActivityLocked();
+            }
+        }
+    }
+
     // TODO: now used only in one place to address race-condition. Remove when that will be fixed.
     void setLastReportedConfiguration(@NonNull Configuration config) {
         mLastReportedConfiguration.setTo(config);
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 55066fd..22d8078 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -2285,13 +2285,14 @@
             // the screen based on the new activity order.
             boolean notUpdated = true;
             if (mStackSupervisor.isFocusedStack(this)) {
-                Configuration config = mWindowManager.updateOrientationFromAppTokens(
-                        mService.getGlobalConfiguration(),
-                        next.mayFreezeScreenLocked(next.app) ? next.appToken : null);
+                final Configuration config = mWindowManager.updateOrientationFromAppTokens(
+                        mStackSupervisor.getDisplayOverrideConfiguration(mDisplayId),
+                        next.mayFreezeScreenLocked(next.app) ? next.appToken : null, mDisplayId);
                 if (config != null) {
                     next.frozenBeforeDestroy = true;
                 }
-                notUpdated = !mService.updateConfigurationLocked(config, next, false);
+                notUpdated = !mService.updateDisplayOverrideConfigurationLocked(config, next,
+                        false /* deferResume */, mDisplayId);
             }
 
             if (notUpdated) {
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index a111230..ca36908 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -419,7 +419,7 @@
     }
 
     @Override
-    protected ConfigurationContainer getChildAt(int index) {
+    protected ActivityDisplay getChildAt(int index) {
         return mActivityDisplays.valueAt(index);
     }
 
@@ -428,6 +428,24 @@
         return null;
     }
 
+    Configuration getDisplayOverrideConfiguration(int displayId) {
+        final ActivityDisplay activityDisplay = mActivityDisplays.get(displayId);
+        if (activityDisplay == null) {
+            throw new IllegalArgumentException("No display found with id: " + displayId);
+        }
+
+        return activityDisplay.getOverrideConfiguration();
+    }
+
+    void setDisplayOverrideConfiguration(Configuration overrideConfiguration, int displayId) {
+        final ActivityDisplay activityDisplay = mActivityDisplays.get(displayId);
+        if (activityDisplay == null) {
+            throw new IllegalArgumentException("No display found with id: " + displayId);
+        }
+
+        activityDisplay.onOverrideConfigurationChanged(overrideConfiguration);
+    }
+
     static class FindTaskResult {
         ActivityRecord r;
         boolean matchedByRootAffinity;
@@ -1190,20 +1208,20 @@
             r.startLaunchTickingLocked();
         }
 
-        // Have the window manager re-evaluate the orientation of
-        // the screen based on the new activity order.  Note that
-        // as a result of this, it can call back into the activity
-        // manager with a new orientation.  We don't care about that,
-        // because the activity is not currently running so we are
-        // just restarting it anyway.
+        // Have the window manager re-evaluate the orientation of the screen based on the new
+        // activity order.  Note that as a result of this, it can call back into the activity
+        // manager with a new orientation.  We don't care about that, because the activity is not
+        // currently running so we are just restarting it anyway.
         if (checkConfig) {
-            Configuration config = mWindowManager.updateOrientationFromAppTokens(
-                    mService.getGlobalConfiguration(),
-                    r.mayFreezeScreenLocked(app) ? r.appToken : null);
+            final int displayId = r.getDisplayId();
+            final Configuration config = mWindowManager.updateOrientationFromAppTokens(
+                    getDisplayOverrideConfiguration(displayId),
+                    r.mayFreezeScreenLocked(app) ? r.appToken : null, displayId);
             // Deferring resume here because we're going to launch new activity shortly.
             // We don't want to perform a redundant launch of the same record while ensuring
             // configurations and trying to resume top activity of focused stack.
-            mService.updateConfigurationLocked(config, r, false, true /* deferResume */);
+            mService.updateDisplayOverrideConfigurationLocked(config, r, true /* deferResume */,
+                    displayId);
         }
 
         r.app = app;
@@ -3156,6 +3174,20 @@
     }
 
     /**
+     * Dump all connected displays' configurations.
+     * @param prefix Prefix to apply to each line of the dump.
+     */
+    void dumpDisplayConfigs(PrintWriter pw, String prefix) {
+        pw.print(prefix); pw.println("Display override configurations:");
+        final int displayCount = mActivityDisplays.size();
+        for (int i = 0; i < displayCount; i++) {
+            final ActivityDisplay activityDisplay = mActivityDisplays.valueAt(i);
+            pw.print(prefix); pw.print("  "); pw.print(activityDisplay.mDisplayId); pw.print(": ");
+                    pw.println(activityDisplay.getOverrideConfiguration());
+        }
+    }
+
+    /**
      * Dumps the activities matching the given {@param name} in the either the focused stack
      * or all visible stacks if {@param dumpVisibleStacks} is true.
      */
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index b8b16d5..6e5c025 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -5408,18 +5408,15 @@
                 }
             } else if (mDismissKeyguard != DISMISS_KEYGUARD_NONE) {
                 mKeyguardHidden = false;
-                boolean willDismiss = false;
+                boolean dismissKeyguard = false;
+                final boolean trusted = mKeyguardDelegate.isTrusted();
                 if (mDismissKeyguard == DISMISS_KEYGUARD_START) {
-                    final boolean trusted = mKeyguardDelegate.isTrusted();
-                    willDismiss = trusted && mKeyguardOccluded && mKeyguardDelegate != null
-                            && mKeyguardDelegate.isShowing();
+                    final boolean willDismiss = trusted && mKeyguardOccluded
+                            && mKeyguardDelegate != null && mKeyguardDelegate.isShowing();
                     if (willDismiss) {
                         mCurrentlyDismissingKeyguard = true;
                     }
-
-                    // Only launch the next keyguard unlock window once per window.
-                    mHandler.post(() -> mKeyguardDelegate.dismiss(
-                            trusted /* allowWhileOccluded */));
+                    dismissKeyguard = true;
                 }
 
                 // If we are currently dismissing Keyguard, there is no need to unocclude it.
@@ -5430,6 +5427,12 @@
                                 | FINISH_LAYOUT_REDO_WALLPAPER;
                     }
                 }
+
+                if (dismissKeyguard) {
+                    // Only launch the next keyguard unlock window once per window.
+                    mHandler.post(() -> mKeyguardDelegate.dismiss(
+                            trusted /* allowWhileOccluded */));
+                }
             } else {
                 mWinDismissingKeyguard = null;
                 mSecureDismissingKeyguard = false;
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 56eaa83..d46b535 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -949,7 +949,7 @@
 
             mService.updateFocusedWindowLocked(
                     UPDATE_FOCUS_WILL_PLACE_SURFACES, true /*updateInputWindows*/);
-            mService.getDefaultDisplayContentLocked().setLayoutNeeded();
+            getDisplayContent().setLayoutNeeded();
             mService.mWindowPlacerLocked.performSurfacePlacement();
             Binder.restoreCallingIdentity(origId);
             return true;
diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java
index 8b5f62e..f75f224 100644
--- a/services/core/java/com/android/server/wm/DockedStackDividerController.java
+++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java
@@ -371,6 +371,7 @@
     }
 
     void notifyDockedStackExistsChanged(boolean exists) {
+        // TODO(multi-display): Perform all actions only for current display.
         final int size = mDockedStackListeners.beginBroadcast();
         for (int i = 0; i < size; ++i) {
             final IDockedStackListener listener = mDockedStackListeners.getBroadcastItem(i);
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index 065a3dc..6a06ef3 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManager.INPUT_CONSUMER_PIP;
 import static android.view.WindowManager.INPUT_CONSUMER_WALLPAPER;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DRAG;
@@ -384,7 +385,8 @@
     /* Notifies that the input device configuration has changed. */
     @Override
     public void notifyConfigurationChanged() {
-        mService.sendNewConfiguration();
+        // TODO(multi-display): Notify proper displays that are associated with this input device.
+        mService.sendNewConfiguration(DEFAULT_DISPLAY);
 
         synchronized (mInputDevicesReadyMonitor) {
             if (!mInputDevicesReady) {
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 24a0d1a..90e27ea 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -236,7 +236,7 @@
             mService.configureDisplayPolicyLocked(dc);
 
             // TODO(multi-display): Create an input channel for each display with touch capability.
-            if (displayId == Display.DEFAULT_DISPLAY) {
+            if (displayId == DEFAULT_DISPLAY) {
                 dc.mTapDetector = new TaskTapPointerEventListener(
                         mService, dc);
                 mService.registerPointerEventListener(dc.mTapDetector);
@@ -274,8 +274,7 @@
 
             mService.mStackIdToStack.put(stackId, stack);
             if (stackId == DOCKED_STACK_ID) {
-                mService.getDefaultDisplayContentLocked().mDividerControllerLocked
-                        .notifyDockedStackExistsChanged(true);
+                dc.mDividerControllerLocked.notifyDockedStackExistsChanged(true);
             }
         }
 
@@ -569,8 +568,33 @@
         }
     }
 
-    /** Set new config and return array of ids of stacks that were changed during update. */
-    int[] setGlobalConfigurationIfNeeded(Configuration newConfiguration) {
+    /**
+     * Set new display override config and return array of ids of stacks that were changed during
+     * update. If called for the default display, global configuration will also be updated.
+     */
+    int[] setDisplayOverrideConfigurationIfNeeded(Configuration newConfiguration, int displayId) {
+        final DisplayContent displayContent = getDisplayContent(displayId);
+        if (displayContent == null) {
+            throw new IllegalArgumentException("Display not found for id: " + displayId);
+        }
+
+        final Configuration currentConfig = displayContent.getOverrideConfiguration();
+        final boolean configChanged = currentConfig.diff(newConfiguration) != 0;
+        if (!configChanged) {
+            return null;
+        }
+        displayContent.onOverrideConfigurationChanged(currentConfig);
+
+        if (displayId == DEFAULT_DISPLAY) {
+            // Override configuration of the default display duplicates global config. In this case
+            // we also want to update the global config.
+            return setGlobalConfigurationIfNeeded(newConfiguration);
+        } else {
+            return updateStackBoundsAfterConfigChange(displayId);
+        }
+    }
+
+    private int[] setGlobalConfigurationIfNeeded(Configuration newConfiguration) {
         final boolean configChanged = getConfiguration().diff(newConfiguration) != 0;
         if (!configChanged) {
             return null;
@@ -603,6 +627,16 @@
         return mChangedStackList.isEmpty() ? null : ArrayUtils.convertToIntArray(mChangedStackList);
     }
 
+    /** Same as {@link #updateStackBoundsAfterConfigChange()} but only for a specific display. */
+    private int[] updateStackBoundsAfterConfigChange(int displayId) {
+        mChangedStackList.clear();
+
+        final DisplayContent dc = getDisplayContent(displayId);
+        dc.updateStackBoundsAfterConfigChange(mChangedStackList);
+
+        return mChangedStackList.isEmpty() ? null : ArrayUtils.convertToIntArray(mChangedStackList);
+    }
+
     private void prepareFreezingTaskBounds() {
         for (int i = mChildren.size() - 1; i >= 0; i--) {
             mChildren.get(i).prepareFreezingTaskBounds();
@@ -1145,8 +1179,10 @@
 
         if (mUpdateRotation) {
             if (DEBUG_ORIENTATION) Slog.d(TAG, "Performing post-rotate rotation");
-            if (mService.updateRotationUncheckedLocked(false)) {
-                mService.mH.sendEmptyMessage(SEND_NEW_CONFIGURATION);
+            // TODO(multi-display): Update rotation for different displays separately.
+            final int displayId = defaultDisplay.getDisplayId();
+            if (mService.updateRotationUncheckedLocked(false, displayId)) {
+                mService.mH.obtainMessage(SEND_NEW_CONFIGURATION, displayId).sendToTarget();
             } else {
                 mUpdateRotation = false;
             }
@@ -1231,7 +1267,7 @@
             final int displayId = dc.getDisplayId();
             final int dw = displayInfo.logicalWidth;
             final int dh = displayInfo.logicalHeight;
-            final boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY);
+            final boolean isDefaultDisplay = (displayId == DEFAULT_DISPLAY);
             final WindowSurfacePlacer surfacePlacer = mService.mWindowPlacerLocked;
 
             // Reset for each display.
@@ -1259,9 +1295,9 @@
                 if (isDefaultDisplay
                         && (dc.pendingLayoutChanges & FINISH_LAYOUT_REDO_CONFIG) != 0) {
                     if (DEBUG_LAYOUT) Slog.v(TAG, "Computing new config from layout");
-                    if (mService.updateOrientationFromAppTokensLocked(true)) {
+                    if (mService.updateOrientationFromAppTokensLocked(true, displayId)) {
                         dc.setLayoutNeeded();
-                        mService.mH.sendEmptyMessage(SEND_NEW_CONFIGURATION);
+                        mService.mH.obtainMessage(SEND_NEW_CONFIGURATION, displayId).sendToTarget();
                     }
                 }
 
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 19c9b7d..7f543f9 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -327,8 +327,9 @@
      *                    the adjusted bounds's top.
      */
     void alignToAdjustedBounds(Rect adjustedBounds, Rect tempInsetBounds, boolean alignBottom) {
-        final Configuration overrideConfig = getOverrideConfiguration();
-        if (!isResizeable() || Configuration.EMPTY.equals(overrideConfig)) {
+        // Task override config might be empty, while display or stack override config isn't, so
+        // we have to check merged override config here.
+        if (!isResizeable() || Configuration.EMPTY.equals(getMergedOverrideConfiguration())) {
             return;
         }
 
@@ -340,7 +341,7 @@
             mTmpRect2.offsetTo(adjustedBounds.left, adjustedBounds.top);
         }
         setTempInsetBounds(tempInsetBounds);
-        resizeLocked(mTmpRect2, overrideConfig, false /* forced */);
+        resizeLocked(mTmpRect2, getOverrideConfiguration(), false /* forced */);
     }
 
     /** Return true if the current bound can get outputted to the rest of the system as-is. */
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 9effb8d..5402f0a 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -448,8 +448,7 @@
 
         // Calculate the current position.
         final DisplayInfo displayInfo = mDisplayContent.getDisplayInfo();
-        final int dividerSize = mService.getDefaultDisplayContentLocked()
-                .getDockedDividerController().getContentWidth();
+        final int dividerSize = mDisplayContent.getDockedDividerController().getContentWidth();
         final int dockSide = getDockSide(outBounds);
         final int dividerPosition = DockedDividerUtils.calculatePositionForBounds(outBounds,
                 dockSide, dividerSize);
@@ -783,13 +782,14 @@
             mAnimationBackgroundSurface.destroySurface();
             mAnimationBackgroundSurface = null;
         }
+        final DockedStackDividerController dividerController =
+                mDisplayContent.mDividerControllerLocked;
         mDisplayContent = null;
 
         mService.mWindowPlacerLocked.requestTraversal();
 
         if (mStackId == DOCKED_STACK_ID) {
-            mService.getDefaultDisplayContentLocked().mDividerControllerLocked
-                    .notifyDockedStackExistsChanged(false);
+            dividerController.notifyDockedStackExistsChanged(false);
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 40001b2..e6c9512 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -1413,7 +1413,7 @@
             win.applyAdjustForImeIfNeeded();
 
             if (type == TYPE_DOCK_DIVIDER) {
-                getDefaultDisplayContentLocked().getDockedDividerController().setWindow(win);
+                mRoot.getDisplayContent(displayId).getDockedDividerController().setWindow(win);
             }
 
             final WindowStateAnimator winAnimator = win.mWinAnimator;
@@ -1480,13 +1480,13 @@
             if (localLOGV || DEBUG_ADD_REMOVE) Slog.v(TAG_WM, "addWindow: New client "
                     + client.asBinder() + ": window=" + win + " Callers=" + Debug.getCallers(5));
 
-            if (win.isVisibleOrAdding() && updateOrientationFromAppTokensLocked(false)) {
+            if (win.isVisibleOrAdding() && updateOrientationFromAppTokensLocked(false, displayId)) {
                 reportNewConfig = true;
             }
         }
 
         if (reportNewConfig) {
-            sendNewConfiguration();
+            sendNewConfiguration(displayId);
         }
 
         Binder.restoreCallingIdentity(origId);
@@ -1881,11 +1881,13 @@
                         == PackageManager.PERMISSION_GRANTED;
 
         long origId = Binder.clearCallingIdentity();
+        final int displayId;
         synchronized(mWindowMap) {
             WindowState win = windowForClientLocked(session, client, false);
             if (win == null) {
                 return 0;
             }
+            displayId = win.getDisplayId();
 
             WindowStateAnimator winAnimator = win.mWinAnimator;
             if (viewVisibility != View.GONE) {
@@ -2066,16 +2068,16 @@
             }
 
             if (wallpaperMayMove) {
-                getDefaultDisplayContentLocked().pendingLayoutChanges |=
+                win.getDisplayContent().pendingLayoutChanges |=
                         WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
             }
 
             win.setDisplayLayoutNeeded();
             win.mGivenInsetsPending = (flags&WindowManagerGlobal.RELAYOUT_INSETS_PENDING) != 0;
-            configChanged = updateOrientationFromAppTokensLocked(false);
+            configChanged = updateOrientationFromAppTokensLocked(false, displayId);
             mWindowPlacerLocked.performSurfacePlacement();
             if (toBeDisplayed && win.mIsWallpaper) {
-                DisplayInfo displayInfo = getDefaultDisplayInfoLocked();
+                DisplayInfo displayInfo = win.getDisplayContent().getDisplayInfo();
                 dc.mWallpaperController.updateWallpaperOffset(
                         win, displayInfo.logicalWidth, displayInfo.logicalHeight, false);
             }
@@ -2121,7 +2123,7 @@
         }
 
         if (configChanged) {
-            sendNewConfiguration();
+            sendNewConfiguration(displayId);
         }
         Binder.restoreCallingIdentity(origId);
         return result;
@@ -2156,9 +2158,8 @@
             }
             win.destroyOrSaveSurface();
         }
-        //TODO (multidisplay): Magnification is supported only for the default
-        if (mAccessibilityController != null
-                && win.getDisplayId() == DEFAULT_DISPLAY) {
+        // TODO(multidisplay): Magnification is supported only for the default display.
+        if (mAccessibilityController != null && win.getDisplayId() == DEFAULT_DISPLAY) {
             mAccessibilityController.onWindowTransitionLocked(win, transit);
         }
         return focusMayChange;
@@ -2278,7 +2279,7 @@
         }
     }
 
-    public void finishDrawingWindow(Session session, IWindow client) {
+    void finishDrawingWindow(Session session, IWindow client) {
         final long origId = Binder.clearCallingIdentity();
         try {
             synchronized (mWindowMap) {
@@ -2287,7 +2288,7 @@
                         + (win != null ? win.mWinAnimator.drawStateToString() : "null"));
                 if (win != null && win.mWinAnimator.finishDrawingLocked()) {
                     if ((win.mAttrs.flags & FLAG_SHOW_WALLPAPER) != 0) {
-                        getDefaultDisplayContentLocked().pendingLayoutChanges |=
+                        win.getDisplayContent().pendingLayoutChanges |=
                                 WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
                     }
                     win.setDisplayLayoutNeeded();
@@ -2530,32 +2531,34 @@
     }
 
     @Override
-    public Configuration updateOrientationFromAppTokens(
-            Configuration currentConfig, IBinder freezeThisOneIfNeeded) {
+    public Configuration updateOrientationFromAppTokens(Configuration currentConfig,
+            IBinder freezeThisOneIfNeeded, int displayId) {
         if (!checkCallingPermission(MANAGE_APP_TOKENS, "updateOrientationFromAppTokens()")) {
             throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
         }
 
-        Configuration config = null;
-        long ident = Binder.clearCallingIdentity();
-
-        synchronized(mWindowMap) {
-            config = updateOrientationFromAppTokensLocked(currentConfig,
-                    freezeThisOneIfNeeded);
+        final Configuration config;
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            synchronized(mWindowMap) {
+                config = updateOrientationFromAppTokensLocked(currentConfig, freezeThisOneIfNeeded,
+                        displayId);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
         }
 
-        Binder.restoreCallingIdentity(ident);
         return config;
     }
 
-    private Configuration updateOrientationFromAppTokensLocked(
-            Configuration currentConfig, IBinder freezeThisOneIfNeeded) {
+    private Configuration updateOrientationFromAppTokensLocked(Configuration currentConfig,
+            IBinder freezeThisOneIfNeeded, int displayId) {
         if (!mDisplayReady) {
             return null;
         }
         Configuration config = null;
 
-        if (updateOrientationFromAppTokensLocked(false)) {
+        if (updateOrientationFromAppTokensLocked(false, displayId)) {
             // If we changed the orientation but mOrientationChangeComplete is already true,
             // we used seamless rotation, and we don't need to freeze the screen.
             if (freezeThisOneIfNeeded != null && !mRoot.mOrientationChangeComplete) {
@@ -2564,7 +2567,7 @@
                     atoken.startFreezingScreen();
                 }
             }
-            config = computeNewConfigurationLocked();
+            config = computeNewConfigurationLocked(displayId);
 
         } else if (currentConfig != null) {
             // No obvious action we need to take, but if our current state mismatches the activity
@@ -2574,10 +2577,10 @@
             // to keep override configs clear of non-empty values (e.g. fontSize).
             mTempConfiguration.unset();
             mTempConfiguration.updateFrom(currentConfig);
-            computeScreenConfigurationLocked(mTempConfiguration);
+            computeScreenConfigurationLocked(mTempConfiguration, displayId);
             if (currentConfig.diff(mTempConfiguration) != 0) {
                 mWaitingForConfig = true;
-                final DisplayContent displayContent = getDefaultDisplayContentLocked();
+                final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
                 displayContent.setLayoutNeeded();
                 int anim[] = new int[2];
                 if (displayContent.isDimming()) {
@@ -2593,31 +2596,28 @@
         return config;
     }
 
-    /*
-     * Determine the new desired orientation of the display, returning
-     * a non-null new Configuration if it has changed from the current
-     * orientation.  IF TRUE IS RETURNED SOMEONE MUST CALL
-     * setNewConfiguration() TO TELL THE WINDOW MANAGER IT CAN UNFREEZE THE
-     * SCREEN.  This will typically be done for you if you call
-     * sendNewConfiguration().
+    /**
+     * Determine the new desired orientation of the display, returning a non-null new Configuration
+     * if it has changed from the current orientation.  IF TRUE IS RETURNED SOMEONE MUST CALL
+     * {@link #setNewDisplayOverrideConfiguration(Configuration, int)} TO TELL THE WINDOW MANAGER IT
+     * CAN UNFREEZE THE SCREEN.  This will typically be done for you if you call
+     * {@link #sendNewConfiguration(int)}.
      *
-     * The orientation is computed from non-application windows first. If none of
-     * the non-application windows specify orientation, the orientation is computed from
-     * application tokens.
-     * @see android.view.IWindowManager#updateOrientationFromAppTokens(
-     * android.os.IBinder)
+     * The orientation is computed from non-application windows first. If none of the
+     * non-application windows specify orientation, the orientation is computed from application
+     * tokens.
+     * @see android.view.IWindowManager#updateOrientationFromAppTokens(Configuration, IBinder, int)
      */
-    boolean updateOrientationFromAppTokensLocked(boolean inTransaction) {
+    boolean updateOrientationFromAppTokensLocked(boolean inTransaction, int displayId) {
         long ident = Binder.clearCallingIdentity();
         try {
-            // TODO: multi-display
-            int req = getDefaultDisplayContentLocked().getOrientation();
+            final int req = mRoot.getDisplayContent(displayId).getOrientation();
             if (req != mLastOrientation) {
                 mLastOrientation = req;
                 //send a message to Policy indicating orientation change to take
                 //action like disabling/enabling sensors etc.,
                 mPolicy.setCurrentOrientationLw(req);
-                if (updateRotationUncheckedLocked(inTransaction)) {
+                if (updateRotationUncheckedLocked(inTransaction, displayId)) {
                     // changed
                     return true;
                 }
@@ -2643,8 +2643,8 @@
     }
 
     @Override
-    public int[] setNewConfiguration(Configuration config) {
-        if (!checkCallingPermission(MANAGE_APP_TOKENS, "setNewConfiguration()")) {
+    public int[] setNewDisplayOverrideConfiguration(Configuration overrideConfig, int displayId) {
+        if (!checkCallingPermission(MANAGE_APP_TOKENS, "setNewDisplayOverrideConfiguration()")) {
             throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
         }
 
@@ -2653,7 +2653,7 @@
                 mWaitingForConfig = false;
                 mLastFinishedFreezeSource = "new-config";
             }
-            return mRoot.setGlobalConfigurationIfNeeded(config);
+            return mRoot.setDisplayOverrideConfigurationIfNeeded(overrideConfig, displayId);
         }
     }
 
@@ -4436,8 +4436,9 @@
         }
         try {
             Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "screenshotWallpaper");
-            return screenshotApplicationsInner(null, DEFAULT_DISPLAY, -1, -1, true, 1f,
-                    Bitmap.Config.ARGB_8888, true);
+            return screenshotApplicationsInner(null /* appToken */, DEFAULT_DISPLAY, -1 /* width */,
+                    -1 /* height */, true /* includeFullDisplay */, 1f /* frameScale */,
+                    Bitmap.Config.ARGB_8888, true /* wallpaperOnly */);
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
         }
@@ -4455,15 +4456,13 @@
             throw new SecurityException("Requires READ_FRAME_BUFFER permission");
         }
 
-        FgThread.getHandler().post(new Runnable() {
-            @Override
-            public void run() {
-                Bitmap bm = screenshotApplicationsInner(null, DEFAULT_DISPLAY, -1, -1,
-                        true, 1f, Bitmap.Config.ARGB_8888, false);
-                try {
-                    receiver.send(bm);
-                } catch (RemoteException e) {
-                }
+        FgThread.getHandler().post(() -> {
+            Bitmap bm = screenshotApplicationsInner(null /* appToken */, DEFAULT_DISPLAY,
+                    -1 /* width */, -1 /* height */, true /* includeFullDisplay */,
+                    1f /* frameScale */, Bitmap.Config.ARGB_8888, false /* wallpaperOnly */);
+            try {
+                receiver.send(bm);
+            } catch (RemoteException e) {
             }
         });
 
@@ -4864,16 +4863,17 @@
         if (mDeferredRotationPauseCount > 0) {
             mDeferredRotationPauseCount -= 1;
             if (mDeferredRotationPauseCount == 0) {
-                boolean changed = updateRotationUncheckedLocked(false);
+                // TODO(multi-display): Update rotation for different displays separately.
+                final int displayId = DEFAULT_DISPLAY;
+                final boolean changed = updateRotationUncheckedLocked(false, displayId);
                 if (changed) {
-                    mH.sendEmptyMessage(H.SEND_NEW_CONFIGURATION);
+                    mH.obtainMessage(H.SEND_NEW_CONFIGURATION, displayId).sendToTarget();
                 }
             }
         }
     }
 
-    private void updateRotationUnchecked(boolean alwaysSendConfiguration,
-            boolean forceRelayout) {
+    private void updateRotationUnchecked(boolean alwaysSendConfiguration, boolean forceRelayout) {
         if(DEBUG_ORIENTATION) Slog.v(TAG_WM, "updateRotationUnchecked:"
                 + " alwaysSendConfiguration=" + alwaysSendConfiguration
                 + " forceRelayout=" + forceRelayout);
@@ -4882,8 +4882,10 @@
 
         try {
             final boolean rotationChanged;
+            // TODO(multi-display): Update rotation for different displays separately.
+            int displayId = DEFAULT_DISPLAY;
             synchronized (mWindowMap) {
-                rotationChanged = updateRotationUncheckedLocked(false);
+                rotationChanged = updateRotationUncheckedLocked(false, displayId);
                 if (!rotationChanged || forceRelayout) {
                     getDefaultDisplayContentLocked().setLayoutNeeded();
                     mWindowPlacerLocked.performSurfacePlacement();
@@ -4891,22 +4893,20 @@
             }
 
             if (rotationChanged || alwaysSendConfiguration) {
-                sendNewConfiguration();
+                sendNewConfiguration(displayId);
             }
         } finally {
             Binder.restoreCallingIdentity(origId);
         }
     }
 
-
-    // TODO(multidisplay): Rotate any display?
     /**
-     * Updates the current rotation.
+     * Updates the current rotation of the specified display.
      *
-     * Returns true if the rotation has been changed.  In this case YOU
-     * MUST CALL sendNewConfiguration() TO UNFREEZE THE SCREEN.
+     * Returns true if the rotation has been changed.  In this case YOU MUST CALL
+     * {@link #sendNewConfiguration(int)} TO UNFREEZE THE SCREEN.
      */
-    boolean updateRotationUncheckedLocked(boolean inTransaction) {
+    boolean updateRotationUncheckedLocked(boolean inTransaction, int displayId) {
         if (mDeferredRotationPauseCount > 0) {
             // Rotation updates have been paused temporarily.  Defer the update until
             // updates have been resumed.
@@ -4915,7 +4915,7 @@
         }
 
         ScreenRotationAnimation screenRotationAnimation =
-                mAnimator.getScreenRotationAnimationLocked(DEFAULT_DISPLAY);
+                mAnimator.getScreenRotationAnimationLocked(displayId);
         if (screenRotationAnimation != null && screenRotationAnimation.isAnimating()) {
             // Rotation updates cannot be performed while the previous rotation change
             // animation is still in progress.  Skip this update.  We will try updating
@@ -4937,7 +4937,7 @@
             return false;
         }
 
-        final DisplayContent displayContent = getDefaultDisplayContentLocked();
+        final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
         final WindowList windows = displayContent.getWindowList();
 
         final int oldRotation = mRotation;
@@ -5017,7 +5017,7 @@
             startFreezingDisplayLocked(inTransaction, anim[0], anim[1]);
             // startFreezingDisplayLocked can reset the ScreenRotationAnimation.
             screenRotationAnimation =
-                mAnimator.getScreenRotationAnimationLocked(DEFAULT_DISPLAY);
+                mAnimator.getScreenRotationAnimationLocked(displayId);
         } else {
             // The screen rotation animation uses a screenshot to freeze the screen
             // while windows resize underneath.
@@ -5035,7 +5035,7 @@
         // the top of the method, the caller is obligated to call computeNewConfigurationLocked().
         // By updating the Display info here it will be available to
         // computeScreenConfigurationLocked later.
-        updateDisplayAndOrientationLocked(mRoot.getConfiguration().uiMode);
+        updateDisplayAndOrientationLocked(displayContent.getConfiguration().uiMode, displayId);
 
         final DisplayInfo displayInfo = displayContent.getDisplayInfo();
         if (!inTransaction) {
@@ -5568,13 +5568,14 @@
     }
 
     /**
-     * Instruct the Activity Manager to fetch new configurations, update global configuration
-     * and broadcast changes to config-changed listeners if appropriate.
+     * Instruct the Activity Manager to fetch and update the current display's configuration and
+     * broadcast them to config-changed listeners if appropriate.
      * NOTE: Can't be called with the window manager lock held since it call into activity manager.
      */
-    void sendNewConfiguration() {
+    void sendNewConfiguration(int displayId) {
         try {
-            final boolean configUpdated = mActivityManager.updateConfiguration(null);
+            final boolean configUpdated = mActivityManager.updateDisplayOverrideConfiguration(
+                    null /* values */, displayId);
             if (!configUpdated) {
                 // Something changed (E.g. device rotation), but no configuration update is needed.
                 // E.g. changing device rotation by 180 degrees. Go ahead and perform surface
@@ -5584,7 +5585,7 @@
                     if (mWaitingForConfig) {
                         mWaitingForConfig = false;
                         mLastFinishedFreezeSource = "config-unchanged";
-                        getDefaultDisplayContentLocked().setLayoutNeeded();
+                        mRoot.getDisplayContent(displayId).setLayoutNeeded();
                         mWindowPlacerLocked.performSurfacePlacement();
                     }
                 }
@@ -5593,18 +5594,18 @@
         }
     }
 
-    public Configuration computeNewConfiguration() {
+    public Configuration computeNewConfiguration(int displayId) {
         synchronized (mWindowMap) {
-            return computeNewConfigurationLocked();
+            return computeNewConfigurationLocked(displayId);
         }
     }
 
-    private Configuration computeNewConfigurationLocked() {
+    private Configuration computeNewConfigurationLocked(int displayId) {
         if (!mDisplayReady) {
             return null;
         }
-        Configuration config = new Configuration();
-        computeScreenConfigurationLocked(config);
+        final Configuration config = new Configuration();
+        computeScreenConfigurationLocked(config, displayId);
         return config;
     }
 
@@ -5713,9 +5714,8 @@
     }
 
     /** Do not call if mDisplayReady == false */
-    DisplayInfo updateDisplayAndOrientationLocked(int uiMode) {
-        // TODO(multidisplay): For now, apply Configuration to main screen only.
-        final DisplayContent displayContent = getDefaultDisplayContentLocked();
+    private DisplayInfo updateDisplayAndOrientationLocked(int uiMode, int displayId) {
+        final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
 
         // Use the effective "visual" dimensions based on current rotation
         final boolean rotated = (mRotation == Surface.ROTATION_90
@@ -5776,9 +5776,8 @@
     }
 
     /** Do not call if mDisplayReady == false */
-    void computeScreenConfigurationLocked(Configuration config) {
-        final DisplayInfo displayInfo = updateDisplayAndOrientationLocked(
-                config.uiMode);
+    private void computeScreenConfigurationLocked(Configuration config, int displayId) {
+        final DisplayInfo displayInfo = updateDisplayAndOrientationLocked(config.uiMode, displayId);
 
         final int dw = displayInfo.logicalWidth;
         final int dh = displayInfo.logicalHeight;
@@ -6417,11 +6416,9 @@
 
                     View view = null;
                     try {
-                        final Configuration overrideConfig =
-                                wtoken != null ? wtoken.getMergedOverrideConfiguration() : null;
                         view = mPolicy.addStartingWindow(wtoken.token, sd.pkg, sd.theme,
                             sd.compatInfo, sd.nonLocalizedLabel, sd.labelRes, sd.icon, sd.logo,
-                            sd.windowFlags, overrideConfig);
+                            sd.windowFlags, wtoken.getMergedOverrideConfiguration());
                     } catch (Exception e) {
                         Slog.w(TAG_WM, "Exception when adding starting window", e);
                     }
@@ -6668,8 +6665,9 @@
                 }
 
                 case SEND_NEW_CONFIGURATION: {
-                    removeMessages(SEND_NEW_CONFIGURATION);
-                    sendNewConfiguration();
+                    removeMessages(SEND_NEW_CONFIGURATION, msg.obj);
+                    final int displayId = (Integer) msg.obj;
+                    sendNewConfiguration(displayId);
                     break;
                 }
 
@@ -7317,16 +7315,19 @@
         configureDisplayPolicyLocked(displayContent);
         displayContent.setLayoutNeeded();
 
-        boolean configChanged = updateOrientationFromAppTokensLocked(false);
-        final Configuration globalConfig = mRoot.getConfiguration();
-        mTempConfiguration.setTo(globalConfig);
-        computeScreenConfigurationLocked(mTempConfiguration);
-        configChanged |= globalConfig.diff(mTempConfiguration) != 0;
+        final int displayId = displayContent.getDisplayId();
+        boolean configChanged = updateOrientationFromAppTokensLocked(false /* inTransaction */,
+                displayId);
+        final Configuration currentDisplayConfig = displayContent.getConfiguration();
+        mTempConfiguration.setTo(currentDisplayConfig);
+        computeScreenConfigurationLocked(mTempConfiguration, displayId);
+        configChanged |= currentDisplayConfig.diff(mTempConfiguration) != 0;
 
         if (configChanged) {
             mWaitingForConfig = true;
-            startFreezingDisplayLocked(false, 0, 0);
-            mH.sendEmptyMessage(H.SEND_NEW_CONFIGURATION);
+            startFreezingDisplayLocked(false /* inTransaction */, 0 /* exitAnim */,
+                    0 /* enterAnim */);
+            mH.obtainMessage(H.SEND_NEW_CONFIGURATION, displayId).sendToTarget();
         }
 
         mWindowPlacerLocked.performSurfacePlacement();
@@ -7744,7 +7745,7 @@
         // to avoid inconsistent states.  However, something interesting
         // could have actually changed during that time so re-evaluate it
         // now to catch that.
-        configChanged = updateOrientationFromAppTokensLocked(false);
+        configChanged = updateOrientationFromAppTokensLocked(false, displayId);
 
         // A little kludge: a lot could have happened while the
         // display was frozen, so now that we are coming back we
@@ -7758,11 +7759,11 @@
 
         if (updateRotation) {
             if (DEBUG_ORIENTATION) Slog.d(TAG_WM, "Performing post-rotate rotation");
-            configChanged |= updateRotationUncheckedLocked(false);
+            configChanged |= updateRotationUncheckedLocked(false, displayId);
         }
 
         if (configChanged) {
-            mH.sendEmptyMessage(H.SEND_NEW_CONFIGURATION);
+            mH.obtainMessage(H.SEND_NEW_CONFIGURATION, displayId).sendToTarget();
         }
     }
 
@@ -8887,8 +8888,9 @@
             if (DEBUG_ORIENTATION) {
                 Slog.i(TAG, "Performing post-rotate rotation after seamless rotation");
             }
-            if (updateRotationUncheckedLocked(false)) {
-                mH.sendEmptyMessage(H.SEND_NEW_CONFIGURATION);
+            final int displayId = w.getDisplayId();
+            if (updateRotationUncheckedLocked(false, displayId)) {
+                mH.obtainMessage(H.SEND_NEW_CONFIGURATION, displayId).sendToTarget();
             }
         }
     }
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index f80e085..a7b46111 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -38,7 +38,6 @@
 import android.util.DisplayMetrics;
 import android.util.Slog;
 import android.util.TimeUtils;
-import android.view.Display;
 import android.view.DisplayInfo;
 import android.view.Gravity;
 import android.view.IApplicationToken;
@@ -1670,7 +1669,7 @@
 
         //TODO (multidisplay): Accessibility supported only for the default display.
         if (mService.mAccessibilityController != null
-                && getDisplayContent().getDisplayId() == Display.DEFAULT_DISPLAY) {
+                && getDisplayContent().getDisplayId() == DEFAULT_DISPLAY) {
             mService.mAccessibilityController.onSomeWindowResizedOrMovedLocked();
         }
 
@@ -1831,6 +1830,8 @@
         // Visibility of the removed window. Will be used later to update orientation later on.
         boolean wasVisible = false;
 
+        final int displayId = getDisplayId();
+
         // First, see if we need to run an animation. If we do, we have to hold off on removing the
         // window until the animation is done. If the display is frozen, just remove immediately,
         // since the animation wouldn't be seen.
@@ -1891,8 +1892,7 @@
                     mAnimatingExit = true;
                 }
                 //TODO (multidisplay): Magnification is supported only for the default display.
-                if (mService.mAccessibilityController != null
-                        && getDisplayId() == Display.DEFAULT_DISPLAY) {
+                if (mService.mAccessibilityController != null && displayId == DEFAULT_DISPLAY) {
                     mService.mAccessibilityController.onWindowTransitionLocked(this, transit);
                 }
             }
@@ -1922,8 +1922,8 @@
         removeImmediately();
         // Removing a visible window will effect the computed orientation
         // So just update orientation if needed.
-        if (wasVisible && mService.updateOrientationFromAppTokensLocked(false)) {
-            mService.mH.sendEmptyMessage(SEND_NEW_CONFIGURATION);
+        if (wasVisible && mService.updateOrientationFromAppTokensLocked(false, displayId)) {
+            mService.mH.obtainMessage(SEND_NEW_CONFIGURATION, displayId).sendToTarget();
         }
         mService.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, true /*updateInputWindows*/);
         Binder.restoreCallingIdentity(origId);
@@ -3037,8 +3037,7 @@
             }
 
             //TODO (multidisplay): Accessibility supported only for the default display.
-            if (mService.mAccessibilityController != null
-                    && getDisplayId() == Display.DEFAULT_DISPLAY) {
+            if (mService.mAccessibilityController != null && getDisplayId() == DEFAULT_DISPLAY) {
                 mService.mAccessibilityController.onSomeWindowResizedOrMovedLocked();
             }
 
diff --git a/services/core/jni/Android.mk b/services/core/jni/Android.mk
index 121067a..2c46413 100644
--- a/services/core/jni/Android.mk
+++ b/services/core/jni/Android.mk
@@ -71,3 +71,5 @@
     android.hardware.vibrator@1.0 \
     android.hardware.light@2.0 \
     android.hardware.vr@1.0 \
+    android.hardware.audio.common@2.0 \
+    android.hardware.tv.input@1.0 \
diff --git a/services/core/jni/com_android_server_tv_TvInputHal.cpp b/services/core/jni/com_android_server_tv_TvInputHal.cpp
index e34a8e8..179fba0 100644
--- a/services/core/jni/com_android_server_tv_TvInputHal.cpp
+++ b/services/core/jni/com_android_server_tv_TvInputHal.cpp
@@ -24,6 +24,9 @@
 #include "JNIHelp.h"
 #include "jni.h"
 
+#include <android/hardware/tv/input/1.0/ITvInputCallback.h>
+#include <android/hardware/tv/input/1.0/ITvInput.h>
+#include <android/hardware/tv/input/1.0/types.h>
 #include <gui/Surface.h>
 #include <utils/Errors.h>
 #include <utils/KeyedVector.h>
@@ -32,6 +35,20 @@
 #include <utils/NativeHandle.h>
 #include <hardware/tv_input.h>
 
+using ::android::hardware::audio::common::V2_0::AudioDevice;
+using ::android::hardware::tv::input::V1_0::ITvInput;
+using ::android::hardware::tv::input::V1_0::ITvInputCallback;
+using ::android::hardware::tv::input::V1_0::Result;
+using ::android::hardware::tv::input::V1_0::TvInputDeviceInfo;
+using ::android::hardware::tv::input::V1_0::TvInputEvent;
+using ::android::hardware::tv::input::V1_0::TvInputEventType;
+using ::android::hardware::tv::input::V1_0::TvInputType;
+using ::android::hardware::tv::input::V1_0::TvStreamConfig;
+using ::android::hardware::Return;
+using ::android::hardware::Void;
+using ::android::hardware::hidl_vec;
+using ::android::hardware::hidl_string;
+
 namespace android {
 
 static struct {
@@ -239,9 +256,9 @@
 
     int addOrUpdateStream(int deviceId, int streamId, const sp<Surface>& surface);
     int removeStream(int deviceId, int streamId);
-    const tv_stream_config_t* getStreamConfigs(int deviceId, int* numConfigs);
+    const hidl_vec<TvStreamConfig> getStreamConfigs(int deviceId);
 
-    void onDeviceAvailable(const tv_input_device_info_t& info);
+    void onDeviceAvailable(const TvInputDeviceInfo& info);
     void onDeviceUnavailable(int deviceId);
     void onStreamConfigurationsChanged(int deviceId);
     void onCaptured(int deviceId, int streamId, uint32_t seq, bool succeeded);
@@ -263,73 +280,60 @@
 
     class NotifyHandler : public MessageHandler {
     public:
-        NotifyHandler(JTvInputHal* hal, const tv_input_event_t* event);
-        ~NotifyHandler();
+        NotifyHandler(JTvInputHal* hal, const TvInputEvent& event);
 
         virtual void handleMessage(const Message& message);
 
     private:
-        tv_input_event_t mEvent;
+        TvInputEvent mEvent;
         JTvInputHal* mHal;
     };
 
-    JTvInputHal(JNIEnv* env, jobject thiz, tv_input_device_t* dev, const sp<Looper>& looper);
+    class TvInputCallback : public ITvInputCallback {
+    public:
+        TvInputCallback(JTvInputHal* hal);
+        Return<void> notify(const TvInputEvent& event) override;
+    private:
+        JTvInputHal* mHal;
+    };
 
-    static void notify(
-            tv_input_device_t* dev, tv_input_event_t* event, void* data);
-
-    static void cloneTvInputEvent(
-            tv_input_event_t* dstEvent, const tv_input_event_t* srcEvent);
+    JTvInputHal(JNIEnv* env, jobject thiz, sp<ITvInput> tvInput, const sp<Looper>& looper);
 
     Mutex mLock;
     jweak mThiz;
-    tv_input_device_t* mDevice;
-    tv_input_callback_ops_t mCallback;
     sp<Looper> mLooper;
 
     KeyedVector<int, KeyedVector<int, Connection> > mConnections;
+
+    sp<ITvInput> mTvInput;
+    sp<ITvInputCallback> mTvInputCallback;
 };
 
-JTvInputHal::JTvInputHal(JNIEnv* env, jobject thiz, tv_input_device_t* device,
+JTvInputHal::JTvInputHal(JNIEnv* env, jobject thiz, sp<ITvInput> tvInput,
         const sp<Looper>& looper) {
     mThiz = env->NewWeakGlobalRef(thiz);
-    mDevice = device;
-    mCallback.notify = &JTvInputHal::notify;
+    mTvInput = tvInput;
     mLooper = looper;
-
-    mDevice->initialize(mDevice, &mCallback, this);
+    mTvInputCallback = new TvInputCallback(this);
+    mTvInput->setCallback(mTvInputCallback);
 }
 
 JTvInputHal::~JTvInputHal() {
-    mDevice->common.close((hw_device_t*)mDevice);
-
+    mTvInput->setCallback(nullptr);
     JNIEnv* env = AndroidRuntime::getJNIEnv();
     env->DeleteWeakGlobalRef(mThiz);
     mThiz = NULL;
 }
 
 JTvInputHal* JTvInputHal::createInstance(JNIEnv* env, jobject thiz, const sp<Looper>& looper) {
-    tv_input_module_t* module = NULL;
-    status_t err = hw_get_module(TV_INPUT_HARDWARE_MODULE_ID,
-            (hw_module_t const**)&module);
-    if (err) {
-        ALOGE("Couldn't load %s module (%s)",
-                TV_INPUT_HARDWARE_MODULE_ID, strerror(-err));
-        return 0;
+    // TODO(b/31632518)
+    sp<ITvInput> tvInput = ITvInput::getService("tv.input");
+    if (tvInput == nullptr) {
+        ALOGE("Couldn't get tv.input service.");
+        return nullptr;
     }
 
-    tv_input_device_t* device = NULL;
-    err = module->common.methods->open(
-            (hw_module_t*)module,
-            TV_INPUT_DEFAULT_DEVICE,
-            (hw_device_t**)&device);
-    if (err) {
-        ALOGE("Couldn't open %s device (%s)",
-                TV_INPUT_DEFAULT_DEVICE, strerror(-err));
-        return 0;
-    }
-
-    return new JTvInputHal(env, thiz, device, looper);
+    return new JTvInputHal(env, thiz, tvInput, looper);
 }
 
 int JTvInputHal::addOrUpdateStream(int deviceId, int streamId, const sp<Surface>& surface) {
@@ -353,16 +357,22 @@
     }
     if (connection.mSourceHandle == NULL && connection.mThread == NULL) {
         // Need to configure stream
-        int numConfigs = 0;
-        const tv_stream_config_t* configs = NULL;
-        if (mDevice->get_stream_configurations(
-                mDevice, deviceId, &numConfigs, &configs) != 0) {
-            ALOGE("Couldn't get stream configs");
+        Result result = Result::UNKNOWN;
+        hidl_vec<TvStreamConfig> list;
+        mTvInput->getStreamConfigurations(deviceId,
+                [&result, &list](Result res, hidl_vec<TvStreamConfig> configs) {
+                    result = res;
+                    if (res == Result::OK) {
+                        list = configs;
+                    }
+                });
+        if (result != Result::OK) {
+            ALOGE("Couldn't get stream configs for device id:%d result:%d", deviceId, result);
             return UNKNOWN_ERROR;
         }
         int configIndex = -1;
-        for (int i = 0; i < numConfigs; ++i) {
-            if (configs[i].stream_id == streamId) {
+        for (size_t i = 0; i < list.size(); ++i) {
+            if (list[i].streamId == streamId) {
                 configIndex = i;
                 break;
             }
@@ -371,34 +381,27 @@
             ALOGE("Cannot find a config with given stream ID: %d", streamId);
             return BAD_VALUE;
         }
-        connection.mStreamType = configs[configIndex].type;
+        connection.mStreamType = TV_STREAM_TYPE_INDEPENDENT_VIDEO_SOURCE;
 
-        tv_stream_t stream;
-        stream.stream_id = configs[configIndex].stream_id;
-        if (connection.mStreamType == TV_STREAM_TYPE_BUFFER_PRODUCER) {
-            stream.buffer_producer.width = configs[configIndex].max_video_width;
-            stream.buffer_producer.height = configs[configIndex].max_video_height;
-        }
-        if (mDevice->open_stream(mDevice, deviceId, &stream) != 0) {
-            ALOGE("Couldn't add stream");
+        result = Result::UNKNOWN;
+        const native_handle_t* sidebandStream;
+        mTvInput->openStream(deviceId, streamId,
+                [&result, &sidebandStream](Result res, const native_handle_t* handle) {
+                    result = res;
+                    if (res == Result::OK) {
+                        sidebandStream = handle;
+                    }
+                });
+        if (result != Result::OK) {
+            ALOGE("Couldn't open stream. device id:%d stream id:%d result:%d", deviceId, streamId,
+                    result);
             return UNKNOWN_ERROR;
         }
-        if (connection.mStreamType == TV_STREAM_TYPE_INDEPENDENT_VIDEO_SOURCE) {
-            connection.mSourceHandle = NativeHandle::create(
-                    stream.sideband_stream_source_handle, false);
-        } else if (connection.mStreamType == TV_STREAM_TYPE_BUFFER_PRODUCER) {
-            if (connection.mThread != NULL) {
-                connection.mThread->shutdown();
-            }
-            connection.mThread = new BufferProducerThread(mDevice, deviceId, &stream);
-            connection.mThread->run("BufferProducerThread");
-        }
+        connection.mSourceHandle = NativeHandle::create((native_handle_t*)sidebandStream, false);
     }
     connection.mSurface = surface;
-    if (connection.mStreamType == TV_STREAM_TYPE_INDEPENDENT_VIDEO_SOURCE) {
+    if (connection.mSurface != nullptr) {
         connection.mSurface->setSidebandStream(connection.mSourceHandle);
-    } else if (connection.mStreamType == TV_STREAM_TYPE_BUFFER_PRODUCER) {
-        connection.mThread->setSurface(surface);
     }
     return NO_ERROR;
 }
@@ -421,8 +424,8 @@
         connection.mThread->shutdown();
         connection.mThread.clear();
     }
-    if (mDevice->close_stream(mDevice, deviceId, streamId) != 0) {
-        ALOGE("Couldn't remove stream");
+    if (mTvInput->closeStream(deviceId, streamId) != Result::OK) {
+        ALOGE("Couldn't close stream. device id:%d stream id:%d", deviceId, streamId);
         return BAD_VALUE;
     }
     if (connection.mSourceHandle != NULL) {
@@ -431,41 +434,26 @@
     return NO_ERROR;
 }
 
-const tv_stream_config_t* JTvInputHal::getStreamConfigs(int deviceId, int* numConfigs) {
-    const tv_stream_config_t* configs = NULL;
-    if (mDevice->get_stream_configurations(
-            mDevice, deviceId, numConfigs, &configs) != 0) {
-        ALOGE("Couldn't get stream configs");
-        return NULL;
+const hidl_vec<TvStreamConfig> JTvInputHal::getStreamConfigs(int deviceId) {
+    Result result = Result::UNKNOWN;
+    hidl_vec<TvStreamConfig> list;
+    mTvInput->getStreamConfigurations(deviceId,
+            [&result, &list](Result res, hidl_vec<TvStreamConfig> configs) {
+                result = res;
+                if (res == Result::OK) {
+                    list = configs;
+                }
+            });
+    if (result != Result::OK) {
+        ALOGE("Couldn't get stream configs for device id:%d result:%d", deviceId, result);
     }
-    return configs;
+    return list;
 }
 
-// static
-void JTvInputHal::notify(
-        tv_input_device_t* dev, tv_input_event_t* event, void* data) {
-    JTvInputHal* thiz = (JTvInputHal*)data;
-    thiz->mLooper->sendMessage(new NotifyHandler(thiz, event), event->type);
-}
-
-// static
-void JTvInputHal::cloneTvInputEvent(
-        tv_input_event_t* dstEvent, const tv_input_event_t* srcEvent) {
-    memcpy(dstEvent, srcEvent, sizeof(tv_input_event_t));
-    if ((srcEvent->type == TV_INPUT_EVENT_DEVICE_AVAILABLE ||
-            srcEvent->type == TV_INPUT_EVENT_DEVICE_UNAVAILABLE ||
-            srcEvent->type == TV_INPUT_EVENT_STREAM_CONFIGURATIONS_CHANGED) &&
-            srcEvent->device_info.audio_address != NULL){
-        char* audio_address = new char[strlen(srcEvent->device_info.audio_address) + 1];
-        strcpy(audio_address, srcEvent->device_info.audio_address);
-        dstEvent->device_info.audio_address = audio_address;
-    }
-}
-
-void JTvInputHal::onDeviceAvailable(const tv_input_device_info_t& info) {
+void JTvInputHal::onDeviceAvailable(const TvInputDeviceInfo& info) {
     {
         Mutex::Autolock autoLock(&mLock);
-        mConnections.add(info.device_id, KeyedVector<int, Connection>());
+        mConnections.add(info.deviceId, KeyedVector<int, Connection>());
     }
     JNIEnv* env = AndroidRuntime::getJNIEnv();
 
@@ -473,17 +461,20 @@
             gTvInputHardwareInfoBuilderClassInfo.clazz,
             gTvInputHardwareInfoBuilderClassInfo.constructor);
     env->CallObjectMethod(
-            builder, gTvInputHardwareInfoBuilderClassInfo.deviceId, info.device_id);
+            builder, gTvInputHardwareInfoBuilderClassInfo.deviceId, info.deviceId);
     env->CallObjectMethod(
             builder, gTvInputHardwareInfoBuilderClassInfo.type, info.type);
-    if (info.type == TV_INPUT_TYPE_HDMI) {
+    if (info.type == TvInputType::HDMI) {
         env->CallObjectMethod(
-                builder, gTvInputHardwareInfoBuilderClassInfo.hdmiPortId, info.hdmi.port_id);
+                builder, gTvInputHardwareInfoBuilderClassInfo.hdmiPortId, info.portId);
     }
     env->CallObjectMethod(
-            builder, gTvInputHardwareInfoBuilderClassInfo.audioType, info.audio_type);
-    if (info.audio_type != AUDIO_DEVICE_NONE) {
-        jstring audioAddress = env->NewStringUTF(info.audio_address);
+            builder, gTvInputHardwareInfoBuilderClassInfo.audioType, info.audioType);
+    if (info.audioType != AudioDevice::NONE) {
+        uint8_t buffer[info.audioAddress.size() + 1];
+        memcpy(buffer, info.audioAddress.data(), info.audioAddress.size());
+        buffer[info.audioAddress.size()] = '\0';
+        jstring audioAddress = env->NewStringUTF(reinterpret_cast<const char *>(buffer));
         env->CallObjectMethod(
                 builder, gTvInputHardwareInfoBuilderClassInfo.audioAddress, audioAddress);
         env->DeleteLocalRef(audioAddress);
@@ -556,48 +547,37 @@
     }
 }
 
-JTvInputHal::NotifyHandler::NotifyHandler(JTvInputHal* hal, const tv_input_event_t* event) {
+JTvInputHal::NotifyHandler::NotifyHandler(JTvInputHal* hal, const TvInputEvent& event) {
     mHal = hal;
-    cloneTvInputEvent(&mEvent, event);
-}
-
-JTvInputHal::NotifyHandler::~NotifyHandler() {
-    if ((mEvent.type == TV_INPUT_EVENT_DEVICE_AVAILABLE ||
-            mEvent.type == TV_INPUT_EVENT_DEVICE_UNAVAILABLE ||
-            mEvent.type == TV_INPUT_EVENT_STREAM_CONFIGURATIONS_CHANGED) &&
-            mEvent.device_info.audio_address != NULL) {
-        delete mEvent.device_info.audio_address;
-    }
+    mEvent = event;
 }
 
 void JTvInputHal::NotifyHandler::handleMessage(const Message& message) {
     switch (mEvent.type) {
-        case TV_INPUT_EVENT_DEVICE_AVAILABLE: {
-            mHal->onDeviceAvailable(mEvent.device_info);
+        case TvInputEventType::DEVICE_AVAILABLE: {
+            mHal->onDeviceAvailable(mEvent.deviceInfo);
         } break;
-        case TV_INPUT_EVENT_DEVICE_UNAVAILABLE: {
-            mHal->onDeviceUnavailable(mEvent.device_info.device_id);
+        case TvInputEventType::DEVICE_UNAVAILABLE: {
+            mHal->onDeviceUnavailable(mEvent.deviceInfo.deviceId);
         } break;
-        case TV_INPUT_EVENT_STREAM_CONFIGURATIONS_CHANGED: {
-            mHal->onStreamConfigurationsChanged(mEvent.device_info.device_id);
-        } break;
-        case TV_INPUT_EVENT_CAPTURE_SUCCEEDED: {
-            mHal->onCaptured(mEvent.capture_result.device_id,
-                             mEvent.capture_result.stream_id,
-                             mEvent.capture_result.seq,
-                             true /* succeeded */);
-        } break;
-        case TV_INPUT_EVENT_CAPTURE_FAILED: {
-            mHal->onCaptured(mEvent.capture_result.device_id,
-                             mEvent.capture_result.stream_id,
-                             mEvent.capture_result.seq,
-                             false /* succeeded */);
+        case TvInputEventType::STREAM_CONFIGURATIONS_CHANGED: {
+            mHal->onStreamConfigurationsChanged(mEvent.deviceInfo.deviceId);
         } break;
         default:
             ALOGE("Unrecognizable event");
     }
 }
 
+JTvInputHal::TvInputCallback::TvInputCallback(JTvInputHal* hal) {
+    mHal = hal;
+}
+
+Return<void> JTvInputHal::TvInputCallback::notify(const TvInputEvent& event) {
+    // TODO(b/32200867): Ensure the event type values are in sync with the framework code.
+    mHal->mLooper->sendMessage(new NotifyHandler(mHal, event), static_cast<int>(event.type));
+    return Void();
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 
 static jlong nativeOpen(JNIEnv* env, jobject thiz, jobject messageQueueObj) {
@@ -628,22 +608,22 @@
 static jobjectArray nativeGetStreamConfigs(JNIEnv* env, jclass clazz,
         jlong ptr, jint deviceId, jint generation) {
     JTvInputHal* tvInputHal = (JTvInputHal*)ptr;
-    int numConfigs = 0;
-    const tv_stream_config_t* configs = tvInputHal->getStreamConfigs(deviceId, &numConfigs);
+    const hidl_vec<TvStreamConfig> configs = tvInputHal->getStreamConfigs(deviceId);
 
-    jobjectArray result = env->NewObjectArray(numConfigs, gTvStreamConfigClassInfo.clazz, NULL);
-    for (int i = 0; i < numConfigs; ++i) {
+    jobjectArray result = env->NewObjectArray(configs.size(), gTvStreamConfigClassInfo.clazz, NULL);
+    for (size_t i = 0; i < configs.size(); ++i) {
         jobject builder = env->NewObject(
                 gTvStreamConfigBuilderClassInfo.clazz,
                 gTvStreamConfigBuilderClassInfo.constructor);
         env->CallObjectMethod(
-                builder, gTvStreamConfigBuilderClassInfo.streamId, configs[i].stream_id);
+                builder, gTvStreamConfigBuilderClassInfo.streamId, configs[i].streamId);
         env->CallObjectMethod(
-                builder, gTvStreamConfigBuilderClassInfo.type, configs[i].type);
+                builder, gTvStreamConfigBuilderClassInfo.type,
+                        TV_STREAM_TYPE_INDEPENDENT_VIDEO_SOURCE);
         env->CallObjectMethod(
-                builder, gTvStreamConfigBuilderClassInfo.maxWidth, configs[i].max_video_width);
+                builder, gTvStreamConfigBuilderClassInfo.maxWidth, configs[i].maxVideoWidth);
         env->CallObjectMethod(
-                builder, gTvStreamConfigBuilderClassInfo.maxHeight, configs[i].max_video_height);
+                builder, gTvStreamConfigBuilderClassInfo.maxHeight, configs[i].maxVideoHeight);
         env->CallObjectMethod(
                 builder, gTvStreamConfigBuilderClassInfo.generation, generation);
 
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 85b0d96..62947eb 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -118,6 +118,8 @@
 import java.util.Timer;
 import java.util.TimerTask;
 
+import static android.view.Display.DEFAULT_DISPLAY;
+
 public final class SystemServer {
     private static final String TAG = "SystemServer";
 
@@ -1403,7 +1405,7 @@
         // Update the configuration for this context by hand, because we're going
         // to start using it before the config change done in wm.systemReady() will
         // propagate to it.
-        Configuration config = wm.computeNewConfiguration();
+        final Configuration config = wm.computeNewConfiguration(DEFAULT_DISPLAY);
         DisplayMetrics metrics = new DisplayMetrics();
         WindowManager w = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
         w.getDefaultDisplay().getMetrics(metrics);
diff --git a/services/tests/runtests.py b/services/tests/runtests.py
new file mode 100755
index 0000000..35fec90f
--- /dev/null
+++ b/services/tests/runtests.py
@@ -0,0 +1,78 @@
+#!/usr/bin/env python
+
+# Copyright (C) 2016 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.
+
+import os
+import subprocess
+import sys
+
+INSTRUMENTED_PACKAGE_RUNNER = ('com.android.frameworks.servicestests/'
+                               'android.support.test.runner.AndroidJUnitRunner')
+
+PACKAGE_WHITELIST = (
+    'android.net',
+    'com.android.server.connectivity',
+)
+
+COLOR_RED = '\033[0;31m'
+COLOR_NONE ='\033[0m'
+
+def run(shell_command, echo=True):
+    if echo:
+        print '%s + %s%s' % (
+                COLOR_RED,
+                echo if isinstance(echo, str) else shell_command,
+                COLOR_NONE)
+    return subprocess.check_call(shell_command, shell=True)
+
+
+def main():
+    build_top = os.environ.get('ANDROID_BUILD_TOP', None)
+    out_dir = os.environ.get('OUT', None)
+    if build_top is None or out_dir is None:
+        print 'You need to source and lunch before you can use this script'
+        return 1
+
+    print 'Building tests...'
+    run('make -j32 -C %s -f build/core/main.mk '
+        'MODULES-IN-frameworks-base-services-tests-servicestests' % build_top,
+        echo='mmma -j32 %s/frameworks/base/services/tests/servicestests' %
+             build_top)
+
+    print 'Installing tests...'
+    run('adb root')
+    run('adb wait-for-device')
+    apk_path = (
+            '%s/data/app/FrameworksServicesTests/FrameworksServicesTests.apk' %
+            out_dir)
+    run('adb install -r -g "%s"' % apk_path)
+
+    print 'Running tests...'
+    if len(sys.argv) != 1:
+        run('adb shell am instrument -w "%s" %s' %
+            (INSTRUMENTED_PACKAGE_RUNNER, ' '.join(sys.argv[1:])))
+        return 0
+
+    # It would be nice if the activity manager accepted a list of packages, but
+    # in lieu of that...
+    for package in PACKAGE_WHITELIST:
+        run('adb shell am instrument -w -e package %s %s' %
+            (package, INSTRUMENTED_PACKAGE_RUNNER))
+
+    return 0
+
+
+if __name__ == '__main__':
+    sys.exit(main())
diff --git a/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java b/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java
index 8424344..f737b24 100644
--- a/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java
+++ b/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java
@@ -25,6 +25,7 @@
 import junit.framework.TestCase;
 
 import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
+import static android.view.Display.DEFAULT_DISPLAY;
 
 /**
  * TODO: Remove this. This is only a placeholder, need to implement this.
@@ -113,7 +114,8 @@
         }
 
         try {
-            mWm.updateOrientationFromAppTokens(new Configuration(), null);
+            mWm.updateOrientationFromAppTokens(new Configuration(),
+                    null /* freezeThisOneIfNeeded */, DEFAULT_DISPLAY);
             fail("IWindowManager.updateOrientationFromAppTokens did not throw SecurityException as"
                     + " expected");
         } catch (SecurityException e) {
diff --git a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
index 09ab657..6b4db42 100644
--- a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
+++ b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
@@ -413,7 +413,8 @@
     }
 
     @Override
-    public int[] setNewConfiguration(Configuration arg0) throws RemoteException {
+    public int[] setNewDisplayOverrideConfiguration(Configuration arg0, int displayId)
+            throws RemoteException {
         // TODO Auto-generated method stub
         return null;
     }
@@ -487,7 +488,7 @@
     }
 
     @Override
-    public Configuration updateOrientationFromAppTokens(Configuration arg0, IBinder arg1)
+    public Configuration updateOrientationFromAppTokens(Configuration arg0, IBinder arg1, int arg2)
             throws RemoteException {
         // TODO Auto-generated method stub
         return null;