Merge "Fix wrong task bounds when docking from recents."
diff --git a/api/current.txt b/api/current.txt
index e912440..15cc347 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -4830,17 +4830,17 @@
     field public android.app.Notification.Action[] actions;
     field public android.media.AudioAttributes audioAttributes;
     field public deprecated int audioStreamType;
-    field public android.widget.RemoteViews bigContentView;
+    field public deprecated android.widget.RemoteViews bigContentView;
     field public java.lang.String category;
     field public int color;
     field public android.app.PendingIntent contentIntent;
-    field public android.widget.RemoteViews contentView;
+    field public deprecated android.widget.RemoteViews contentView;
     field public int defaults;
     field public android.app.PendingIntent deleteIntent;
     field public android.os.Bundle extras;
     field public int flags;
     field public android.app.PendingIntent fullScreenIntent;
-    field public android.widget.RemoteViews headsUpContentView;
+    field public deprecated android.widget.RemoteViews headsUpContentView;
     field public deprecated int icon;
     field public int iconLevel;
     field public deprecated android.graphics.Bitmap largeIcon;
@@ -4931,14 +4931,22 @@
     method public android.app.Notification.Builder extend(android.app.Notification.Extender);
     method public android.os.Bundle getExtras();
     method public deprecated android.app.Notification getNotification();
+    method public android.widget.RemoteViews makeBigContentView();
+    method public android.widget.RemoteViews makeContentView();
+    method public android.widget.RemoteViews makeHeadsUpContentView();
+    method public static android.app.Notification.Builder recoverBuilder(android.content.Context, android.app.Notification);
+    method public android.app.Notification.Builder setActions(android.app.Notification.Action...);
     method public android.app.Notification.Builder setAutoCancel(boolean);
     method public android.app.Notification.Builder setCategory(java.lang.String);
     method public android.app.Notification.Builder setColor(int);
-    method public android.app.Notification.Builder setContent(android.widget.RemoteViews);
+    method public deprecated android.app.Notification.Builder setContent(android.widget.RemoteViews);
     method public android.app.Notification.Builder setContentInfo(java.lang.CharSequence);
     method public android.app.Notification.Builder setContentIntent(android.app.PendingIntent);
     method public android.app.Notification.Builder setContentText(java.lang.CharSequence);
     method public android.app.Notification.Builder setContentTitle(java.lang.CharSequence);
+    method public android.app.Notification.Builder setCustomBigContentView(android.widget.RemoteViews);
+    method public android.app.Notification.Builder setCustomContentView(android.widget.RemoteViews);
+    method public android.app.Notification.Builder setCustomHeadsUpContentView(android.widget.RemoteViews);
     method public android.app.Notification.Builder setDefaults(int);
     method public android.app.Notification.Builder setDeleteIntent(android.app.PendingIntent);
     method public android.app.Notification.Builder setExtras(android.os.Bundle);
@@ -22153,6 +22161,7 @@
     field public static final int GL_ACTIVE_PROGRAM = 33369; // 0x8259
     field public static final int GL_ACTIVE_RESOURCES = 37621; // 0x92f5
     field public static final int GL_ACTIVE_VARIABLES = 37637; // 0x9305
+    field public static final int GL_ALL_BARRIER_BITS = -1; // 0xffffffff
     field public static final int GL_ALL_SHADER_BITS = -1; // 0xffffffff
     field public static final int GL_ARRAY_SIZE = 37627; // 0x92fb
     field public static final int GL_ARRAY_STRIDE = 37630; // 0x92fe
@@ -22176,6 +22185,7 @@
     field public static final int GL_DISPATCH_INDIRECT_BUFFER_BINDING = 37103; // 0x90ef
     field public static final int GL_DRAW_INDIRECT_BUFFER = 36671; // 0x8f3f
     field public static final int GL_DRAW_INDIRECT_BUFFER_BINDING = 36675; // 0x8f43
+    field public static final int GL_ELEMENT_ARRAY_BARRIER_BIT = 2; // 0x2
     field public static final int GL_FRAGMENT_SHADER_BIT = 2; // 0x2
     field public static final int GL_FRAMEBUFFER_BARRIER_BIT = 1024; // 0x400
     field public static final int GL_FRAMEBUFFER_DEFAULT_FIXED_SAMPLE_LOCATIONS = 37652; // 0x9314
@@ -22265,6 +22275,7 @@
     field public static final int GL_SAMPLE_MASK = 36433; // 0x8e51
     field public static final int GL_SAMPLE_MASK_VALUE = 36434; // 0x8e52
     field public static final int GL_SAMPLE_POSITION = 36432; // 0x8e50
+    field public static final int GL_SHADER_IMAGE_ACCESS_BARRIER_BIT = 32; // 0x20
     field public static final int GL_SHADER_STORAGE_BARRIER_BIT = 8192; // 0x2000
     field public static final int GL_SHADER_STORAGE_BLOCK = 37606; // 0x92e6
     field public static final int GL_SHADER_STORAGE_BUFFER = 37074; // 0x90d2
@@ -22310,6 +22321,7 @@
     field public static final int GL_UNSIGNED_INT_IMAGE_3D = 36964; // 0x9064
     field public static final int GL_UNSIGNED_INT_IMAGE_CUBE = 36966; // 0x9066
     field public static final int GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE = 37130; // 0x910a
+    field public static final int GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT = 1; // 0x1
     field public static final int GL_VERTEX_ATTRIB_BINDING = 33492; // 0x82d4
     field public static final int GL_VERTEX_ATTRIB_RELATIVE_OFFSET = 33493; // 0x82d5
     field public static final int GL_VERTEX_BINDING_BUFFER = 36687; // 0x8f4f
diff --git a/api/system-current.txt b/api/system-current.txt
index b996af3..a9a8753 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -4947,17 +4947,17 @@
     field public android.app.Notification.Action[] actions;
     field public android.media.AudioAttributes audioAttributes;
     field public deprecated int audioStreamType;
-    field public android.widget.RemoteViews bigContentView;
+    field public deprecated android.widget.RemoteViews bigContentView;
     field public java.lang.String category;
     field public int color;
     field public android.app.PendingIntent contentIntent;
-    field public android.widget.RemoteViews contentView;
+    field public deprecated android.widget.RemoteViews contentView;
     field public int defaults;
     field public android.app.PendingIntent deleteIntent;
     field public android.os.Bundle extras;
     field public int flags;
     field public android.app.PendingIntent fullScreenIntent;
-    field public android.widget.RemoteViews headsUpContentView;
+    field public deprecated android.widget.RemoteViews headsUpContentView;
     field public deprecated int icon;
     field public int iconLevel;
     field public deprecated android.graphics.Bitmap largeIcon;
@@ -5048,14 +5048,22 @@
     method public android.app.Notification.Builder extend(android.app.Notification.Extender);
     method public android.os.Bundle getExtras();
     method public deprecated android.app.Notification getNotification();
+    method public android.widget.RemoteViews makeBigContentView();
+    method public android.widget.RemoteViews makeContentView();
+    method public android.widget.RemoteViews makeHeadsUpContentView();
+    method public static android.app.Notification.Builder recoverBuilder(android.content.Context, android.app.Notification);
+    method public android.app.Notification.Builder setActions(android.app.Notification.Action...);
     method public android.app.Notification.Builder setAutoCancel(boolean);
     method public android.app.Notification.Builder setCategory(java.lang.String);
     method public android.app.Notification.Builder setColor(int);
-    method public android.app.Notification.Builder setContent(android.widget.RemoteViews);
+    method public deprecated android.app.Notification.Builder setContent(android.widget.RemoteViews);
     method public android.app.Notification.Builder setContentInfo(java.lang.CharSequence);
     method public android.app.Notification.Builder setContentIntent(android.app.PendingIntent);
     method public android.app.Notification.Builder setContentText(java.lang.CharSequence);
     method public android.app.Notification.Builder setContentTitle(java.lang.CharSequence);
+    method public android.app.Notification.Builder setCustomBigContentView(android.widget.RemoteViews);
+    method public android.app.Notification.Builder setCustomContentView(android.widget.RemoteViews);
+    method public android.app.Notification.Builder setCustomHeadsUpContentView(android.widget.RemoteViews);
     method public android.app.Notification.Builder setDefaults(int);
     method public android.app.Notification.Builder setDeleteIntent(android.app.PendingIntent);
     method public android.app.Notification.Builder setExtras(android.os.Bundle);
@@ -24098,6 +24106,7 @@
     field public static final int GL_ACTIVE_PROGRAM = 33369; // 0x8259
     field public static final int GL_ACTIVE_RESOURCES = 37621; // 0x92f5
     field public static final int GL_ACTIVE_VARIABLES = 37637; // 0x9305
+    field public static final int GL_ALL_BARRIER_BITS = -1; // 0xffffffff
     field public static final int GL_ALL_SHADER_BITS = -1; // 0xffffffff
     field public static final int GL_ARRAY_SIZE = 37627; // 0x92fb
     field public static final int GL_ARRAY_STRIDE = 37630; // 0x92fe
@@ -24121,6 +24130,7 @@
     field public static final int GL_DISPATCH_INDIRECT_BUFFER_BINDING = 37103; // 0x90ef
     field public static final int GL_DRAW_INDIRECT_BUFFER = 36671; // 0x8f3f
     field public static final int GL_DRAW_INDIRECT_BUFFER_BINDING = 36675; // 0x8f43
+    field public static final int GL_ELEMENT_ARRAY_BARRIER_BIT = 2; // 0x2
     field public static final int GL_FRAGMENT_SHADER_BIT = 2; // 0x2
     field public static final int GL_FRAMEBUFFER_BARRIER_BIT = 1024; // 0x400
     field public static final int GL_FRAMEBUFFER_DEFAULT_FIXED_SAMPLE_LOCATIONS = 37652; // 0x9314
@@ -24210,6 +24220,7 @@
     field public static final int GL_SAMPLE_MASK = 36433; // 0x8e51
     field public static final int GL_SAMPLE_MASK_VALUE = 36434; // 0x8e52
     field public static final int GL_SAMPLE_POSITION = 36432; // 0x8e50
+    field public static final int GL_SHADER_IMAGE_ACCESS_BARRIER_BIT = 32; // 0x20
     field public static final int GL_SHADER_STORAGE_BARRIER_BIT = 8192; // 0x2000
     field public static final int GL_SHADER_STORAGE_BLOCK = 37606; // 0x92e6
     field public static final int GL_SHADER_STORAGE_BUFFER = 37074; // 0x90d2
@@ -24255,6 +24266,7 @@
     field public static final int GL_UNSIGNED_INT_IMAGE_3D = 36964; // 0x9064
     field public static final int GL_UNSIGNED_INT_IMAGE_CUBE = 36966; // 0x9066
     field public static final int GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE = 37130; // 0x910a
+    field public static final int GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT = 1; // 0x1
     field public static final int GL_VERTEX_ATTRIB_BINDING = 33492; // 0x82d4
     field public static final int GL_VERTEX_ATTRIB_RELATIVE_OFFSET = 33493; // 0x82d5
     field public static final int GL_VERTEX_BINDING_BUFFER = 36687; // 0x8f4f
diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java
index 3f8e311..2960cdc 100644
--- a/cmds/pm/src/com/android/commands/pm/Pm.java
+++ b/cmds/pm/src/com/android/commands/pm/Pm.java
@@ -283,7 +283,7 @@
             } else if (args.length == 2) {
                 if (args[0].equalsIgnoreCase("-p")) {
                     validCommand = true;
-                    return displayPackageFilePath(args[1], UserHandle.USER_OWNER);
+                    return displayPackageFilePath(args[1], UserHandle.USER_SYSTEM);
                 }
             }
             return 1;
@@ -767,7 +767,7 @@
     }
 
     private int runPath() {
-        int userId = UserHandle.USER_OWNER;
+        int userId = UserHandle.USER_SYSTEM;
         String option = nextOption();
         if (option != null && option.equals("--user")) {
             String optionData = nextOptionData();
@@ -1650,7 +1650,7 @@
     }
 
     private int runClear() {
-        int userId = UserHandle.USER_OWNER;
+        int userId = UserHandle.USER_SYSTEM;
         String option = nextOption();
         if (option != null && option.equals("--user")) {
             String optionData = nextOptionData();
@@ -1722,7 +1722,7 @@
     }
 
     private int runSetEnabledSetting(int state) {
-        int userId = UserHandle.USER_OWNER;
+        int userId = UserHandle.USER_SYSTEM;
         String option = nextOption();
         if (option != null && option.equals("--user")) {
             String optionData = nextOptionData();
@@ -1771,7 +1771,7 @@
     }
 
     private int runSetHiddenSetting(boolean state) {
-        int userId = UserHandle.USER_OWNER;
+        int userId = UserHandle.USER_SYSTEM;
         String option = nextOption();
         if (option != null && option.equals("--user")) {
             String optionData = nextOptionData();
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 6ae32d0..fce1b2e 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -1200,6 +1200,12 @@
         public int id;
 
         /**
+         * The stack that currently contains this task.
+         * @hide
+         */
+        public int stackId;
+
+        /**
          * The component launched as the first activity in the task.  This can
          * be considered the "application" of this task.
          */
@@ -1248,6 +1254,7 @@
 
         public void writeToParcel(Parcel dest, int flags) {
             dest.writeInt(id);
+            dest.writeInt(stackId);
             ComponentName.writeToParcel(baseActivity, dest);
             ComponentName.writeToParcel(topActivity, dest);
             if (thumbnail != null) {
@@ -1264,6 +1271,7 @@
 
         public void readFromParcel(Parcel source) {
             id = source.readInt();
+            stackId = source.readInt();
             baseActivity = ComponentName.readFromParcel(source);
             topActivity = ComponentName.readFromParcel(source);
             if (source.readInt() != 0) {
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index db18722..49edff4 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -251,15 +251,26 @@
     public RemoteViews tickerView;
 
     /**
-     * The view that will represent this notification in the expanded status bar.
+     * The view that will represent this notification in the notification list (which is pulled
+     * down from the status bar).
+     *
+     * As of N, this field is not used. The notification view is determined by the inputs to
+     * {@link Notification.Builder}; a custom RemoteViews can optionally be
+     * supplied with {@link Notification.Builder#setCustomContentView(RemoteViews)}.
      */
+    @Deprecated
     public RemoteViews contentView;
 
     /**
      * A large-format version of {@link #contentView}, giving the Notification an
      * opportunity to show more detail. The system UI may choose to show this
      * instead of the normal content view at its discretion.
+     *
+     * As of N, this field is not used. The expanded notification view is determined by the
+     * inputs to {@link Notification.Builder}; a custom RemoteViews can optionally be
+     * supplied with {@link Notification.Builder#setCustomBigContentView(RemoteViews)}.
      */
+    @Deprecated
     public RemoteViews bigContentView;
 
 
@@ -268,7 +279,12 @@
      * opportunity to add action buttons to contentView. At its discretion, the system UI may
      * choose to show this as a heads-up notification, which will pop up so the user can see
      * it without leaving their current activity.
+     *
+     * As of N, this field is not used. The heads-up notification view is determined by the
+     * inputs to {@link Notification.Builder}; a custom RemoteViews can optionally be
+     * supplied with {@link Notification.Builder#setCustomHeadsUpContentView(RemoteViews)}.
      */
+    @Deprecated
     public RemoteViews headsUpContentView;
 
     /**
@@ -867,6 +883,11 @@
     public static final String EXTRA_ORIGINATING_USERID = "android.originatingUserId";
 
     /**
+     * @hide
+     */
+    public static final String EXTRA_BUILDER_APPLICATION_INFO = "android.appInfo";
+
+    /**
      * Value for {@link #EXTRA_AS_HEADS_UP} that indicates this notification should not be
      * displayed in the heads up space.
      *
@@ -1707,8 +1728,6 @@
             extras.remove(Notification.EXTRA_LARGE_ICON_BIG);
             extras.remove(Notification.EXTRA_PICTURE);
             extras.remove(Notification.EXTRA_BIG_TEXT);
-            // Prevent light notifications from being rebuilt.
-            extras.remove(Builder.EXTRA_NEEDS_REBUILD);
         }
     }
 
@@ -1901,21 +1920,13 @@
     @Deprecated
     public void setLatestEventInfo(Context context,
             CharSequence contentTitle, CharSequence contentText, PendingIntent contentIntent) {
-        Notification.Builder builder = new Notification.Builder(context);
+        if (context.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1){
+            Log.e(TAG, "setLatestEventInfo() is deprecated and you should feel deprecated.",
+                    new Throwable());
+        }
 
-        // First, ensure that key pieces of information that may have been set directly
-        // are preserved
-        builder.setWhen(this.when);
-        builder.setSmallIcon(this.icon);
-        builder.setPriority(this.priority);
-        builder.setTicker(this.tickerText);
-        builder.setNumber(this.number);
-        builder.setColor(this.color);
-        builder.mFlags = this.flags;
-        builder.setSound(this.sound, this.audioStreamType);
-        builder.setDefaults(this.defaults);
-        builder.setVibrate(this.vibrate);
-        builder.setDeleteIntent(this.deleteIntent);
+        // ensure that any information already set directly is preserved
+        final Notification.Builder builder = new Notification.Builder(context, this);
 
         // now apply the latestEventInfo fields
         if (contentTitle != null) {
@@ -1925,7 +1936,8 @@
             builder.setContentText(contentText);
         }
         builder.setContentIntent(contentIntent);
-        builder.buildInto(this);
+
+        builder.build(); // callers expect this notification to be ready to use
     }
 
     @Override
@@ -2080,15 +2092,6 @@
     /**
      * @hide
      */
-    public boolean isValid() {
-        // Would like to check for icon!=0 here, too, but NotificationManagerService accepts that
-        // for legacy reasons.
-        return contentView != null || extras.getBoolean(Builder.EXTRA_REBUILD_CONTENT_VIEW);
-    }
-
-    /**
-     * @hide
-     */
     public boolean isGroupSummary() {
         return mGroupKey != null && (flags & FLAG_GROUP_SUMMARY) != 0;
     }
@@ -2125,99 +2128,14 @@
         private static final int MAX_ACTION_BUTTONS = 3;
         private static final float LARGE_TEXT_SCALE = 1.3f;
 
-        /**
-         * @hide
-         */
-        public static final String EXTRA_NEEDS_REBUILD = "android.rebuild";
-
-        /**
-         * @hide
-         */
-        public static final String EXTRA_REBUILD_LARGE_ICON = "android.rebuild.largeIcon";
-        /**
-         * @hide
-         */
-        public static final String EXTRA_REBUILD_CONTENT_VIEW = "android.rebuild.contentView";
-        /**
-         * @hide
-         */
-        public static final String EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT =
-                "android.rebuild.contentViewActionCount";
-        /**
-         * @hide
-         */
-        public static final String EXTRA_REBUILD_BIG_CONTENT_VIEW
-                = "android.rebuild.bigView";
-        /**
-         * @hide
-         */
-        public static final String EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT
-                = "android.rebuild.bigViewActionCount";
-        /**
-         * @hide
-         */
-        public static final String EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW
-                = "android.rebuild.hudView";
-        /**
-         * @hide
-         */
-        public static final String EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT
-                = "android.rebuild.hudViewActionCount";
-
-        /**
-         * The ApplicationInfo of the package that created the notification, used to create
-         * a context to rebuild the notification via a Builder.
-         * @hide
-         */
-        private static final String EXTRA_REBUILD_CONTEXT_APPLICATION_INFO =
-                "android.rebuild.applicationInfo";
-
-        // Whether to enable stripping (at post time) & rebuilding (at listener receive time) of
-        // memory intensive resources.
-        private static final boolean STRIP_AND_REBUILD = true;
-
         private Context mContext;
-
-        private long mWhen;
-        private Icon mSmallIcon, mLargeIcon;
-        private int mSmallIconLevel;
-        private int mNumber;
-        private CharSequence mContentTitle;
-        private CharSequence mContentText;
-        private CharSequence mContentInfo;
-        private CharSequence mSubText;
-        private PendingIntent mContentIntent;
-        private RemoteViews mContentView;
-        private PendingIntent mDeleteIntent;
-        private PendingIntent mFullScreenIntent;
-        private CharSequence mTickerText;
-        private RemoteViews mTickerView;
-        private Uri mSound;
-        private int mAudioStreamType;
-        private AudioAttributes mAudioAttributes;
-        private long[] mVibrate;
-        private int mLedArgb;
-        private int mLedOnMs;
-        private int mLedOffMs;
-        private int mDefaults;
-        private int mFlags;
-        private int mProgressMax;
-        private int mProgress;
-        private boolean mProgressIndeterminate;
-        private String mCategory;
-        private String mGroupKey;
-        private String mSortKey;
-        private Bundle mExtras;
-        private int mPriority;
-        private ArrayList<Action> mActions = new ArrayList<Action>(MAX_ACTION_BUTTONS);
-        private boolean mUseChronometer;
+        private Notification mN;
+        private Bundle mUserExtras = new Bundle();
         private Style mStyle;
-        private boolean mShowWhen = true;
-        private int mVisibility = VISIBILITY_PRIVATE;
-        private Notification mPublicVersion = null;
-        private final NotificationColorUtil mColorUtil;
-        private ArrayList<String> mPeople;
-        private int mColor = COLOR_DEFAULT;
+        private ArrayList<Action> mActions = new ArrayList<Action>(MAX_ACTION_BUTTONS);
+        private ArrayList<String> mPersonList = new ArrayList<String>();
+        private NotificationColorUtil mColorUtil;
+        private boolean mColorUtilInited = false;
         private List<Topic> mTopics = new ArrayList<>();
 
         /**
@@ -2226,25 +2144,6 @@
         private int mOriginatingUserId;
 
         /**
-         * Contains extras related to rebuilding during the build phase.
-         */
-        private Bundle mRebuildBundle = new Bundle();
-        /**
-         * Contains the notification to rebuild when this Builder is in "rebuild" mode.
-         * Null otherwise.
-         */
-        private Notification mRebuildNotification = null;
-
-        /**
-         * Whether the build notification has three lines. This is used to make the top padding for
-         * both the contracted and expanded layout consistent.
-         *
-         * <p>
-         * This field is only valid during the build phase.
-         */
-        private boolean mHasThreeLines;
-
-        /**
          * Constructs a new Builder with the defaults:
          *
 
@@ -2264,61 +2163,67 @@
          *            object.
          */
         public Builder(Context context) {
-            /*
-             * Important compatibility note!
-             * Some apps out in the wild create a Notification.Builder in their Activity subclass
-             * constructor for later use. At this point Activities - themselves subclasses of
-             * ContextWrapper - do not have their inner Context populated yet. This means that
-             * any calls to Context methods from within this constructor can cause NPEs in existing
-             * apps. Any data populated from mContext should therefore be populated lazily to
-             * preserve compatibility.
-             */
-            mContext = context;
-
-            // Set defaults to match the defaults of a Notification
-            mWhen = System.currentTimeMillis();
-            mAudioStreamType = STREAM_DEFAULT;
-            mAudioAttributes = AUDIO_ATTRIBUTES_DEFAULT;
-            mPriority = PRIORITY_DEFAULT;
-            mPeople = new ArrayList<String>();
-
-            mColorUtil = context.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.LOLLIPOP ?
-                    NotificationColorUtil.getInstance(mContext) : null;
+            this(context, null);
         }
 
         /**
-         * Creates a Builder for rebuilding the given Notification.
-         * <p>
-         * Call {@link #rebuild()} to retrieve the rebuilt version of 'n'.
+         * @hide
          */
-        private Builder(Context context, Notification n) {
-            this(context);
-            mRebuildNotification = n;
-            restoreFromNotification(n);
+        public Builder(Context context, Notification toAdopt) {
+            mContext = context;
 
-            Style style = null;
-            Bundle extras = n.extras;
-            String templateClass = extras.getString(EXTRA_TEMPLATE);
-            if (!TextUtils.isEmpty(templateClass)) {
-                Class<? extends Style> styleClass = getNotificationStyleClass(templateClass);
-                if (styleClass == null) {
-                    Log.d(TAG, "Unknown style class: " + styleClass);
-                    return;
+            if (toAdopt == null) {
+                mN = new Notification();
+                mN.extras.putBoolean(EXTRA_SHOW_WHEN, true);
+                mN.priority = PRIORITY_DEFAULT;
+                mN.visibility = VISIBILITY_PRIVATE;
+            } else {
+                mN = toAdopt;
+                if (mN.actions != null) {
+                    Collections.addAll(mActions, mN.actions);
                 }
 
-                try {
-                    Constructor<? extends Style> constructor = styleClass.getConstructor();
-                    constructor.setAccessible(true);
-                    style = constructor.newInstance();
-                    style.restoreFromExtras(extras);
-                } catch (Throwable t) {
-                    Log.e(TAG, "Could not create Style", t);
-                    return;
+                if (mN.extras.containsKey(EXTRA_PEOPLE)) {
+                    Collections.addAll(mPersonList, mN.extras.getStringArray(EXTRA_PEOPLE));
+                }
+
+                if (mN.getTopics() != null) {
+                    Collections.addAll(mTopics, mN.getTopics());
+                }
+
+                String templateClass = mN.extras.getString(EXTRA_TEMPLATE);
+                if (!TextUtils.isEmpty(templateClass)) {
+                    final Class<? extends Style> styleClass
+                            = getNotificationStyleClass(templateClass);
+                    if (styleClass == null) {
+                        Log.d(TAG, "Unknown style class: " + templateClass);
+                    } else {
+                        try {
+                            final Constructor<? extends Style> ctor = styleClass.getConstructor();
+                            ctor.setAccessible(true);
+                            final Style style = ctor.newInstance();
+                            style.restoreFromExtras(mN.extras);
+
+                            if (style != null) {
+                                setStyle(style);
+                            }
+                        } catch (Throwable t) {
+                            Log.e(TAG, "Could not create Style", t);
+                        }
+                    }
+                }
+
+            }
+        }
+
+        private NotificationColorUtil getColorUtil() {
+            if (!mColorUtilInited) {
+                mColorUtilInited = true;
+                if (mContext.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.LOLLIPOP) {
+                    mColorUtil = NotificationColorUtil.getInstance(mContext);
                 }
             }
-            if (style != null) {
-                setStyle(style);
-            }
+            return mColorUtil;
         }
 
         /**
@@ -2329,7 +2234,7 @@
          * @see Notification#when
          */
         public Builder setWhen(long when) {
-            mWhen = when;
+            mN.when = when;
             return this;
         }
 
@@ -2338,7 +2243,7 @@
          * in the content view.
          */
         public Builder setShowWhen(boolean show) {
-            mShowWhen = show;
+            mN.extras.putBoolean(EXTRA_SHOW_WHEN, show);
             return this;
         }
 
@@ -2354,7 +2259,7 @@
          * @see Notification#when
          */
         public Builder setUsesChronometer(boolean b) {
-            mUseChronometer = b;
+            mN.extras.putBoolean(EXTRA_SHOW_CHRONOMETER, b);
             return this;
         }
 
@@ -2390,7 +2295,7 @@
          * @see Notification#iconLevel
          */
         public Builder setSmallIcon(@DrawableRes int icon, int level) {
-            mSmallIconLevel = level;
+            mN.iconLevel = level;
             return setSmallIcon(icon);
         }
 
@@ -2403,7 +2308,10 @@
          * @see Notification#icon
          */
         public Builder setSmallIcon(Icon icon) {
-            mSmallIcon = icon;
+            mN.setSmallIcon(icon);
+            if (icon != null && icon.getType() == Icon.TYPE_RESOURCE) {
+                mN.icon = icon.getResId();
+            }
             return this;
         }
 
@@ -2411,7 +2319,7 @@
          * Set the first line of text in the platform notification template.
          */
         public Builder setContentTitle(CharSequence title) {
-            mContentTitle = safeCharSequence(title);
+            mN.extras.putCharSequence(EXTRA_TITLE, safeCharSequence(title));
             return this;
         }
 
@@ -2419,7 +2327,7 @@
          * Set the second line of text in the platform notification template.
          */
         public Builder setContentText(CharSequence text) {
-            mContentText = safeCharSequence(text);
+            mN.extras.putCharSequence(EXTRA_TEXT, safeCharSequence(text));
             return this;
         }
 
@@ -2429,7 +2337,7 @@
          * same location in the standard template.
          */
         public Builder setSubText(CharSequence text) {
-            mSubText = safeCharSequence(text);
+            mN.extras.putCharSequence(EXTRA_SUB_TEXT, safeCharSequence(text));
             return this;
         }
 
@@ -2439,7 +2347,7 @@
          * font size for readability.
          */
         public Builder setNumber(int number) {
-            mNumber = number;
+            mN.number = number;
             return this;
         }
 
@@ -2450,7 +2358,7 @@
          * right (to the right of a smallIcon if it has been placed there).
          */
         public Builder setContentInfo(CharSequence info) {
-            mContentInfo = safeCharSequence(info);
+            mN.extras.putCharSequence(EXTRA_INFO_TEXT, safeCharSequence(info));
             return this;
         }
 
@@ -2460,19 +2368,52 @@
          * The platform template will represent this using a {@link ProgressBar}.
          */
         public Builder setProgress(int max, int progress, boolean indeterminate) {
-            mProgressMax = max;
-            mProgress = progress;
-            mProgressIndeterminate = indeterminate;
+            mN.extras.putInt(EXTRA_PROGRESS, progress);
+            mN.extras.putInt(EXTRA_PROGRESS_MAX, max);
+            mN.extras.putBoolean(EXTRA_PROGRESS_INDETERMINATE, indeterminate);
             return this;
         }
 
         /**
          * Supply a custom RemoteViews to use instead of the platform template.
          *
-         * @see Notification#contentView
+         * Use {@link #setCustomContentView(RemoteViews)} instead.
          */
+        @Deprecated
         public Builder setContent(RemoteViews views) {
-            mContentView = views;
+            return setCustomContentView(views);
+        }
+
+        /**
+         * Supply custom RemoteViews to use instead of the platform template.
+         *
+         * This will override the layout that would otherwise be constructed by this Builder
+         * object.
+         */
+        public Builder setCustomContentView(RemoteViews contentView) {
+            mN.contentView = contentView;
+            return this;
+        }
+
+        /**
+         * Supply custom RemoteViews to use instead of the platform template in the expanded form.
+         *
+         * This will override the expanded layout that would otherwise be constructed by this
+         * Builder object.
+         */
+        public Builder setCustomBigContentView(RemoteViews contentView) {
+            mN.bigContentView = contentView;
+            return this;
+        }
+
+        /**
+         * Supply custom RemoteViews to use instead of the platform template in the heads up dialog.
+         *
+         * This will override the heads-up layout that would otherwise be constructed by this
+         * Builder object.
+         */
+        public Builder setCustomHeadsUpContentView(RemoteViews contentView) {
+            mN.headsUpContentView = contentView;
             return this;
         }
 
@@ -2488,7 +2429,7 @@
          * @see Notification#contentIntent Notification.contentIntent
          */
         public Builder setContentIntent(PendingIntent intent) {
-            mContentIntent = intent;
+            mN.contentIntent = intent;
             return this;
         }
 
@@ -2498,7 +2439,7 @@
          * @see Notification#deleteIntent
          */
         public Builder setDeleteIntent(PendingIntent intent) {
-            mDeleteIntent = intent;
+            mN.deleteIntent = intent;
             return this;
         }
 
@@ -2523,7 +2464,7 @@
          * @see Notification#fullScreenIntent
          */
         public Builder setFullScreenIntent(PendingIntent intent, boolean highPriority) {
-            mFullScreenIntent = intent;
+            mN.fullScreenIntent = intent;
             setFlag(FLAG_HIGH_PRIORITY, highPriority);
             return this;
         }
@@ -2534,7 +2475,7 @@
          * @see Notification#tickerText
          */
         public Builder setTicker(CharSequence tickerText) {
-            mTickerText = safeCharSequence(tickerText);
+            mN.tickerText = safeCharSequence(tickerText);
             return this;
         }
 
@@ -2544,8 +2485,8 @@
          */
         @Deprecated
         public Builder setTicker(CharSequence tickerText, RemoteViews views) {
-            mTickerText = safeCharSequence(tickerText);
-            mTickerView = views; // we'll save it for you anyway
+            setTicker(tickerText);
+            // views is ignored
             return this;
         }
 
@@ -2568,7 +2509,8 @@
          * badge atop the large icon).
          */
         public Builder setLargeIcon(Icon icon) {
-            mLargeIcon = icon;
+            mN.mLargeIcon = icon;
+            mN.extras.putParcelable(EXTRA_LARGE_ICON, icon);
             return this;
         }
 
@@ -2585,8 +2527,8 @@
          * @see Notification#sound
          */
         public Builder setSound(Uri sound) {
-            mSound = sound;
-            mAudioAttributes = AUDIO_ATTRIBUTES_DEFAULT;
+            mN.sound = sound;
+            mN.audioAttributes = AUDIO_ATTRIBUTES_DEFAULT;
             return this;
         }
 
@@ -2603,8 +2545,8 @@
          */
         @Deprecated
         public Builder setSound(Uri sound, int streamType) {
-            mSound = sound;
-            mAudioStreamType = streamType;
+            mN.sound = sound;
+            mN.audioStreamType = streamType;
             return this;
         }
 
@@ -2619,8 +2561,8 @@
          * @see Notification#sound
          */
         public Builder setSound(Uri sound, AudioAttributes audioAttributes) {
-            mSound = sound;
-            mAudioAttributes = audioAttributes;
+            mN.sound = sound;
+            mN.audioAttributes = audioAttributes;
             return this;
         }
 
@@ -2637,7 +2579,7 @@
          * @see Notification#vibrate
          */
         public Builder setVibrate(long[] pattern) {
-            mVibrate = pattern;
+            mN.vibrate = pattern;
             return this;
         }
 
@@ -2654,9 +2596,9 @@
          * @see Notification#ledOffMS
          */
         public Builder setLights(@ColorInt int argb, int onMs, int offMs) {
-            mLedArgb = argb;
-            mLedOnMs = onMs;
-            mLedOffMs = offMs;
+            mN.ledARGB = argb;
+            mN.ledOnMS = onMs;
+            mN.ledOffMS = offMs;
             return this;
         }
 
@@ -2724,7 +2666,7 @@
          * For all default values, use {@link #DEFAULT_ALL}.
          */
         public Builder setDefaults(int defaults) {
-            mDefaults = defaults;
+            mN.defaults = defaults;
             return this;
         }
 
@@ -2734,7 +2676,7 @@
          * @see Notification#priority
          */
         public Builder setPriority(@Priority int pri) {
-            mPriority = pri;
+            mN.priority = pri;
             return this;
         }
 
@@ -2744,7 +2686,7 @@
          * @see Notification#category
          */
         public Builder setCategory(String category) {
-            mCategory = category;
+            mN.category = category;
             return this;
         }
 
@@ -2771,7 +2713,7 @@
          * @see Notification#EXTRA_PEOPLE
          */
         public Builder addPerson(String uri) {
-            mPeople.add(uri);
+            mPersonList.add(uri);
             return this;
         }
 
@@ -2787,7 +2729,7 @@
          * @return this object for method chaining
          */
         public Builder setGroup(String groupKey) {
-            mGroupKey = groupKey;
+            mN.mGroupKey = groupKey;
             return this;
         }
 
@@ -2816,7 +2758,7 @@
          * @see String#compareTo(String)
          */
         public Builder setSortKey(String sortKey) {
-            mSortKey = sortKey;
+            mN.mSortKey = sortKey;
             return this;
         }
 
@@ -2829,11 +2771,7 @@
          */
         public Builder addExtras(Bundle extras) {
             if (extras != null) {
-                if (mExtras == null) {
-                    mExtras = new Bundle(extras);
-                } else {
-                    mExtras.putAll(extras);
-                }
+                mUserExtras.putAll(extras);
             }
             return this;
         }
@@ -2851,7 +2789,9 @@
          * @see Notification#extras
          */
         public Builder setExtras(Bundle extras) {
-            mExtras = extras;
+            if (extras != null) {
+                mUserExtras = extras;
+            }
             return this;
         }
 
@@ -2866,10 +2806,13 @@
          * @see Notification#extras
          */
         public Bundle getExtras() {
-            if (mExtras == null) {
-                mExtras = new Bundle();
-            }
-            return mExtras;
+            return mUserExtras;
+        }
+
+        private Bundle getAllExtras() {
+            final Bundle saveExtras = (Bundle) mUserExtras.clone();
+            saveExtras.putAll(mN.extras);
+            return saveExtras;
         }
 
         /**
@@ -2918,6 +2861,21 @@
         }
 
         /**
+         * Alter the complete list of actions attached to this notification.
+         * @see #addAction(Action).
+         *
+         * @param actions
+         * @return
+         */
+        public Builder setActions(Action... actions) {
+            mActions.clear();
+            for (int i = 0; i < actions.length; i++) {
+                mActions.add(actions[i]);
+            }
+            return this;
+        }
+
+        /**
          * Add a rich notification style to be applied at build time.
          *
          * @param style Object responsible for modifying the notification style.
@@ -2927,6 +2885,9 @@
                 mStyle = style;
                 if (mStyle != null) {
                     mStyle.setBuilder(this);
+                    mN.extras.putString(EXTRA_TEMPLATE, style.getClass().getName());
+                }  else {
+                    mN.extras.remove(EXTRA_TEMPLATE);
                 }
             }
             return this;
@@ -2941,7 +2902,7 @@
          * @return The same Builder.
          */
         public Builder setVisibility(int visibility) {
-            mVisibility = visibility;
+            mN.visibility = visibility;
             return this;
         }
 
@@ -2952,7 +2913,12 @@
          * @return The same Builder.
          */
         public Builder setPublicVersion(Notification n) {
-            mPublicVersion = n;
+            if (n != null) {
+                mN.publicVersion = new Notification();
+                n.cloneInto(mN.publicVersion, /*heavy=*/ true);
+            } else {
+                mN.publicVersion = null;
+            }
             return this;
         }
 
@@ -2970,9 +2936,9 @@
          */
         public void setFlag(int mask, boolean value) {
             if (value) {
-                mFlags |= mask;
+                mN.flags |= mask;
             } else {
-                mFlags &= ~mask;
+                mN.flags &= ~mask;
             }
         }
 
@@ -2984,7 +2950,7 @@
          * @return The same Builder.
          */
         public Builder setColor(@ColorInt int argb) {
-            mColor = argb;
+            mN.color = argb;
             return this;
         }
 
@@ -3075,7 +3041,6 @@
             contentView.setViewVisibility(R.id.overflow_divider, View.GONE);
             contentView.setViewVisibility(R.id.progress, View.GONE);
             contentView.setViewVisibility(R.id.chronometer, View.GONE);
-            contentView.setViewVisibility(R.id.time, View.GONE);
         }
 
         private RemoteViews applyStandardTemplate(int resId) {
@@ -3093,39 +3058,43 @@
             boolean showLine3 = false;
             boolean showLine2 = false;
             boolean contentTextInLine2 = false;
+            final Bundle ex = mN.extras;
 
-            if (mLargeIcon != null) {
-                contentView.setImageViewIcon(R.id.icon, mLargeIcon);
-                processLargeLegacyIcon(mLargeIcon, contentView);
-                contentView.setImageViewIcon(R.id.right_icon, mSmallIcon);
+            if (mN.mLargeIcon != null) {
+                contentView.setImageViewIcon(R.id.icon, mN.mLargeIcon);
+                processLargeLegacyIcon(mN.mLargeIcon, contentView);
+                contentView.setImageViewIcon(R.id.right_icon, mN.mSmallIcon);
                 contentView.setViewVisibility(R.id.right_icon, View.VISIBLE);
-                processSmallRightIcon(mSmallIcon, contentView);
+                processSmallRightIcon(mN.mSmallIcon, contentView);
             } else { // small icon at left
-                contentView.setImageViewIcon(R.id.icon, mSmallIcon);
+                contentView.setImageViewIcon(R.id.icon, mN.mSmallIcon);
                 contentView.setViewVisibility(R.id.icon, View.VISIBLE);
-                processSmallIconAsLarge(mSmallIcon, contentView);
+                processSmallIconAsLarge(mN.mSmallIcon, contentView);
             }
-            if (mContentTitle != null) {
-                contentView.setTextViewText(R.id.title, processLegacyText(mContentTitle));
+            if (ex.getCharSequence(EXTRA_TITLE) != null) {
+                contentView.setTextViewText(R.id.title,
+                        processLegacyText(ex.getCharSequence(EXTRA_TITLE)));
             }
-            if (mContentText != null) {
-                contentView.setTextViewText(R.id.text, processLegacyText(mContentText));
+            if (ex.getCharSequence(EXTRA_TEXT) != null) {
+                contentView.setTextViewText(R.id.text,
+                        processLegacyText(ex.getCharSequence(EXTRA_TEXT)));
                 showLine3 = true;
             }
-            if (mContentInfo != null) {
-                contentView.setTextViewText(R.id.info, processLegacyText(mContentInfo));
+            if (ex.getCharSequence(EXTRA_INFO_TEXT) != null) {
+                contentView.setTextViewText(R.id.info,
+                        processLegacyText(ex.getCharSequence(EXTRA_INFO_TEXT)));
                 contentView.setViewVisibility(R.id.info, View.VISIBLE);
                 showLine3 = true;
-            } else if (mNumber > 0) {
+            } else if (mN.number > 0) {
                 final int tooBig = mContext.getResources().getInteger(
                         R.integer.status_bar_notification_info_maxnum);
-                if (mNumber > tooBig) {
+                if (mN.number > tooBig) {
                     contentView.setTextViewText(R.id.info, processLegacyText(
                             mContext.getResources().getString(
                                     R.string.status_bar_notification_info_overflow)));
                 } else {
                     NumberFormat f = NumberFormat.getIntegerInstance();
-                    contentView.setTextViewText(R.id.info, processLegacyText(f.format(mNumber)));
+                    contentView.setTextViewText(R.id.info, processLegacyText(f.format(mN.number)));
                 }
                 contentView.setViewVisibility(R.id.info, View.VISIBLE);
                 showLine3 = true;
@@ -3134,10 +3103,12 @@
             }
 
             // Need to show three lines?
-            if (mSubText != null) {
-                contentView.setTextViewText(R.id.text, processLegacyText(mSubText));
-                if (mContentText != null) {
-                    contentView.setTextViewText(R.id.text2, processLegacyText(mContentText));
+            if (ex.getCharSequence(EXTRA_SUB_TEXT) != null) {
+                contentView.setTextViewText(R.id.text,
+                        processLegacyText(ex.getCharSequence(EXTRA_SUB_TEXT)));
+                if (ex.getCharSequence(EXTRA_TEXT) != null) {
+                    contentView.setTextViewText(R.id.text2,
+                            processLegacyText(ex.getCharSequence(EXTRA_TEXT)));
                     contentView.setViewVisibility(R.id.text2, View.VISIBLE);
                     showLine2 = true;
                     contentTextInLine2 = true;
@@ -3146,15 +3117,18 @@
                 }
             } else {
                 contentView.setViewVisibility(R.id.text2, View.GONE);
-                if (hasProgress && (mProgressMax != 0 || mProgressIndeterminate)) {
+                final int max = ex.getInt(EXTRA_PROGRESS_MAX, 0);
+                final int progress = ex.getInt(EXTRA_PROGRESS, 0);
+                final boolean ind = ex.getBoolean(EXTRA_PROGRESS_INDETERMINATE);
+                if (hasProgress && (max != 0 || ind)) {
                     contentView.setViewVisibility(R.id.progress, View.VISIBLE);
                     contentView.setProgressBar(
-                            R.id.progress, mProgressMax, mProgress, mProgressIndeterminate);
+                            R.id.progress, max, progress, ind);
                     contentView.setProgressBackgroundTintList(
                             R.id.progress, ColorStateList.valueOf(mContext.getColor(
                                     R.color.notification_progress_background_color)));
-                    if (mColor != COLOR_DEFAULT) {
-                        ColorStateList colorStateList = ColorStateList.valueOf(mColor);
+                    if (mN.color != COLOR_DEFAULT) {
+                        ColorStateList colorStateList = ColorStateList.valueOf(mN.color);
                         contentView.setProgressTintList(R.id.progress, colorStateList);
                         contentView.setProgressIndeterminateTintList(R.id.progress, colorStateList);
                     }
@@ -3170,20 +3144,21 @@
             }
 
             if (showsTimeOrChronometer()) {
-                if (mUseChronometer) {
+                if (ex.getBoolean(EXTRA_SHOW_CHRONOMETER)) {
                     contentView.setViewVisibility(R.id.chronometer, View.VISIBLE);
                     contentView.setLong(R.id.chronometer, "setBase",
-                            mWhen + (SystemClock.elapsedRealtime() - System.currentTimeMillis()));
+                            mN.when + (SystemClock.elapsedRealtime() - System.currentTimeMillis()));
                     contentView.setBoolean(R.id.chronometer, "setStarted", true);
                 } else {
                     contentView.setViewVisibility(R.id.time, View.VISIBLE);
-                    contentView.setLong(R.id.time, "setTime", mWhen);
+                    contentView.setLong(R.id.time, "setTime", mN.when);
                 }
             }
 
             // Adjust padding depending on line count and font size.
-            contentView.setViewPadding(R.id.line1, 0, calculateTopPadding(mContext,
-                    mHasThreeLines, mContext.getResources().getConfiguration().fontScale),
+            contentView.setViewPadding(R.id.line1, 0,
+                    calculateTopPadding(mContext, hasThreeLines(),
+                            mContext.getResources().getConfiguration().fontScale),
                     0, 0);
 
             // We want to add badge to first line of text.
@@ -3196,7 +3171,8 @@
 
             // Note getStandardView may hide line 3 again.
             contentView.setViewVisibility(R.id.line3, showLine3 ? View.VISIBLE : View.GONE);
-            contentView.setViewVisibility(R.id.overflow_divider, showLine3 ? View.VISIBLE : View.GONE);
+            contentView.setViewVisibility(R.id.overflow_divider,
+                    showLine3 ? View.VISIBLE : View.GONE);
             return contentView;
         }
 
@@ -3205,7 +3181,7 @@
          *         otherwise
          */
         private boolean showsTimeOrChronometer() {
-            return mWhen != 0 && mShowWhen;
+            return mN.when != 0 && mN.extras.getBoolean(EXTRA_SHOW_WHEN);
         }
 
         /**
@@ -3216,15 +3192,19 @@
          *         is going to have one or two lines
          */
         private boolean hasThreeLines() {
-            boolean contentTextInLine2 = mSubText != null && mContentText != null;
+            final CharSequence subText = mN.extras.getCharSequence(EXTRA_SUB_TEXT);
+            final CharSequence text = mN.extras.getCharSequence(EXTRA_TEXT);
+            boolean contentTextInLine2 = subText != null && text != null;
             boolean hasProgress = mStyle == null || mStyle.hasProgress();
             // If we have content text in line 2, badge goes into line 2, or line 3 otherwise
             boolean badgeInLine3 = getProfileBadgeDrawable() != null && !contentTextInLine2;
-            boolean hasLine3 = mContentText != null || mContentInfo != null || mNumber > 0
-                    || badgeInLine3;
-            boolean hasLine2 = (mSubText != null && mContentText != null) ||
-                    (hasProgress && mSubText == null
-                            && (mProgressMax != 0 || mProgressIndeterminate));
+            boolean hasLine3 = text != null || mN.extras.getCharSequence(EXTRA_INFO_TEXT) != null
+                    || mN.number > 0 || badgeInLine3;
+            final Bundle ex = mN.extras;
+            final int max = ex.getInt(EXTRA_PROGRESS_MAX, 0);
+            final boolean ind = ex.getBoolean(EXTRA_PROGRESS_INDETERMINATE);
+            boolean hasLine2 = (subText != null && text != null) ||
+                    (hasProgress && subText == null && (max != 0 || ind));
             return hasLine2 && hasLine3;
         }
 
@@ -3271,29 +3251,48 @@
             return big;
         }
 
-        private RemoteViews makeContentView() {
-            if (mContentView != null) {
-                return mContentView;
-            } else {
-                return applyStandardTemplate(getBaseLayoutResource());
+        /**
+         * Construct a RemoteViews for the final 1U notification layout. In order:
+         *   1. Custom contentView from the caller
+         *   2. Style's proposed content view
+         *   3. Standard template view
+         */
+        public RemoteViews makeContentView() {
+            if (mN.contentView != null) {
+                return mN.contentView;
+            } else if (mStyle != null) {
+                final RemoteViews styleView = mStyle.makeContentView();
+                if (styleView != null) {
+                    return styleView;
+                }
             }
+            return applyStandardTemplate(getBaseLayoutResource());
         }
 
-        private RemoteViews makeTickerView() {
-            if (mTickerView != null) {
-                return mTickerView;
-            }
-            return null; // tickers are not created by default anymore
-        }
-
-        private RemoteViews makeBigContentView() {
-            if (mActions.size() == 0) return null;
+        /**
+         * Construct a RemoteViews for the final big notification layout.
+         */
+        public RemoteViews makeBigContentView() {
+            if (mStyle != null) {
+                final RemoteViews styleView = mStyle.makeBigContentView();
+                if (styleView != null) {
+                    return styleView;
+                }
+            } else if (mActions.size() == 0) return null;
 
             return applyStandardTemplateWithActions(getBigBaseLayoutResource());
         }
 
-        private RemoteViews makeHeadsUpContentView() {
-            if (mActions.size() == 0) return null;
+        /**
+         * Construct a RemoteViews for the final heads-up notification layout.
+         */
+        public RemoteViews makeHeadsUpContentView() {
+            if (mStyle != null) {
+                final RemoteViews styleView = mStyle.makeHeadsUpContentView();
+                if (styleView != null) {
+                    return styleView;
+                }
+            } else if (mActions.size() == 0) return null;
 
             return applyStandardTemplateWithActions(getBigBaseLayoutResource());
         }
@@ -3320,11 +3319,11 @@
          *         doesn't create material notifications by itself) app.
          */
         private boolean isLegacy() {
-            return mColorUtil != null;
+            return getColorUtil() != null;
         }
 
         private void processLegacyAction(Action action, RemoteViews button) {
-            if (!isLegacy() || mColorUtil.isGrayscaleIcon(mContext, action.getIcon())) {
+            if (!isLegacy() || getColorUtil().isGrayscaleIcon(mContext, action.getIcon())) {
                 button.setTextViewCompoundDrawablesRelativeColorFilter(R.id.action0, 0,
                         mContext.getColor(R.color.notification_action_color_filter),
                         PorterDuff.Mode.MULTIPLY);
@@ -3333,7 +3332,7 @@
 
         private CharSequence processLegacyText(CharSequence charSequence) {
             if (isLegacy()) {
-                return mColorUtil.invertCharSequenceColors(charSequence);
+                return getColorUtil().invertCharSequenceColors(charSequence);
             } else {
                 return charSequence;
             }
@@ -3349,7 +3348,7 @@
                         PorterDuff.Mode.SRC_ATOP, -1);
                 applyLargeIconBackground(contentView);
             } else {
-                if (mColorUtil.isGrayscaleIcon(mContext, largeIcon)) {
+                if (getColorUtil().isGrayscaleIcon(mContext, largeIcon)) {
                     applyLargeIconBackground(contentView);
                 }
             }
@@ -3362,7 +3361,7 @@
         // TODO: also check bounds, transparency, that sort of thing.
         private void processLargeLegacyIcon(Icon largeIcon, RemoteViews contentView) {
             if (largeIcon != null && isLegacy()
-                    && mColorUtil.isGrayscaleIcon(mContext, largeIcon)) {
+                    && getColorUtil().isGrayscaleIcon(mContext, largeIcon)) {
                 applyLargeIconBackground(contentView);
             } else {
                 removeLargeIconBackground(contentView);
@@ -3404,7 +3403,7 @@
             }
             final boolean gray = isLegacy()
                     && smallIcon.getType() == Icon.TYPE_RESOURCE
-                    && mColorUtil.isGrayscaleIcon(mContext, smallIcon.getResId());
+                    && getColorUtil().isGrayscaleIcon(mContext, smallIcon.getResId());
             if (!isLegacy() || gray) {
                 contentView.setInt(R.id.right_icon,
                         "setBackgroundResource",
@@ -3421,17 +3420,17 @@
         }
 
         private int sanitizeColor() {
-            if (mColor != COLOR_DEFAULT) {
-                mColor |= 0xFF000000; // no alpha for custom colors
+            if (mN.color != COLOR_DEFAULT) {
+                mN.color |= 0xFF000000; // no alpha for custom colors
             }
-            return mColor;
+            return mN.color;
         }
 
         private int resolveColor() {
-            if (mColor == COLOR_DEFAULT) {
+            if (mN.color == COLOR_DEFAULT) {
                 return mContext.getColor(R.color.notification_icon_bg_color);
             }
-            return mColor;
+            return mN.color;
         }
 
         /**
@@ -3439,165 +3438,25 @@
          * @hide
          */
         public Notification buildUnstyled() {
-            Notification n = new Notification();
-            n.when = mWhen;
-            n.mSmallIcon = mSmallIcon;
-            if (mSmallIcon != null && mSmallIcon.getType() == Icon.TYPE_RESOURCE) {
-                n.icon = mSmallIcon.getResId();
-            }
-            n.iconLevel = mSmallIconLevel;
-            n.number = mNumber;
-
-            n.color = sanitizeColor();
-
-            setBuilderContentView(n, makeContentView());
-            n.contentIntent = mContentIntent;
-            n.deleteIntent = mDeleteIntent;
-            n.fullScreenIntent = mFullScreenIntent;
-            n.tickerText = mTickerText;
-            n.tickerView = makeTickerView();
-            n.mLargeIcon = mLargeIcon;
-            if (mLargeIcon != null && mLargeIcon.getType() == Icon.TYPE_BITMAP) {
-                n.largeIcon = mLargeIcon.getBitmap();
-            }
-            n.sound = mSound;
-            n.audioStreamType = mAudioStreamType;
-            n.audioAttributes = mAudioAttributes;
-            n.vibrate = mVibrate;
-            n.ledARGB = mLedArgb;
-            n.ledOnMS = mLedOnMs;
-            n.ledOffMS = mLedOffMs;
-            n.defaults = mDefaults;
-            n.flags = mFlags;
-            setBuilderBigContentView(n, makeBigContentView());
-            setBuilderHeadsUpContentView(n, makeHeadsUpContentView());
-            if (mLedOnMs != 0 || mLedOffMs != 0) {
-                n.flags |= FLAG_SHOW_LIGHTS;
-            }
-            if ((mDefaults & DEFAULT_LIGHTS) != 0) {
-                n.flags |= FLAG_SHOW_LIGHTS;
-            }
-            n.category = mCategory;
-            n.mGroupKey = mGroupKey;
-            n.mSortKey = mSortKey;
-            n.priority = mPriority;
             if (mActions.size() > 0) {
-                n.actions = new Action[mActions.size()];
-                mActions.toArray(n.actions);
+                mN.actions = new Action[mActions.size()];
+                mActions.toArray(mN.actions);
             }
-            n.visibility = mVisibility;
-
-            if (mPublicVersion != null) {
-                n.publicVersion = new Notification();
-                mPublicVersion.cloneInto(n.publicVersion, true);
+            if (!mPersonList.isEmpty()) {
+                mN.extras.putStringArray(EXTRA_PEOPLE,
+                        mPersonList.toArray(new String[mPersonList.size()]));
             }
             if (mTopics.size() > 0) {
-                n.topics = new Topic[mTopics.size()];
-                mTopics.toArray(n.topics);
+                mN.topics = new Topic[mTopics.size()];
+                mTopics.toArray(mN.topics);
             }
-            // Note: If you're adding new fields, also update restoreFromNotitification().
-            return n;
+            return mN;
         }
 
-        /**
-         * Capture, in the provided bundle, semantic information used in the construction of
-         * this Notification object.
-         * @hide
-         */
-        public void populateExtras(Bundle extras) {
-            // Store original information used in the construction of this object
-            extras.putInt(EXTRA_ORIGINATING_USERID, mOriginatingUserId);
-            extras.putParcelable(EXTRA_REBUILD_CONTEXT_APPLICATION_INFO,
-                    mContext.getApplicationInfo());
-            extras.putCharSequence(EXTRA_TITLE, mContentTitle);
-            extras.putCharSequence(EXTRA_TEXT, mContentText);
-            extras.putCharSequence(EXTRA_SUB_TEXT, mSubText);
-            extras.putCharSequence(EXTRA_INFO_TEXT, mContentInfo);
-            extras.putParcelable(EXTRA_SMALL_ICON, mSmallIcon);
-            extras.putInt(EXTRA_PROGRESS, mProgress);
-            extras.putInt(EXTRA_PROGRESS_MAX, mProgressMax);
-            extras.putBoolean(EXTRA_PROGRESS_INDETERMINATE, mProgressIndeterminate);
-            extras.putBoolean(EXTRA_SHOW_CHRONOMETER, mUseChronometer);
-            extras.putBoolean(EXTRA_SHOW_WHEN, mShowWhen);
-            if (mLargeIcon != null) {
-                extras.putParcelable(EXTRA_LARGE_ICON, mLargeIcon);
-            }
-            if (!mPeople.isEmpty()) {
-                extras.putStringArray(EXTRA_PEOPLE, mPeople.toArray(new String[mPeople.size()]));
-            }
-            // NOTE: If you're adding new extras also update restoreFromNotification().
-        }
-
-
-        /**
-         * @hide
-         */
-        public static void stripForDelivery(Notification n) {
-            if (!STRIP_AND_REBUILD) {
-                return;
-            }
-
-            String templateClass = n.extras.getString(EXTRA_TEMPLATE);
-            // Only strip views for known Styles because we won't know how to
-            // re-create them otherwise.
-            boolean stripViews = TextUtils.isEmpty(templateClass) ||
-                    getNotificationStyleClass(templateClass) != null;
-
-            boolean isStripped = false;
-
-            if (n.largeIcon != null && n.extras.containsKey(EXTRA_LARGE_ICON)) {
-                // TODO: Would like to check for equality here, but if the notification
-                // has been cloned, we can't.
-                n.largeIcon = null;
-                n.extras.putBoolean(EXTRA_REBUILD_LARGE_ICON, true);
-                isStripped = true;
-            }
-            // Get rid of unmodified BuilderRemoteViews.
-
-            if (stripViews &&
-                    n.contentView instanceof BuilderRemoteViews &&
-                    n.extras.getInt(EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT, -1) ==
-                            n.contentView.getSequenceNumber()) {
-                n.contentView = null;
-                n.extras.putBoolean(EXTRA_REBUILD_CONTENT_VIEW, true);
-                n.extras.remove(EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT);
-                isStripped = true;
-            }
-            if (stripViews &&
-                    n.bigContentView instanceof BuilderRemoteViews &&
-                    n.extras.getInt(EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT, -1) ==
-                            n.bigContentView.getSequenceNumber()) {
-                n.bigContentView = null;
-                n.extras.putBoolean(EXTRA_REBUILD_BIG_CONTENT_VIEW, true);
-                n.extras.remove(EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT);
-                isStripped = true;
-            }
-            if (stripViews &&
-                    n.headsUpContentView instanceof BuilderRemoteViews &&
-                    n.extras.getInt(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT, -1) ==
-                            n.headsUpContentView.getSequenceNumber()) {
-                n.headsUpContentView = null;
-                n.extras.putBoolean(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW, true);
-                n.extras.remove(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT);
-                isStripped = true;
-            }
-
-            if (isStripped) {
-                n.extras.putBoolean(EXTRA_NEEDS_REBUILD, true);
-            }
-        }
-
-        /**
-         * @hide
-         */
-        public static Notification rebuild(Context context, Notification n) {
-            Bundle extras = n.extras;
-            if (!extras.getBoolean(EXTRA_NEEDS_REBUILD)) return n;
-            extras.remove(EXTRA_NEEDS_REBUILD);
-
+        public static Notification.Builder recoverBuilder(Context context, Notification n) {
             // Re-create notification context so we can access app resources.
-            ApplicationInfo applicationInfo = extras.getParcelable(
-                    EXTRA_REBUILD_CONTEXT_APPLICATION_INFO);
+            ApplicationInfo applicationInfo = n.extras.getParcelable(
+                    EXTRA_BUILDER_APPLICATION_INFO);
             Context builderContext;
             try {
                 builderContext = context.createApplicationContext(applicationInfo,
@@ -3607,58 +3466,7 @@
                 builderContext = context;  // try with our context
             }
 
-            Builder b = new Builder(builderContext, n);
-            return b.rebuild();
-        }
-
-        /**
-         * Rebuilds the notification passed in to the rebuild-constructor
-         * {@link #Builder(Context, Notification)}.
-         *
-         * <p>
-         * Throws IllegalStateException when invoked on a Builder that isn't in rebuild mode.
-         *
-         * @hide
-         */
-        private Notification rebuild() {
-            if (mRebuildNotification == null) {
-                throw new IllegalStateException("rebuild() only valid when in 'rebuild' mode.");
-            }
-            mHasThreeLines = hasThreeLines();
-
-            Bundle extras = mRebuildNotification.extras;
-
-            if (extras.getBoolean(EXTRA_REBUILD_LARGE_ICON)) {
-                mRebuildNotification.largeIcon = extras.getParcelable(EXTRA_LARGE_ICON);
-            }
-            extras.remove(EXTRA_REBUILD_LARGE_ICON);
-
-            if (extras.getBoolean(EXTRA_REBUILD_CONTENT_VIEW)) {
-                setBuilderContentView(mRebuildNotification, makeContentView());
-                if (mStyle != null) {
-                    mStyle.populateContentView(mRebuildNotification);
-                }
-            }
-            extras.remove(EXTRA_REBUILD_CONTENT_VIEW);
-
-            if (extras.getBoolean(EXTRA_REBUILD_BIG_CONTENT_VIEW)) {
-                setBuilderBigContentView(mRebuildNotification, makeBigContentView());
-                if (mStyle != null) {
-                    mStyle.populateBigContentView(mRebuildNotification);
-                }
-            }
-            extras.remove(EXTRA_REBUILD_BIG_CONTENT_VIEW);
-
-            if (extras.getBoolean(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW)) {
-                setBuilderHeadsUpContentView(mRebuildNotification, makeHeadsUpContentView());
-                if (mStyle != null) {
-                    mStyle.populateHeadsUpContentView(mRebuildNotification);
-                }
-            }
-            extras.remove(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW);
-
-            mHasThreeLines = false;
-            return mRebuildNotification;
+            return new Builder(builderContext, n);
         }
 
         private static Class<? extends Style> getNotificationStyleClass(String templateClass) {
@@ -3674,91 +3482,15 @@
 
         private void setBuilderContentView(Notification n, RemoteViews contentView) {
             n.contentView = contentView;
-            if (contentView instanceof BuilderRemoteViews) {
-                mRebuildBundle.putInt(Builder.EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT,
-                        contentView.getSequenceNumber());
-            }
         }
 
         private void setBuilderBigContentView(Notification n, RemoteViews bigContentView) {
             n.bigContentView = bigContentView;
-            if (bigContentView instanceof BuilderRemoteViews) {
-                mRebuildBundle.putInt(Builder.EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT,
-                        bigContentView.getSequenceNumber());
-            }
         }
 
         private void setBuilderHeadsUpContentView(Notification n,
                 RemoteViews headsUpContentView) {
             n.headsUpContentView = headsUpContentView;
-            if (headsUpContentView instanceof BuilderRemoteViews) {
-                mRebuildBundle.putInt(Builder.EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT,
-                        headsUpContentView.getSequenceNumber());
-            }
-        }
-
-        private void restoreFromNotification(Notification n) {
-
-            // Notification fields.
-            mWhen = n.when;
-            mSmallIcon = n.mSmallIcon;
-            mSmallIconLevel = n.iconLevel;
-            mNumber = n.number;
-
-            mColor = n.color;
-
-            mContentView = n.contentView;
-            mDeleteIntent = n.deleteIntent;
-            mFullScreenIntent = n.fullScreenIntent;
-            mTickerText = n.tickerText;
-            mTickerView = n.tickerView;
-            mLargeIcon = n.mLargeIcon;
-            mSound = n.sound;
-            mAudioStreamType = n.audioStreamType;
-            mAudioAttributes = n.audioAttributes;
-
-            mVibrate = n.vibrate;
-            mLedArgb = n.ledARGB;
-            mLedOnMs = n.ledOnMS;
-            mLedOffMs = n.ledOffMS;
-            mDefaults = n.defaults;
-            mFlags = n.flags;
-
-            mCategory = n.category;
-            mGroupKey = n.mGroupKey;
-            mSortKey = n.mSortKey;
-            mPriority = n.priority;
-            mActions.clear();
-            if (n.actions != null) {
-                Collections.addAll(mActions, n.actions);
-            }
-            mVisibility = n.visibility;
-
-            mPublicVersion = n.publicVersion;
-
-            if (n.topics != null) {
-                Collections.addAll(mTopics, n.topics);
-            }
-
-            // Extras.
-            Bundle extras = n.extras;
-            mOriginatingUserId = extras.getInt(EXTRA_ORIGINATING_USERID);
-            mContentTitle = extras.getCharSequence(EXTRA_TITLE);
-            mContentText = extras.getCharSequence(EXTRA_TEXT);
-            mSubText = extras.getCharSequence(EXTRA_SUB_TEXT);
-            mContentInfo = extras.getCharSequence(EXTRA_INFO_TEXT);
-            mProgress = extras.getInt(EXTRA_PROGRESS);
-            mProgressMax = extras.getInt(EXTRA_PROGRESS_MAX);
-            mProgressIndeterminate = extras.getBoolean(EXTRA_PROGRESS_INDETERMINATE);
-            mUseChronometer = extras.getBoolean(EXTRA_SHOW_CHRONOMETER);
-            mShowWhen = extras.getBoolean(EXTRA_SHOW_WHEN);
-            if (extras.containsKey(EXTRA_LARGE_ICON)) {
-                mLargeIcon = extras.getParcelable(EXTRA_LARGE_ICON);
-            }
-            if (extras.containsKey(EXTRA_PEOPLE)) {
-                mPeople.clear();
-                Collections.addAll(mPeople, extras.getStringArray(EXTRA_PEOPLE));
-            }
         }
 
         /**
@@ -3774,38 +3506,23 @@
          * object.
          */
         public Notification build() {
-            if (mSmallIcon != null) {
-                mSmallIcon.convertToAshmem();
+            // first, add any extras from the calling code
+            if (mUserExtras != null) {
+                mN.extras = getAllExtras();
             }
-            if (mLargeIcon != null) {
-                mLargeIcon.convertToAshmem();
-            }
+
+            // lazy stuff from mContext; see comment in Builder(Context, Notification)
+            mN.extras.putParcelable(EXTRA_BUILDER_APPLICATION_INFO, mContext.getApplicationInfo());
             mOriginatingUserId = mContext.getUserId();
-            mHasThreeLines = hasThreeLines();
+            mN.extras.putInt(EXTRA_ORIGINATING_USERID, mOriginatingUserId);
 
-            Notification n = buildUnstyled();
+            buildUnstyled();
 
             if (mStyle != null) {
-                mStyle.purgeResources();
-                n = mStyle.buildStyled(n);
+                mStyle.buildStyled(mN);
             }
 
-            if (mExtras != null) {
-                n.extras.putAll(mExtras);
-            }
-
-            if (mRebuildBundle.size() > 0) {
-                n.extras.putAll(mRebuildBundle);
-                mRebuildBundle.clear();
-            }
-
-            populateExtras(n.extras);
-            if (mStyle != null) {
-                mStyle.addExtras(n.extras);
-            }
-
-            mHasThreeLines = false;
-            return n;
+            return mN;
         }
 
         /**
@@ -3901,14 +3618,15 @@
             checkBuilder();
 
             // Nasty.
-            CharSequence oldBuilderContentTitle = mBuilder.mContentTitle;
+            CharSequence oldBuilderContentTitle =
+                    mBuilder.getAllExtras().getCharSequence(EXTRA_TITLE);
             if (mBigContentTitle != null) {
                 mBuilder.setContentTitle(mBigContentTitle);
             }
 
             RemoteViews contentView = mBuilder.applyStandardTemplateWithActions(layoutId);
 
-            mBuilder.mContentTitle = oldBuilderContentTitle;
+            mBuilder.getAllExtras().putCharSequence(EXTRA_TITLE, oldBuilderContentTitle);
 
             if (mBigContentTitle != null && mBigContentTitle.equals("")) {
                 contentView.setViewVisibility(R.id.line1, View.GONE);
@@ -3919,7 +3637,7 @@
             // The last line defaults to the subtext, but can be replaced by mSummaryText
             final CharSequence overflowText =
                     mSummaryTextSet ? mSummaryText
-                                    : mBuilder.mSubText;
+                                    : mBuilder.getAllExtras().getCharSequence(EXTRA_SUB_TEXT);
             if (overflowText != null) {
                 contentView.setTextViewText(R.id.text, mBuilder.processLegacyText(overflowText));
                 contentView.setViewVisibility(R.id.overflow_divider, View.VISIBLE);
@@ -3935,6 +3653,31 @@
         }
 
         /**
+         * Construct a Style-specific RemoteViews for the final 1U notification layout.
+         * The default implementation has nothing additional to add.
+         * @hide
+         */
+        public RemoteViews makeContentView() {
+            return null;
+        }
+
+        /**
+         * Construct a Style-specific RemoteViews for the final big notification layout.
+         * @hide
+         */
+        public RemoteViews makeBigContentView() {
+            return null;
+        }
+
+        /**
+         * Construct a Style-specific RemoteViews for the final HUN layout.
+         * @hide
+         */
+        public RemoteViews makeHeadsUpContentView() {
+            return null;
+        }
+
+        /**
          * Changes the padding of the first line such that the big and small content view have the
          * same top padding.
          *
@@ -3942,12 +3685,13 @@
          */
         protected void applyTopPadding(RemoteViews contentView) {
             int topPadding = Builder.calculateTopPadding(mBuilder.mContext,
-                    mBuilder.mHasThreeLines,
+                    mBuilder.hasThreeLines(),
                     mBuilder.mContext.getResources().getConfiguration().fontScale);
             contentView.setViewPadding(R.id.line1, 0, topPadding, 0, 0);
         }
 
         /**
+         * Apply any style-specific extras to this notification before shipping it out.
          * @hide
          */
         public void addExtras(Bundle extras) {
@@ -3961,6 +3705,7 @@
         }
 
         /**
+         * Reconstruct the internal state of this Style object from extras.
          * @hide
          */
         protected void restoreFromExtras(Bundle extras) {
@@ -3978,10 +3723,7 @@
          * @hide
          */
         public Notification buildStyled(Notification wip) {
-            populateTickerView(wip);
-            populateContentView(wip);
-            populateBigContentView(wip);
-            populateHeadsUpContentView(wip);
+            addExtras(wip.extras);
             return wip;
         }
 
@@ -3990,26 +3732,6 @@
          */
         public void purgeResources() {}
 
-        // The following methods are split out so we can re-create notification partially.
-        /**
-         * @hide
-         */
-        protected void populateTickerView(Notification wip) {}
-        /**
-         * @hide
-         */
-        protected void populateContentView(Notification wip) {}
-
-        /**
-         * @hide
-         */
-        protected void populateBigContentView(Notification wip) {}
-
-        /**
-         * @hide
-         */
-        protected void populateHeadsUpContentView(Notification wip) {}
-
         /**
          * Calls {@link android.app.Notification.Builder#build()} on the Builder this Style is
          * attached to.
@@ -4115,29 +3837,33 @@
             }
         }
 
-        private RemoteViews makeBigContentView() {
-            // Replace mLargeIcon with mBigLargeIcon if mBigLargeIconSet
+        /**
+         * @hide
+         */
+        public RemoteViews makeBigContentView() {
+            // Replace mN.mLargeIcon with mBigLargeIcon if mBigLargeIconSet
             // This covers the following cases:
             //   1. mBigLargeIconSet -> mBigLargeIcon (null or non-null) applies, overrides
-            //          mLargeIcon
-            //   2. !mBigLargeIconSet -> mLargeIcon applies
+            //          mN.mLargeIcon
+            //   2. !mBigLargeIconSet -> mN.mLargeIcon applies
             Icon oldLargeIcon = null;
             if (mBigLargeIconSet) {
-                oldLargeIcon = mBuilder.mLargeIcon;
-                mBuilder.mLargeIcon = mBigLargeIcon;
+                oldLargeIcon = mBuilder.mN.mLargeIcon;
+                mBuilder.mN.mLargeIcon = mBigLargeIcon;
             }
 
             RemoteViews contentView = getStandardView(mBuilder.getBigPictureLayoutResource());
 
             if (mBigLargeIconSet) {
-                mBuilder.mLargeIcon = oldLargeIcon;
+                mBuilder.mN.mLargeIcon = oldLargeIcon;
             }
 
             contentView.setImageViewBitmap(R.id.big_picture, mPicture);
 
             applyTopPadding(contentView);
 
-            boolean twoTextLines = mBuilder.mSubText != null && mBuilder.mContentText != null;
+            boolean twoTextLines = mBuilder.getAllExtras().getCharSequence(EXTRA_SUB_TEXT) != null
+                    && mBuilder.getAllExtras().getCharSequence(EXTRA_TEXT) != null;
             mBuilder.addProfileBadge(contentView,
                     twoTextLines ? R.id.profile_badge_line2 : R.id.profile_badge_line3);
             return contentView;
@@ -4168,14 +3894,6 @@
             }
             mPicture = extras.getParcelable(EXTRA_PICTURE);
         }
-
-        /**
-         * @hide
-         */
-        @Override
-        public void populateBigContentView(Notification wip) {
-            mBuilder.setBuilderBigContentView(wip, makeBigContentView());
-        }
     }
 
     /**
@@ -4255,15 +3973,19 @@
             mBigText = extras.getCharSequence(EXTRA_BIG_TEXT);
         }
 
-        private RemoteViews makeBigContentView() {
+        /**
+         * @hide
+         */
+        public RemoteViews makeBigContentView() {
 
             // Nasty
-            CharSequence oldBuilderContentText = mBuilder.mContentText;
-            mBuilder.mContentText = null;
+            CharSequence oldBuilderContentText =
+                    mBuilder.getAllExtras().getCharSequence(EXTRA_TEXT);
+            mBuilder.getAllExtras().putCharSequence(EXTRA_TEXT, null);
 
             RemoteViews contentView = getStandardView(mBuilder.getBigTextLayoutResource());
 
-            mBuilder.mContentText = oldBuilderContentText;
+            mBuilder.getAllExtras().putCharSequence(EXTRA_TEXT, oldBuilderContentText);
 
             contentView.setTextViewText(R.id.big_text, mBuilder.processLegacyText(mBigText));
             contentView.setViewVisibility(R.id.big_text, View.VISIBLE);
@@ -4282,7 +4004,8 @@
         private int calculateMaxLines() {
             int lineCount = MAX_LINES;
             boolean hasActions = mBuilder.mActions.size() > 0;
-            boolean hasSummary = (mSummaryTextSet ? mSummaryText : mBuilder.mSubText) != null;
+            boolean hasSummary = (mSummaryTextSet ? mSummaryText
+                    : mBuilder.getAllExtras().getCharSequence(EXTRA_SUB_TEXT)) != null;
             if (hasActions) {
                 lineCount -= LINES_CONSUMED_BY_ACTIONS;
             }
@@ -4291,19 +4014,11 @@
             }
 
             // If we have less top padding at the top, we can fit less lines.
-            if (!mBuilder.mHasThreeLines) {
+            if (!mBuilder.hasThreeLines()) {
                 lineCount--;
             }
             return lineCount;
         }
-
-        /**
-         * @hide
-         */
-        @Override
-        public void populateBigContentView(Notification wip) {
-            mBuilder.setBuilderBigContentView(wip, makeBigContentView());
-        }
     }
 
     /**
@@ -4384,16 +4099,18 @@
             }
         }
 
-        private RemoteViews makeBigContentView() {
+        /**
+         * @hide
+         */
+        public RemoteViews makeBigContentView() {
             // Remove the content text so line3 disappears unless you have a summary
-
             // Nasty
-            CharSequence oldBuilderContentText = mBuilder.mContentText;
-            mBuilder.mContentText = null;
+            CharSequence oldBuilderContentText = mBuilder.mN.extras.getCharSequence(EXTRA_TEXT);
+            mBuilder.getAllExtras().putCharSequence(EXTRA_TEXT, null);
 
             RemoteViews contentView = getStandardView(mBuilder.getInboxLayoutResource());
 
-            mBuilder.mContentText = oldBuilderContentText;
+            mBuilder.getAllExtras().putCharSequence(EXTRA_TEXT, oldBuilderContentText);
 
             contentView.setViewVisibility(R.id.text2, View.GONE);
 
@@ -4437,14 +4154,6 @@
 
             return contentView;
         }
-
-        /**
-         * @hide
-         */
-        @Override
-        public void populateBigContentView(Notification wip) {
-            mBuilder.setBuilderBigContentView(wip, makeBigContentView());
-        }
     }
 
     /**
@@ -4536,16 +4245,16 @@
          * @hide
          */
         @Override
-        public void populateContentView(Notification wip) {
-            mBuilder.setBuilderContentView(wip, makeMediaContentView());
+        public RemoteViews makeContentView() {
+            return makeMediaContentView();
         }
 
         /**
          * @hide
          */
         @Override
-        public void populateBigContentView(Notification wip) {
-            mBuilder.setBuilderBigContentView(wip, makeMediaBigContentView());
+        public RemoteViews makeBigContentView() {
+            return makeMediaBigContentView();
         }
 
         /** @hide */
@@ -4659,7 +4368,7 @@
                     R.color.notification_media_secondary_color);
             contentView.setTextColor(R.id.title, primaryColor);
             if (mBuilder.showsTimeOrChronometer()) {
-                if (mBuilder.mUseChronometer) {
+                if (mBuilder.getAllExtras().getBoolean(EXTRA_SHOW_CHRONOMETER)) {
                     contentView.setTextColor(R.id.chronometer, secondaryColor);
                 } else {
                     contentView.setTextColor(R.id.time, secondaryColor);
@@ -5503,7 +5212,7 @@
         /**
          * Gets the accent color.
          *
-         * @see setColor
+         * @see #setColor
          */
         @ColorInt
         public int getColor() {
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index cb0ff33..f75b22a 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -207,33 +207,7 @@
      */
     public void notify(String tag, int id, Notification notification)
     {
-        int[] idOut = new int[1];
-        INotificationManager service = getService();
-        String pkg = mContext.getPackageName();
-        if (notification.sound != null) {
-            notification.sound = notification.sound.getCanonicalUri();
-            if (StrictMode.vmFileUriExposureEnabled()) {
-                notification.sound.checkFileUriExposed("Notification.sound");
-            }
-        }
-        fixLegacySmallIcon(notification, pkg);
-        if (mContext.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1) {
-            if (notification.getSmallIcon() == null) {
-                throw new IllegalArgumentException("Invalid notification (no valid small icon): "
-                    + notification);
-            }
-        }
-        if (localLOGV) Log.v(TAG, pkg + ": notify(" + id + ", " + notification + ")");
-        Notification stripped = notification.clone();
-        Builder.stripForDelivery(stripped);
-        try {
-            service.enqueueNotificationWithTag(pkg, mContext.getOpPackageName(), tag, id,
-                    stripped, idOut, UserHandle.myUserId());
-            if (id != idOut[0]) {
-                Log.w(TAG, "notify: id corrupted: sent " + id + ", got back " + idOut[0]);
-            }
-        } catch (RemoteException e) {
-        }
+        notifyAsUser(tag, id, notification, new UserHandle(UserHandle.myUserId()));
     }
 
     /**
@@ -251,12 +225,17 @@
             }
         }
         fixLegacySmallIcon(notification, pkg);
+        if (mContext.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1) {
+            if (notification.getSmallIcon() == null) {
+                throw new IllegalArgumentException("Invalid notification (no valid small icon): "
+                        + notification);
+            }
+        }
         if (localLOGV) Log.v(TAG, pkg + ": notify(" + id + ", " + notification + ")");
-        Notification stripped = notification.clone();
-        Builder.stripForDelivery(stripped);
+        final Notification copy = notification.clone();
         try {
             service.enqueueNotificationWithTag(pkg, mContext.getOpPackageName(), tag, id,
-                    stripped, idOut, user.getIdentifier());
+                    copy, idOut, user.getIdentifier());
             if (id != idOut[0]) {
                 Log.w(TAG, "notify: id corrupted: sent " + id + ", got back " + idOut[0]);
             }
@@ -287,13 +266,7 @@
      */
     public void cancel(String tag, int id)
     {
-        INotificationManager service = getService();
-        String pkg = mContext.getPackageName();
-        if (localLOGV) Log.v(TAG, pkg + ": cancel(" + id + ")");
-        try {
-            service.cancelNotificationWithTag(pkg, tag, id, UserHandle.myUserId());
-        } catch (RemoteException e) {
-        }
+        cancelAsUser(tag, id, new UserHandle(UserHandle.myUserId()));
     }
 
     /**
diff --git a/core/java/android/app/admin/DevicePolicyManagerInternal.java b/core/java/android/app/admin/DevicePolicyManagerInternal.java
index 0a0d77d..4270e16 100644
--- a/core/java/android/app/admin/DevicePolicyManagerInternal.java
+++ b/core/java/android/app/admin/DevicePolicyManagerInternal.java
@@ -16,6 +16,8 @@
 
 package android.app.admin;
 
+import android.os.Bundle;
+
 import java.util.List;
 
 /**
@@ -69,4 +71,13 @@
      * @return true if the uid is an active admin with the given policy.
      */
     public abstract boolean isActiveAdminWithPolicy(int uid, int reqPolicy);
+
+    /**
+     * Takes a {@link Bundle} containing "base" user restrictions stored in
+     * {@link com.android.server.pm.UserManagerService}, mixes restrictions set by the device owner
+     * and the profile owner and returns the merged restrictions.
+     *
+     * This method always returns a new {@link Bundle}.
+     */
+    public abstract Bundle getComposedUserRestrictions(int userId, Bundle inBundle);
 }
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index 3ade170..12cac85 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -55,14 +55,12 @@
     int getUserHandle(int userSerialNumber);
     Bundle getUserRestrictions(int userHandle);
     boolean hasUserRestriction(in String restrictionKey, int userHandle);
-    void setUserRestrictions(in Bundle restrictions, int userHandle);
     void setUserRestriction(String key, boolean value, int userId);
     void setSystemControlledUserRestriction(String key, boolean value, int userId);
     void setApplicationRestrictions(in String packageName, in Bundle restrictions,
             int userHandle);
     Bundle getApplicationRestrictions(in String packageName);
     Bundle getApplicationRestrictionsForUser(in String packageName, int userHandle);
-    void removeRestrictions();
     void setDefaultGuestRestrictions(in Bundle restrictions);
     Bundle getDefaultGuestRestrictions();
     boolean markGuestForDeletion(int userHandle);
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 1c1575e..909d33c 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -21,6 +21,7 @@
 import android.app.ActivityManager;
 import android.app.ActivityManagerNative;
 import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.UserInfo;
 import android.content.res.Resources;
@@ -55,7 +56,8 @@
      *
      * <p/>Key for user restrictions.
      * <p/>Type: Boolean
-     * @see #setUserRestrictions(Bundle)
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
      * @see #getUserRestrictions()
      */
     public static final String DISALLOW_MODIFY_ACCOUNTS = "no_modify_accounts";
@@ -67,7 +69,8 @@
      *
      * <p/>Key for user restrictions.
      * <p/>Type: Boolean
-     * @see #setUserRestrictions(Bundle)
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
      * @see #getUserRestrictions()
      */
     public static final String DISALLOW_CONFIG_WIFI = "no_config_wifi";
@@ -78,7 +81,8 @@
      *
      * <p/>Key for user restrictions.
      * <p/>Type: Boolean
-     * @see #setUserRestrictions(Bundle)
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
      * @see #getUserRestrictions()
      */
     public static final String DISALLOW_INSTALL_APPS = "no_install_apps";
@@ -89,7 +93,8 @@
      *
      * <p/>Key for user restrictions.
      * <p/>Type: Boolean
-     * @see #setUserRestrictions(Bundle)
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
      * @see #getUserRestrictions()
      */
     public static final String DISALLOW_UNINSTALL_APPS = "no_uninstall_apps";
@@ -102,7 +107,8 @@
      *
      * <p/>Key for user restrictions.
      * <p/>Type: Boolean
-     * @see #setUserRestrictions(Bundle)
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
      * @see #getUserRestrictions()
      */
     public static final String DISALLOW_SHARE_LOCATION = "no_share_location";
@@ -114,7 +120,8 @@
      *
      * <p/>Key for user restrictions.
      * <p/>Type: Boolean
-     * @see #setUserRestrictions(Bundle)
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
      * @see #getUserRestrictions()
      */
     public static final String DISALLOW_INSTALL_UNKNOWN_SOURCES = "no_install_unknown_sources";
@@ -127,7 +134,8 @@
      *
      * <p/>Key for user restrictions.
      * <p/>Type: Boolean
-     * @see #setUserRestrictions(Bundle)
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
      * @see #getUserRestrictions()
      */
     public static final String DISALLOW_CONFIG_BLUETOOTH = "no_config_bluetooth";
@@ -139,7 +147,8 @@
      *
      * <p/>Key for user restrictions.
      * <p/>Type: Boolean
-     * @see #setUserRestrictions(Bundle)
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
      * @see #getUserRestrictions()
      */
     public static final String DISALLOW_USB_FILE_TRANSFER = "no_usb_file_transfer";
@@ -150,7 +159,8 @@
      *
      * <p/>Key for user restrictions.
      * <p/>Type: Boolean
-     * @see #setUserRestrictions(Bundle)
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
      * @see #getUserRestrictions()
      */
     public static final String DISALLOW_CONFIG_CREDENTIALS = "no_config_credentials";
@@ -163,7 +173,8 @@
      *
      * <p/>Key for user restrictions.
      * <p/>Type: Boolean
-     * @see #setUserRestrictions(Bundle)
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
      * @see #getUserRestrictions()
      */
     public static final String DISALLOW_REMOVE_USER = "no_remove_user";
@@ -174,7 +185,8 @@
      *
      * <p/>Key for user restrictions.
      * <p/>Type: Boolean
-     * @see #setUserRestrictions(Bundle)
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
      * @see #getUserRestrictions()
      */
     public static final String DISALLOW_DEBUGGING_FEATURES = "no_debugging_features";
@@ -187,7 +199,8 @@
      *
      * <p/>Key for user restrictions.
      * <p/>Type: Boolean
-     * @see #setUserRestrictions(Bundle)
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
      * @see #getUserRestrictions()
      */
     public static final String DISALLOW_CONFIG_VPN = "no_config_vpn";
@@ -199,7 +212,8 @@
      *
      * <p/>Key for user restrictions.
      * <p/>Type: Boolean
-     * @see #setUserRestrictions(Bundle)
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
      * @see #getUserRestrictions()
      */
     public static final String DISALLOW_CONFIG_TETHERING = "no_config_tethering";
@@ -213,7 +227,8 @@
      *
      * <p/>Key for user restrictions.
      * <p/>Type: Boolean
-     * @see #setUserRestrictions(Bundle)
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
      * @see #getUserRestrictions()
      */
     public static final String DISALLOW_NETWORK_RESET = "no_network_reset";
@@ -227,7 +242,8 @@
      *
      * <p/>Key for user restrictions.
      * <p/>Type: Boolean
-     * @see #setUserRestrictions(Bundle)
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
      * @see #getUserRestrictions()
      */
     public static final String DISALLOW_FACTORY_RESET = "no_factory_reset";
@@ -241,7 +257,8 @@
      *
      * <p/>Key for user restrictions.
      * <p/>Type: Boolean
-     * @see #setUserRestrictions(Bundle)
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
      * @see #getUserRestrictions()
      */
     public static final String DISALLOW_ADD_USER = "no_add_user";
@@ -252,7 +269,8 @@
      *
      * <p/>Key for user restrictions.
      * <p/>Type: Boolean
-     * @see #setUserRestrictions(Bundle)
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
      * @see #getUserRestrictions()
      */
     public static final String ENSURE_VERIFY_APPS = "ensure_verify_apps";
@@ -266,7 +284,8 @@
      *
      * <p/>Key for user restrictions.
      * <p/>Type: Boolean
-     * @see #setUserRestrictions(Bundle)
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
      * @see #getUserRestrictions()
      */
     public static final String DISALLOW_CONFIG_CELL_BROADCASTS = "no_config_cell_broadcasts";
@@ -280,7 +299,8 @@
      *
      * <p/>Key for user restrictions.
      * <p/>Type: Boolean
-     * @see #setUserRestrictions(Bundle)
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
      * @see #getUserRestrictions()
      */
     public static final String DISALLOW_CONFIG_MOBILE_NETWORKS = "no_config_mobile_networks";
@@ -300,7 +320,8 @@
      *
      * <p/>Key for user restrictions.
      * <p/>Type: Boolean
-     * @see #setUserRestrictions(Bundle)
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
      * @see #getUserRestrictions()
      */
     public static final String DISALLOW_APPS_CONTROL = "no_control_apps";
@@ -312,7 +333,8 @@
      *
      * <p/>Key for user restrictions.
      * <p/>Type: Boolean
-     * @see #setUserRestrictions(Bundle)
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
      * @see #getUserRestrictions()
      */
     public static final String DISALLOW_MOUNT_PHYSICAL_MEDIA = "no_physical_media";
@@ -324,7 +346,8 @@
      *
      * <p/>Key for user restrictions.
      * <p/>Type: Boolean
-     * @see #setUserRestrictions(Bundle)
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
      * @see #getUserRestrictions()
      */
     public static final String DISALLOW_UNMUTE_MICROPHONE = "no_unmute_microphone";
@@ -336,7 +359,8 @@
      *
      * <p/>Key for user restrictions.
      * <p/>Type: Boolean
-     * @see #setUserRestrictions(Bundle)
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
      * @see #getUserRestrictions()
      */
     public static final String DISALLOW_ADJUST_VOLUME = "no_adjust_volume";
@@ -350,7 +374,8 @@
      *
      * <p/>Key for user restrictions.
      * <p/>Type: Boolean
-     * @see #setUserRestrictions(Bundle)
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
      * @see #getUserRestrictions()
      */
     public static final String DISALLOW_OUTGOING_CALLS = "no_outgoing_calls";
@@ -361,7 +386,8 @@
      *
      * <p/>Key for user restrictions.
      * <p/>Type: Boolean
-     * @see #setUserRestrictions(Bundle)
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
      * @see #getUserRestrictions()
      */
     public static final String DISALLOW_SMS = "no_sms";
@@ -373,7 +399,8 @@
      *
      * <p/>Key for user restrictions.
      * <p/>Type: Boolean
-     * @see #setUserRestrictions(Bundle)
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
      * @see #getUserRestrictions()
      */
     public static final String DISALLOW_FUN = "no_fun";
@@ -393,7 +420,8 @@
      *
      * <p/>Key for user restrictions.
      * <p/>Type: Boolean
-     * @see #setUserRestrictions(Bundle)
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
      * @see #getUserRestrictions()
      */
     public static final String DISALLOW_CREATE_WINDOWS = "no_create_windows";
@@ -406,7 +434,8 @@
      *
      * <p/>Key for user restrictions.
      * <p/>Type: Boolean
-     * @see #setUserRestrictions(Bundle)
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
      * @see #getUserRestrictions()
      */
     public static final String DISALLOW_CROSS_PROFILE_COPY_PASTE = "no_cross_profile_copy_paste";
@@ -417,7 +446,8 @@
      *
      * <p/>Key for user restrictions.
      * <p/>Type: Boolean
-     * @see #setUserRestrictions(Bundle)
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
      * @see #getUserRestrictions()
      */
     public static final String DISALLOW_OUTGOING_BEAM = "no_outgoing_beam";
@@ -426,7 +456,8 @@
      * Hidden user restriction to disallow access to wallpaper manager APIs. This user restriction
      * is always set for managed profiles.
      * @hide
-     * @see #setUserRestrictions(Bundle)
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
      * @see #getUserRestrictions()
      */
     public static final String DISALLOW_WALLPAPER = "no_wallpaper";
@@ -438,7 +469,8 @@
      *
      * <p/>Key for user restrictions.
      * <p/>Type: Boolean
-     * @see #setUserRestrictions(Bundle)
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
      * @see #getUserRestrictions()
      */
     public static final String DISALLOW_SAFE_BOOT = "no_safe_boot";
@@ -447,7 +479,8 @@
      * Specifies if a user is not allowed to record audio. This restriction is always enabled for
      * background users. The default value is <code>false</code>.
      *
-     * @see #setUserRestrictions(Bundle)
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
      * @see #getUserRestrictions()
      * @hide
      */
@@ -466,7 +499,8 @@
      *
      * <p/>Key for user restrictions.
      * <p/>Type: Boolean
-     * @see #setUserRestrictions(Bundle)
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
      * @see #getUserRestrictions()
      */
     public static final String ALLOW_PARENT_PROFILE_APP_LINKING
@@ -740,36 +774,20 @@
     }
 
     /**
-     * Sets all the user-wide restrictions for this user.
-     * Requires the MANAGE_USERS permission.
-     * @param restrictions the Bundle containing all the restrictions.
-     * @deprecated use {@link android.app.admin.DevicePolicyManager#addUserRestriction(
-     * android.content.ComponentName, String)} or
-     * {@link android.app.admin.DevicePolicyManager#clearUserRestriction(
-     * android.content.ComponentName, String)} instead.
+     * This will no longer work.  Use {@link #setUserRestriction(String, boolean)} instead.
      */
     @Deprecated
     public void setUserRestrictions(Bundle restrictions) {
-        setUserRestrictions(restrictions, Process.myUserHandle());
+        throw new UnsupportedOperationException("This method is no longer supported");
     }
 
     /**
-     * Sets all the user-wide restrictions for the specified user.
-     * Requires the MANAGE_USERS permission.
-     * @param restrictions the Bundle containing all the restrictions.
-     * @param userHandle the UserHandle of the user for whom to set the restrictions.
-     * @deprecated use {@link android.app.admin.DevicePolicyManager#addUserRestriction(
-     * android.content.ComponentName, String)} or
-     * {@link android.app.admin.DevicePolicyManager#clearUserRestriction(
-     * android.content.ComponentName, String)} instead.
+     * This will no longer work.  Use {@link #setUserRestriction(String, boolean, UserHandle)}
+     * instead.
      */
     @Deprecated
     public void setUserRestrictions(Bundle restrictions, UserHandle userHandle) {
-        try {
-            mService.setUserRestrictions(restrictions, userHandle.getIdentifier());
-        } catch (RemoteException re) {
-            Log.w(TAG, "Could not set user restrictions", re);
-        }
+        throw new UnsupportedOperationException("This method is no longer supported");
     }
 
     /**
@@ -784,9 +802,7 @@
      */
     @Deprecated
     public void setUserRestriction(String key, boolean value) {
-        Bundle bundle = getUserRestrictions();
-        bundle.putBoolean(key, value);
-        setUserRestrictions(bundle);
+        setUserRestriction(key, value, Process.myUserHandle());
     }
 
     /**
@@ -882,9 +898,8 @@
         try {
             user = mService.createUser(name, flags);
             if (user != null && !user.isAdmin()) {
-                Bundle userRestrictions = mService.getUserRestrictions(user.id);
-                addDefaultUserRestrictions(userRestrictions);
-                mService.setUserRestrictions(userRestrictions, user.id);
+                mService.setUserRestriction(DISALLOW_SMS, true, user.id);
+                mService.setUserRestriction(DISALLOW_OUTGOING_CALLS, true, user.id);
             }
         } catch (RemoteException re) {
             Log.w(TAG, "Could not create a user", re);
@@ -899,27 +914,22 @@
      * @hide
      */
     public UserInfo createGuest(Context context, String name) {
-        UserInfo guest = createUser(name, UserInfo.FLAG_GUEST);
-        if (guest != null) {
-            Settings.Secure.putStringForUser(context.getContentResolver(),
-                    Settings.Secure.SKIP_FIRST_USE_HINTS, "1", guest.id);
-            try {
-                Bundle guestRestrictions = mService.getDefaultGuestRestrictions();
-                guestRestrictions.putBoolean(DISALLOW_SMS, true);
-                guestRestrictions.putBoolean(DISALLOW_INSTALL_UNKNOWN_SOURCES, true);
-                mService.setUserRestrictions(guestRestrictions, guest.id);
-            } catch (RemoteException re) {
-                Log.w(TAG, "Could not update guest restrictions");
+        UserInfo guest = null;
+        try {
+            guest = mService.createUser(name, UserInfo.FLAG_GUEST);
+            if (guest != null) {
+                Settings.Secure.putStringForUser(context.getContentResolver(),
+                        Settings.Secure.SKIP_FIRST_USE_HINTS, "1", guest.id);
+
+                mService.setUserRestriction(DISALLOW_SMS, true, guest.id);
+                mService.setUserRestriction(DISALLOW_INSTALL_UNKNOWN_SOURCES, true, guest.id);
             }
+        } catch (RemoteException re) {
+            Log.w(TAG, "Could not create a user", re);
         }
         return guest;
     }
 
-    private static void addDefaultUserRestrictions(Bundle restrictions) {
-        restrictions.putBoolean(DISALLOW_OUTGOING_CALLS, true);
-        restrictions.putBoolean(DISALLOW_SMS, true);
-    }
-
     /**
      * Creates a user with the specified name and options as a profile of another user.
      * Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
@@ -1465,15 +1475,6 @@
         return false;
     }
 
-    /** @hide */
-    public void removeRestrictions() {
-        try {
-            mService.removeRestrictions();
-        } catch (RemoteException re) {
-            Log.w(TAG, "Could not change restrictions pin");
-        }
-    }
-
     /**
      * @hide
      * Set restrictions that should apply to any future guest user that's created.
diff --git a/core/java/android/os/UserManagerInternal.java b/core/java/android/os/UserManagerInternal.java
new file mode 100644
index 0000000..d7be6d8
--- /dev/null
+++ b/core/java/android/os/UserManagerInternal.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.os;
+
+/**
+ * @hide Only for use within the system server.
+ */
+public abstract class UserManagerInternal {
+    /**
+     * Lock that must be held when calling certain methods in this class.
+     *
+     * This is used to avoid dead lock between
+     * {@link com.android.server.pm.UserManagerService} and
+     * {@link com.android.server.devicepolicy.DevicePolicyManagerService}.  This lock should not
+     * be newly taken while holding the DPMS lock, which would cause a dead lock.  Take this
+     * lock first before taking the DPMS lock to avoid that.
+     */
+    public abstract Object getUserRestrictionsLock();
+
+    /**
+     * Called by {@link com.android.server.devicepolicy.DevicePolicyManagerService} to get
+     * {@link com.android.server.pm.UserManagerService} to update effective user restrictions.
+     *
+     * Must be called while taking the {@link #getUserRestrictionsLock()} lock.
+     */
+    public abstract void updateEffectiveUserRestrictionsRL(int userId);
+
+    /**
+     * Called by {@link com.android.server.devicepolicy.DevicePolicyManagerService} to get
+     * {@link com.android.server.pm.UserManagerService} to update effective user restrictions.
+     *
+     * Must be called while taking the {@link #getUserRestrictionsLock()} lock.
+     */
+    public abstract void updateEffectiveUserRestrictionsForAllUsersRL();
+
+    /**
+     * Returns the "base" user restrictions.
+     *
+     * Used by {@link com.android.server.devicepolicy.DevicePolicyManagerService} for upgrading
+     * from MNC.
+     */
+    public abstract Bundle getBaseUserRestrictions(int userId);
+
+    /**
+     * Called by {@link com.android.server.devicepolicy.DevicePolicyManagerService} for upgrading
+     * from MNC.
+     */
+    public abstract void setBaseUserRestrictionsByDpmsForMigration(int userId,
+            Bundle baseRestrictions);
+}
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index d424546..7e7b5fc 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -31,6 +31,7 @@
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Icon;
 import android.graphics.Bitmap;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.Parcel;
@@ -472,9 +473,10 @@
                 StatusBarNotification sbn = list.get(i);
                 Notification notification = sbn.getNotification();
                 try {
-                    Builder.rebuild(getContext(), notification);
                     // convert icon metadata to legacy format for older clients
                     createLegacyIconExtras(notification);
+                    // populate remote views for older clients.
+                    maybePopulateRemoteViews(notification);
                 } catch (IllegalArgumentException e) {
                     if (corruptNotifications == null) {
                         corruptNotifications = new ArrayList<>(N);
@@ -676,6 +678,18 @@
         }
     }
 
+    /**
+     * Populates remote views for pre-N targeting apps.
+     */
+    private void maybePopulateRemoteViews(Notification notification) {
+        if (getContext().getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N) {
+            Builder builder = Builder.recoverBuilder(getContext(), notification);
+            notification.contentView = builder.makeContentView();
+            notification.bigContentView = builder.makeBigContentView();
+            notification.headsUpContentView = builder.makeHeadsUpContentView();
+        }
+    }
+
     private class INotificationListenerWrapper extends INotificationListener.Stub {
         @Override
         public void onNotificationPosted(IStatusBarNotificationHolder sbnHolder,
@@ -689,9 +703,10 @@
             }
 
             try {
-                Notification.Builder.rebuild(getContext(), sbn.getNotification());
+                Notification notification = sbn.getNotification();
                 // convert icon metadata to legacy format for older clients
                 createLegacyIconExtras(sbn.getNotification());
+                maybePopulateRemoteViews(sbn.getNotification());
             } catch (IllegalArgumentException e) {
                 // drop corrupt notification
                 sbn = null;
diff --git a/location/java/android/location/Location.java b/location/java/android/location/Location.java
index bf3387b..bca9be6 100644
--- a/location/java/android/location/Location.java
+++ b/location/java/android/location/Location.java
@@ -257,11 +257,13 @@
             int deg = Integer.parseInt(degrees);
             double min;
             double sec = 0.0;
+            boolean secPresent = false;
 
             if (st.hasMoreTokens()) {
                 min = Integer.parseInt(minutes);
                 String seconds = st.nextToken();
                 sec = Double.parseDouble(seconds);
+                secPresent = true;
             } else {
                 min = Double.parseDouble(minutes);
             }
@@ -273,11 +275,15 @@
             if ((deg < 0.0) || (deg > 179 && !isNegative180)) {
                 throw new IllegalArgumentException("coordinate=" + coordinate);
             }
-            if (min < 0 || min > 59) {
+
+            // min must be in [0, 59] if seconds are present, otherwise [0.0, 60.0)
+            if (min < 0 || min >= 60 || (secPresent && (min > 59))) {
                 throw new IllegalArgumentException("coordinate=" +
                         coordinate);
             }
-            if (sec < 0 || sec > 59) {
+
+            // sec must be in [0.0, 60.0)
+            if (sec < 0 || sec >= 60) {
                 throw new IllegalArgumentException("coordinate=" +
                         coordinate);
             }
diff --git a/opengl/java/android/opengl/GLES31.java b/opengl/java/android/opengl/GLES31.java
index 3cbaa60..805930e 100644
--- a/opengl/java/android/opengl/GLES31.java
+++ b/opengl/java/android/opengl/GLES31.java
@@ -24,9 +24,14 @@
 
     public static final int GL_VERTEX_SHADER_BIT                            = 0x00000001;
     public static final int GL_FRAGMENT_SHADER_BIT                          = 0x00000002;
+    public static final int GL_COMPUTE_SHADER_BIT                           = 0x00000020;
+    public static final int GL_ALL_SHADER_BITS                              = -1; // 0xFFFFFFFF
+
+    public static final int GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT              = 0x00000001;
+    public static final int GL_ELEMENT_ARRAY_BARRIER_BIT                    = 0x00000002;
     public static final int GL_UNIFORM_BARRIER_BIT                          = 0x00000004;
     public static final int GL_TEXTURE_FETCH_BARRIER_BIT                    = 0x00000008;
-    public static final int GL_COMPUTE_SHADER_BIT                           = 0x00000020;
+    public static final int GL_SHADER_IMAGE_ACCESS_BARRIER_BIT              = 0x00000020;
     public static final int GL_COMMAND_BARRIER_BIT                          = 0x00000040;
     public static final int GL_PIXEL_BUFFER_BARRIER_BIT                     = 0x00000080;
     public static final int GL_TEXTURE_UPDATE_BARRIER_BIT                   = 0x00000100;
@@ -35,7 +40,8 @@
     public static final int GL_TRANSFORM_FEEDBACK_BARRIER_BIT               = 0x00000800;
     public static final int GL_ATOMIC_COUNTER_BARRIER_BIT                   = 0x00001000;
     public static final int GL_SHADER_STORAGE_BARRIER_BIT                   = 0x00002000;
-    public static final int GL_ALL_SHADER_BITS                              = -1; // 0xFFFFFFFF
+    public static final int GL_ALL_BARRIER_BITS                             = -1; // 0xFFFFFFFF
+
 
     public static final int GL_TEXTURE_WIDTH                                = 0x1000;
     public static final int GL_TEXTURE_HEIGHT                               = 0x1001;
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java
index 0dcd02d..d75b6fd 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java
@@ -287,33 +287,24 @@
             super(context, 0);
 
             final List<RootItem> libraries = new ArrayList<>();
-            final List<RootItem> clouds = new ArrayList<>();
-            final List<RootItem> locals = new ArrayList<>();
+            final List<RootItem> others = new ArrayList<>();
 
-            for (RootInfo root : roots) {
-                RootItem item = new RootItem(root);
-                switch (root.derivedType) {
-                    case RootInfo.TYPE_LOCAL:
-                        locals.add(item);
-                        break;
-                    case RootInfo.TYPE_CLOUD:
-                        clouds.add(item);
-                        break;
-                    default:
-                        libraries.add(item);
-                        break;
+            for (final RootInfo root : roots) {
+                final RootItem item = new RootItem(root);
+                if (root.isLibrary()) {
+                    libraries.add(item);
+                } else {
+                    others.add(item);
                 }
             }
 
             final RootComparator comp = new RootComparator();
             Collections.sort(libraries, comp);
-            Collections.sort(locals, comp);
-            Collections.sort(clouds, comp);
+            Collections.sort(others, comp);
 
             addAll(libraries);
             add(new SpacerItem());
-            addAll(locals);
-            addAll(clouds);
+            addAll(others);
 
             if (includeApps != null) {
                 final PackageManager pm = context.getPackageManager();
diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java b/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java
index 17e6a80..501392c 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java
@@ -51,7 +51,8 @@
     public static final int TYPE_RECENTS = 4;
     public static final int TYPE_DOWNLOADS = 5;
     public static final int TYPE_LOCAL = 6;
-    public static final int TYPE_CLOUD = 7;
+    public static final int TYPE_MTP = 7;
+    public static final int TYPE_CLOUD = 8;
 
     public String authority;
     public String rootId;
@@ -184,6 +185,8 @@
             derivedType = TYPE_AUDIO;
         } else if (isRecents()) {
             derivedType = TYPE_RECENTS;
+        } else if (isMtp()) {
+            derivedType = TYPE_MTP;
         } else {
             derivedType = TYPE_CLOUD;
         }
@@ -216,6 +219,15 @@
                 && "audio_root".equals(rootId);
     }
 
+    public boolean isMtp() {
+        return "com.android.mtp.documents".equals(authority);
+    }
+
+    public boolean isLibrary() {
+        return derivedType == TYPE_IMAGES || derivedType == TYPE_VIDEO || derivedType == TYPE_AUDIO
+                || derivedType == TYPE_RECENTS || derivedType == TYPE_DOWNLOADS;
+    }
+
     @Override
     public String toString() {
         return "Root{authority=" + authority + ", rootId=" + rootId + ", title=" + title + "}";
diff --git a/packages/DocumentsUI/testing/TestDocumentsProvider/Android.mk b/packages/DocumentsUI/testing/TestDocumentsProvider/Android.mk
deleted file mode 100644
index 8baadba..0000000
--- a/packages/DocumentsUI/testing/TestDocumentsProvider/Android.mk
+++ /dev/null
@@ -1,14 +0,0 @@
-LOCAL_PATH := $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
-
-LOCAL_PACKAGE_NAME := TestDocumentsProvider
-LOCAL_CERTIFICATE := platform
-LOCAL_MODULE_TAGS := tests
-#LOCAL_SDK_VERSION := current
-
-LOCAL_PROGUARD_ENABLED := disabled
-LOCAL_DEX_PREOPT := false
-
-include $(BUILD_PACKAGE)
diff --git a/packages/DocumentsUI/testing/TestDocumentsProvider/AndroidManifest.xml b/packages/DocumentsUI/testing/TestDocumentsProvider/AndroidManifest.xml
deleted file mode 100644
index 66988a1..0000000
--- a/packages/DocumentsUI/testing/TestDocumentsProvider/AndroidManifest.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2015 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="com.android.documentsui.testing">
-    <application>
-        <provider android:name="TestDocumentsProvider"
-                android:authorities="com.android.documentsui.testing"
-                android:exported="true"
-                android:grantUriPermissions="true"
-                android:permission="android.permission.MANAGE_DOCUMENTS">
-            <intent-filter>
-                <action android:name="android.content.action.DOCUMENTS_PROVIDER" />
-            </intent-filter>
-        </provider>
-    </application>
-</manifest>
diff --git a/packages/DocumentsUI/testing/TestDocumentsProvider/src/com/android/documentsui/testing/TestDocumentsProvider.java b/packages/DocumentsUI/testing/TestDocumentsProvider/src/com/android/documentsui/testing/TestDocumentsProvider.java
deleted file mode 100644
index 63ff0de..0000000
--- a/packages/DocumentsUI/testing/TestDocumentsProvider/src/com/android/documentsui/testing/TestDocumentsProvider.java
+++ /dev/null
@@ -1,299 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.documentsui.testing;
-
-import android.database.Cursor;
-import android.database.MatrixCursor;
-import android.database.MatrixCursor.RowBuilder;
-import android.os.AsyncTask;
-import android.os.CancellationSignal;
-import android.os.ParcelFileDescriptor;
-import android.provider.DocumentsContract.Document;
-import android.provider.DocumentsContract.Root;
-import android.provider.DocumentsProvider;
-import android.util.Log;
-
-import java.io.ByteArrayOutputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-public class TestDocumentsProvider extends DocumentsProvider {
-    private static final String TAG = "TestDocumentsProvider";
-
-    private static final String[] DEFAULT_ROOT_PROJECTION = new String[] {
-            Root.COLUMN_ROOT_ID,
-            Root.COLUMN_FLAGS,
-            Root.COLUMN_ICON,
-            Root.COLUMN_TITLE,
-            Root.COLUMN_DOCUMENT_ID, Root.COLUMN_AVAILABLE_BYTES,
-    };
-
-    private static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[] {
-            Document.COLUMN_DOCUMENT_ID,
-            Document.COLUMN_MIME_TYPE,
-            Document.COLUMN_DISPLAY_NAME,
-            Document.COLUMN_LAST_MODIFIED,
-            Document.COLUMN_FLAGS,
-            Document.COLUMN_SIZE,
-    };
-
-    private static String[] resolveRootProjection(String[] projection) {
-        return projection != null ? projection : DEFAULT_ROOT_PROJECTION;
-    }
-
-    private static String[] resolveDocumentProjection(String[] projection) {
-        return projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION;
-    }
-
-    @Override
-    public boolean onCreate() {
-        resetRoots();
-        return true;
-    }
-
-    @Override
-    public Cursor queryRoots(String[] projection) throws FileNotFoundException {
-        final MatrixCursor result = new MatrixCursor(resolveRootProjection(projection));
-
-        RowBuilder row = result.newRow();
-        row.add(Root.COLUMN_ROOT_ID, "local");
-        row.add(Root.COLUMN_FLAGS, Root.FLAG_LOCAL_ONLY);
-        row.add(Root.COLUMN_TITLE, "TEST-Local");
-        row.add(Root.COLUMN_SUMMARY, "TEST-LocalSummary");
-        row.add(Root.COLUMN_DOCUMENT_ID, "doc:local");
-
-        row = result.newRow();
-        row.add(Root.COLUMN_ROOT_ID, "create");
-        row.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_CREATE | Root.FLAG_SUPPORTS_IS_CHILD);
-        row.add(Root.COLUMN_TITLE, "TEST-Create");
-        row.add(Root.COLUMN_DOCUMENT_ID, "doc:create");
-
-        return result;
-    }
-
-    private Map<String, Doc> mDocs = new HashMap<>();
-
-    private Doc mLocalRoot;
-    private Doc mCreateRoot;
-
-    private Doc buildDoc(String docId, String displayName, String mimeType) {
-        final Doc doc = new Doc();
-        doc.docId = docId;
-        doc.displayName = displayName;
-        doc.mimeType = mimeType;
-        mDocs.put(doc.docId, doc);
-        return doc;
-    }
-
-    public void resetRoots() {
-        Log.d(TAG, "resetRoots()");
-
-        mDocs.clear();
-
-        mLocalRoot = buildDoc("doc:local", null, Document.MIME_TYPE_DIR);
-
-        mCreateRoot = buildDoc("doc:create", null, Document.MIME_TYPE_DIR);
-        mCreateRoot.flags = Document.FLAG_DIR_SUPPORTS_CREATE;
-
-        {
-            Doc file1 = buildDoc("doc:file1", "FILE1", "mime1/file1");
-            file1.contents = "fileone".getBytes();
-            file1.flags = Document.FLAG_SUPPORTS_WRITE;
-            mLocalRoot.children.add(file1);
-            mCreateRoot.children.add(file1);
-        }
-
-        {
-            Doc file2 = buildDoc("doc:file2", "FILE2", "mime2/file2");
-            file2.contents = "filetwo".getBytes();
-            file2.flags = Document.FLAG_SUPPORTS_WRITE;
-            mLocalRoot.children.add(file2);
-            mCreateRoot.children.add(file2);
-        }
-
-        Doc dir1 = buildDoc("doc:dir1", "DIR1", Document.MIME_TYPE_DIR);
-        mLocalRoot.children.add(dir1);
-
-        {
-            Doc file3 = buildDoc("doc:file3", "FILE3", "mime3/file3");
-            file3.contents = "filethree".getBytes();
-            file3.flags = Document.FLAG_SUPPORTS_WRITE;
-            dir1.children.add(file3);
-        }
-
-        Doc dir2 = buildDoc("doc:dir2", "DIR2", Document.MIME_TYPE_DIR);
-        mCreateRoot.children.add(dir2);
-
-        {
-            Doc file4 = buildDoc("doc:file4", "FILE4", "mime4/file4");
-            file4.contents = "filefour".getBytes();
-            file4.flags = Document.FLAG_SUPPORTS_WRITE;
-            dir2.children.add(file4);
-        }
-    }
-
-    private static class Doc {
-        public String docId;
-        public int flags;
-        public String displayName;
-        public long size;
-        public String mimeType;
-        public long lastModified;
-        public byte[] contents;
-        public List<Doc> children = new ArrayList<>();
-
-        public void include(MatrixCursor result) {
-            final RowBuilder row = result.newRow();
-            row.add(Document.COLUMN_DOCUMENT_ID, docId);
-            row.add(Document.COLUMN_DISPLAY_NAME, displayName);
-            row.add(Document.COLUMN_SIZE, size);
-            row.add(Document.COLUMN_MIME_TYPE, mimeType);
-            row.add(Document.COLUMN_FLAGS, flags);
-            row.add(Document.COLUMN_LAST_MODIFIED, lastModified);
-        }
-    }
-
-    @Override
-    public boolean isChildDocument(String parentDocumentId, String documentId) {
-        for (Doc doc : mDocs.get(parentDocumentId).children) {
-            if (doc.docId.equals(documentId)) {
-                return true;
-            }
-            if (Document.MIME_TYPE_DIR.equals(doc.mimeType)) {
-                return isChildDocument(doc.docId, documentId);
-            }
-        }
-        return false;
-    }
-
-    @Override
-    public String createDocument(String parentDocumentId, String mimeType, String displayName)
-            throws FileNotFoundException {
-        final String docId = "doc:" + System.currentTimeMillis();
-        final Doc doc = buildDoc(docId, displayName, mimeType);
-        doc.flags = Document.FLAG_SUPPORTS_WRITE | Document.FLAG_SUPPORTS_RENAME;
-        mDocs.get(parentDocumentId).children.add(doc);
-        return docId;
-    }
-
-    @Override
-    public String renameDocument(String documentId, String displayName)
-            throws FileNotFoundException {
-        mDocs.get(documentId).displayName = displayName;
-        return null;
-    }
-
-    @Override
-    public void deleteDocument(String documentId) throws FileNotFoundException {
-        mDocs.remove(documentId);
-        for (Doc doc : mDocs.values()) {
-            doc.children.remove(documentId);
-        }
-    }
-
-    @Override
-    public Cursor queryDocument(String documentId, String[] projection)
-            throws FileNotFoundException {
-        final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection));
-        mDocs.get(documentId).include(result);
-        return result;
-    }
-
-    @Override
-    public Cursor queryChildDocuments(String parentDocumentId, String[] projection,
-            String sortOrder) throws FileNotFoundException {
-        final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection));
-        for (Doc doc : mDocs.get(parentDocumentId).children) {
-            doc.include(result);
-        }
-        return result;
-    }
-
-    @Override
-    public ParcelFileDescriptor openDocument(String documentId, String mode,
-            CancellationSignal signal) throws FileNotFoundException {
-        final Doc doc = mDocs.get(documentId);
-        if (doc == null) {
-            throw new FileNotFoundException();
-        }
-        final ParcelFileDescriptor[] pipe;
-        try {
-            pipe = ParcelFileDescriptor.createPipe();
-        } catch (IOException e) {
-            throw new IllegalStateException(e);
-        }
-        if (mode.contains("w")) {
-            new AsyncTask<Void, Void, Void>() {
-                @Override
-                protected Void doInBackground(Void... params) {
-                    synchronized (doc) {
-                        try {
-                            final InputStream is = new ParcelFileDescriptor.AutoCloseInputStream(
-                                    pipe[0]);
-                            doc.contents = readFullyNoClose(is);
-                            is.close();
-                            doc.notifyAll();
-                        } catch (IOException e) {
-                            Log.w(TAG, "Failed to stream", e);
-                        }
-                    }
-                    return null;
-                }
-            }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR);
-            return pipe[1];
-        } else {
-            new AsyncTask<Void, Void, Void>() {
-                @Override
-                protected Void doInBackground(Void... params) {
-                    synchronized (doc) {
-                        try {
-                            final OutputStream os = new ParcelFileDescriptor.AutoCloseOutputStream(
-                                    pipe[1]);
-                            while (doc.contents == null) {
-                                doc.wait();
-                            }
-                            os.write(doc.contents);
-                            os.close();
-                        } catch (IOException e) {
-                            Log.w(TAG, "Failed to stream", e);
-                        } catch (InterruptedException e) {
-                            Log.w(TAG, "Interuppted", e);
-                        }
-                    }
-                    return null;
-                }
-            }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR);
-            return pipe[0];
-        }
-    }
-
-    private static byte[] readFullyNoClose(InputStream in) throws IOException {
-        ByteArrayOutputStream bytes = new ByteArrayOutputStream();
-        byte[] buffer = new byte[1024];
-        int count;
-        while ((count = in.read(buffer)) != -1) {
-            bytes.write(buffer, 0, count);
-        }
-        return bytes.toByteArray();
-    }
-}
diff --git a/packages/DocumentsUI/tests/Android.mk b/packages/DocumentsUI/tests/Android.mk
index cf486b1..2a540d4 100644
--- a/packages/DocumentsUI/tests/Android.mk
+++ b/packages/DocumentsUI/tests/Android.mk
@@ -17,4 +17,3 @@
 
 include $(BUILD_PACKAGE)
 
-include $(LOCAL_PATH)/../testing/TestDocumentsProvider/Android.mk
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index bae8017..07c59a9 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -136,7 +136,7 @@
     <integer name="touch_acceptance_delay">700</integer>
 
     <!-- The duration in seconds to wait before the dismiss buttons are shown. -->
-    <integer name="recents_task_bar_dismiss_delay_seconds">1</integer>
+    <integer name="recents_task_bar_dismiss_delay_seconds">1000</integer>
 
     <!-- The min animation duration for animating views that are currently visible. -->
     <integer name="recents_filter_animate_current_views_duration">250</integer>
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Constants.java b/packages/SystemUI/src/com/android/systemui/recents/Constants.java
index cdb6b93..6668df9 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Constants.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Constants.java
@@ -37,6 +37,8 @@
             public static final boolean EnableTaskFiltering = false;
             // Enables dismiss-all
             public static final boolean EnableDismissAll = false;
+            // Enables fast-toggling
+            public static final boolean EnableFastToggleRecents = false;
             // Enables the thumbnail alpha on the front-most task
             public static final boolean EnableThumbnailAlphaOnFrontmost = false;
             // This disables the search bar integration
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index c416967..0adad85 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -32,6 +32,7 @@
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.provider.Settings;
+import android.util.Log;
 import android.view.KeyEvent;
 import android.view.View;
 import android.view.ViewStub;
@@ -41,16 +42,21 @@
 import com.android.systemui.recents.events.activity.AppWidgetProviderChangedEvent;
 import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationStartedEvent;
 import com.android.systemui.recents.events.activity.HideRecentsEvent;
+import com.android.systemui.recents.events.activity.IterateRecentsEvent;
 import com.android.systemui.recents.events.activity.ToggleRecentsEvent;
 import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
 import com.android.systemui.recents.events.component.ScreenPinningRequestEvent;
-import com.android.systemui.recents.events.ui.DismissTaskEvent;
+import com.android.systemui.recents.events.ui.DismissTaskViewEvent;
 import com.android.systemui.recents.events.ui.ResizeTaskEvent;
 import com.android.systemui.recents.events.ui.ShowApplicationInfoEvent;
 import com.android.systemui.recents.events.ui.UserInteractionEvent;
 import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
 import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent;
+import com.android.systemui.recents.events.ui.focus.DismissFocusedTaskViewEvent;
+import com.android.systemui.recents.events.ui.focus.FocusNextTaskViewEvent;
+import com.android.systemui.recents.events.ui.focus.FocusPreviousTaskViewEvent;
 import com.android.systemui.recents.misc.Console;
+import com.android.systemui.recents.misc.DozeTrigger;
 import com.android.systemui.recents.misc.ReferenceCountedTrigger;
 import com.android.systemui.recents.misc.SystemServicesProxy;
 import com.android.systemui.recents.model.RecentsPackageMonitor;
@@ -69,6 +75,9 @@
  */
 public class RecentsActivity extends Activity implements RecentsView.RecentsViewCallbacks {
 
+    private final static String TAG = "RecentsActivity";
+    private final static boolean DEBUG = false;
+
     public final static int EVENT_BUS_PRIORITY = Recents.EVENT_BUS_PRIORITY + 1;
 
     RecentsConfiguration mConfig;
@@ -96,6 +105,14 @@
     // Runnable to be executed after we paused ourselves
     Runnable mAfterPauseRunnable;
 
+    // The trigger to automatically launch the current task
+    DozeTrigger mIterateTrigger = new DozeTrigger(500, new Runnable() {
+        @Override
+        public void run() {
+            boolean dismissed = dismissRecentsToFocusedTask(false);
+        }
+    });
+
     /**
      * A common Runnable to finish Recents either by calling finish() (with a custom animation) or
      * launching Home with some ActivityOptions.  Generally we always launch home when we exit
@@ -244,7 +261,24 @@
         MetricsLogger.histogram(this, "overview_task_count", taskCount);
     }
 
-    /** Dismisses recents if we are already visible and the intent is to toggle the recents view */
+    /**
+     * Dismisses recents if we are already visible and the intent is to toggle the recents view.
+     */
+    boolean dismissRecentsToFocusedTask(boolean checkFilteredStackState) {
+        SystemServicesProxy ssp = Recents.getSystemServices();
+        if (ssp.isRecentsTopMost(ssp.getTopMostTask(), null)) {
+            // If we currently have filtered stacks, then unfilter those first
+            if (checkFilteredStackState &&
+                    mRecentsView.unfilterFilteredStacks()) return true;
+            // If we have a focused Task, launch that Task now
+            if (mRecentsView.launchFocusedTask()) return true;
+        }
+        return false;
+    }
+
+    /**
+     * Dismisses recents if we are already visible and the intent is to toggle the recents view.
+     */
     boolean dismissRecentsToFocusedTaskOrHome(boolean checkFilteredStackState) {
         RecentsActivityLaunchState launchState = mConfig.getLaunchState();
         SystemServicesProxy ssp = Recents.getSystemServices();
@@ -390,6 +424,11 @@
             mRecentsView.post(mAfterPauseRunnable);
             mAfterPauseRunnable = null;
         }
+
+        if (Constants.DebugFlags.App.EnableFastToggleRecents) {
+            // Stop the fast-toggle dozer
+            mIterateTrigger.stopDozing();
+        }
     }
 
     @Override
@@ -467,22 +506,27 @@
                 if (event.getRepeatCount() <= 0 || hasRepKeyTimeElapsed) {
                     // Focus the next task in the stack
                     final boolean backward = event.isShiftPressed();
-                    mRecentsView.focusNextTask(!backward);
+                    if (backward) {
+                        EventBus.getDefault().send(new FocusPreviousTaskViewEvent());
+                    } else {
+                        EventBus.getDefault().send(new FocusNextTaskViewEvent());
+                    }
                     mLastTabKeyEventTime = SystemClock.elapsedRealtime();
                 }
                 return true;
             }
             case KeyEvent.KEYCODE_DPAD_UP: {
-                mRecentsView.focusNextTask(true);
+                EventBus.getDefault().send(new FocusNextTaskViewEvent());
                 return true;
             }
             case KeyEvent.KEYCODE_DPAD_DOWN: {
-                mRecentsView.focusNextTask(false);
+                EventBus.getDefault().send(new FocusPreviousTaskViewEvent());
                 return true;
             }
             case KeyEvent.KEYCODE_DEL:
             case KeyEvent.KEYCODE_FORWARD_DEL: {
-                mRecentsView.dismissFocusedTask();
+                EventBus.getDefault().send(new DismissFocusedTaskViewEvent());
+
                 // Keep track of deletions by keyboard
                 MetricsLogger.histogram(this, "overview_task_dismissed_source",
                         Constants.Metrics.DismissSourceKeyboard);
@@ -542,6 +586,16 @@
         dismissRecentsToFocusedTaskOrHome(true /* checkFilteredStackState */);
     }
 
+    public final void onBusEvent(IterateRecentsEvent event) {
+        // Focus the next task
+        EventBus.getDefault().send(new FocusNextTaskViewEvent());
+        mIterateTrigger.poke();
+    }
+
+    public final void onBusEvent(UserInteractionEvent event) {
+        mIterateTrigger.stopDozing();
+    }
+
     public final void onBusEvent(HideRecentsEvent event) {
         if (event.triggeredFromAltTab) {
             // If we are hiding from releasing Alt-Tab, dismiss Recents to the focused app
@@ -558,7 +612,7 @@
         // Try and start the enter animation (or restart it on configuration changed)
         ReferenceCountedTrigger t = new ReferenceCountedTrigger(this, null, null, null);
         ViewAnimation.TaskViewEnterContext ctx = new ViewAnimation.TaskViewEnterContext(t);
-        mRecentsView.startEnterRecentsAnimation(ctx);
+        ctx.postAnimationTrigger.increment();
         if (mSearchWidgetInfo != null) {
             ctx.postAnimationTrigger.addLastDecrementRunnable(new Runnable() {
                 @Override
@@ -570,6 +624,20 @@
                 }
             });
         }
+        ctx.postAnimationTrigger.addLastDecrementRunnable(new Runnable() {
+            @Override
+            public void run() {
+                // If we are not launching with alt-tab and fast-toggle is enabled, then start
+                // the dozer now
+                RecentsActivityLaunchState launchState = mConfig.getLaunchState();
+                if (Constants.DebugFlags.App.EnableFastToggleRecents &&
+                        !launchState.launchedWithAltTab) {
+                    mIterateTrigger.startDozing();
+                }
+            }
+        });
+        mRecentsView.startEnterRecentsAnimation(ctx);
+        ctx.postAnimationTrigger.decrement();
     }
 
     public final void onBusEvent(AppWidgetProviderChangedEvent event) {
@@ -589,7 +657,7 @@
         MetricsLogger.count(this, "overview_app_info", 1);
     }
 
-    public final void onBusEvent(DismissTaskEvent event) {
+    public final void onBusEvent(DismissTaskViewEvent event) {
         // Remove any stored data from the loader
         RecentsTaskLoader loader = Recents.getTaskLoader();
         loader.deleteTaskData(event.task, false);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java
index e2e0e918..76b666f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java
@@ -56,7 +56,7 @@
 
     /** Returns whether the status bar scrim should be animated when shown for the first time. */
     public boolean shouldAnimateStatusBarScrim() {
-        return launchedFromHome;
+        return true;
     }
 
     /** Returns whether the status bar scrim should be visible. */
@@ -72,6 +72,6 @@
     /** Returns whether the nav bar scrim should be visible. */
     public boolean hasNavBarScrim() {
         // Only show the scrim if we have recent tasks, and if the nav bar is not transposed
-        return !launchedWithNoRecentTasks && mConfig.hasTransposedNavBar;
+        return !launchedWithNoRecentTasks && !mConfig.hasTransposedNavBar;
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
index b07d23c..6a0fc4f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
@@ -40,6 +40,7 @@
 import com.android.systemui.recents.events.EventBus;
 import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationStartedEvent;
 import com.android.systemui.recents.events.activity.HideRecentsEvent;
+import com.android.systemui.recents.events.activity.IterateRecentsEvent;
 import com.android.systemui.recents.events.activity.ToggleRecentsEvent;
 import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
 import com.android.systemui.recents.events.component.ScreenPinningRequestEvent;
@@ -265,26 +266,38 @@
         mTriggeredFromAltTab = false;
 
         try {
-            // If the user has toggled it too quickly, then just eat up the event here (it's better
-            // than showing a janky screenshot).
-            // NOTE: Ideally, the screenshot mechanism would take the window transform into account
-            if ((SystemClock.elapsedRealtime() - mLastToggleTime) < sMinToggleDelay) {
-                return;
-            }
-
-            // If Recents is the front most activity, then we should just communicate with it
-            // directly to launch the first task or dismiss itself
             SystemServicesProxy ssp = Recents.getSystemServices();
             ActivityManager.RunningTaskInfo topTask = ssp.getTopMostTask();
             MutableBoolean isTopTaskHome = new MutableBoolean(true);
             if (topTask != null && ssp.isRecentsTopMost(topTask, isTopTaskHome)) {
-                // Notify recents to toggle itself
-                EventBus.getDefault().post(new ToggleRecentsEvent());
-                mLastToggleTime = SystemClock.elapsedRealtime();
+                if (Constants.DebugFlags.App.EnableFastToggleRecents) {
+                    // Notify recents to move onto the next task
+                    EventBus.getDefault().post(new IterateRecentsEvent());
+                } else {
+                    // If the user has toggled it too quickly, then just eat up the event here (it's
+                    // better than showing a janky screenshot).
+                    // NOTE: Ideally, the screenshot mechanism would take the window transform into
+                    // account
+                    if ((SystemClock.elapsedRealtime() - mLastToggleTime) < sMinToggleDelay) {
+                        return;
+                    }
+
+                    EventBus.getDefault().post(new ToggleRecentsEvent());
+                    mLastToggleTime = SystemClock.elapsedRealtime();
+                }
                 return;
             } else {
+                // If the user has toggled it too quickly, then just eat up the event here (it's
+                // better than showing a janky screenshot).
+                // NOTE: Ideally, the screenshot mechanism would take the window transform into
+                // account
+                if ((SystemClock.elapsedRealtime() - mLastToggleTime) < sMinToggleDelay) {
+                    return;
+                }
+
                 // Otherwise, start the recents activity
                 startRecentsActivity(topTask, isTopTaskHome.value);
+                mLastToggleTime = SystemClock.elapsedRealtime();
             }
         } catch (ActivityNotFoundException e) {
             Console.logRawError("Failed to launch RecentAppsIntent", e);
@@ -333,7 +346,7 @@
         // Return early if there is no running task (can't determine affiliated tasks in this case)
         if (runningTask == null) return;
         // Return early if the running task is in the home stack (optimization)
-        if (ssp.isInHomeStack(runningTask.id)) return;
+        if (SystemServicesProxy.isHomeStack(runningTask.stackId)) return;
 
         // Find the task in the recents list
         ArrayList<Task> tasks = focusedStack.getTasks();
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/EventBus.java b/packages/SystemUI/src/com/android/systemui/recents/events/EventBus.java
index fec0fc5..deae4c8 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/EventBus.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/EventBus.java
@@ -200,7 +200,8 @@
  */
 public class EventBus extends BroadcastReceiver {
 
-    public static final String TAG = "EventBus";
+    private static final String TAG = "EventBus";
+    private static final boolean DEBUG_TRACE_ALL = false;
 
     /**
      * An event super class that allows us to track internal event state across subscriber
@@ -277,9 +278,6 @@
     // The default priority of all subscribers
     private static final int DEFAULT_SUBSCRIBER_PRIORITY = 1;
 
-    // Used for debugging everything
-    private static final boolean DEBUG_TRACE_ALL = false;
-
     // Orders the handlers by priority and registration time
     private static final Comparator<EventHandler> EVENT_HANDLER_COMPARATOR = new Comparator<EventHandler>() {
         @Override
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/activity/IterateRecentsEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/activity/IterateRecentsEvent.java
new file mode 100644
index 0000000..f7b2706
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/activity/IterateRecentsEvent.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents.events.activity;
+
+import com.android.systemui.recents.events.EventBus;
+
+/**
+ * This is sent when the user taps on the Overview button to iterate to the next item in the
+ * Recents list.
+ */
+public class IterateRecentsEvent extends EventBus.Event {
+    // Simple event
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/DismissTaskEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/DismissTaskViewEvent.java
similarity index 89%
rename from packages/SystemUI/src/com/android/systemui/recents/events/ui/DismissTaskEvent.java
rename to packages/SystemUI/src/com/android/systemui/recents/events/ui/DismissTaskViewEvent.java
index 12e5d3d..968890a 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/ui/DismissTaskEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/DismissTaskViewEvent.java
@@ -23,12 +23,12 @@
 /**
  * This is sent when a {@link TaskView} has been dismissed.
  */
-public class DismissTaskEvent extends EventBus.Event {
+public class DismissTaskViewEvent extends EventBus.Event {
 
     public final Task task;
     public final TaskView taskView;
 
-    public DismissTaskEvent(Task task, TaskView taskView) {
+    public DismissTaskViewEvent(Task task, TaskView taskView) {
         this.task = task;
         this.taskView = taskView;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/focus/DismissFocusedTaskViewEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/focus/DismissFocusedTaskViewEvent.java
new file mode 100644
index 0000000..9f3e9d5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/focus/DismissFocusedTaskViewEvent.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents.events.ui.focus;
+
+import com.android.systemui.recents.events.EventBus;
+
+/**
+ * Dismisses the currently focused task view.
+ */
+public class DismissFocusedTaskViewEvent extends EventBus.Event {
+    // Simple event
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/focus/FocusNextTaskViewEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/focus/FocusNextTaskViewEvent.java
new file mode 100644
index 0000000..171ab5e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/focus/FocusNextTaskViewEvent.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents.events.ui.focus;
+
+import com.android.systemui.recents.events.EventBus;
+
+/**
+ * Focuses the next task view in the stack.
+ */
+public class FocusNextTaskViewEvent extends EventBus.Event {
+    // Simple event
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/focus/FocusPreviousTaskViewEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/focus/FocusPreviousTaskViewEvent.java
new file mode 100644
index 0000000..22469e7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/focus/FocusPreviousTaskViewEvent.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents.events.ui.focus;
+
+import com.android.systemui.recents.events.EventBus;
+
+/**
+ * Focuses the previous task view in the stack.
+ */
+public class FocusPreviousTaskViewEvent extends EventBus.Event {
+    // Simple event
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/DozeTrigger.java b/packages/SystemUI/src/com/android/systemui/recents/misc/DozeTrigger.java
index 735f79f..336d2db 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/DozeTrigger.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/DozeTrigger.java
@@ -19,8 +19,8 @@
 import android.os.Handler;
 
 /**
- * A dozer is a class that fires a trigger after it falls asleep.  You can occasionally poke it to
- * wake it up, but it will fall asleep if left untouched.
+ * A dozer is a class that fires a trigger after it falls asleep.
+ * You can occasionally poke the trigger to wake it up, but it will fall asleep if left untouched.
  */
 public class DozeTrigger {
 
@@ -28,7 +28,7 @@
 
     boolean mIsDozing;
     boolean mHasTriggered;
-    int mDozeDurationSeconds;
+    int mDozeDurationMilliseconds;
     Runnable mSleepRunnable;
 
     // Sleep-runnable
@@ -41,9 +41,9 @@
         }
     };
 
-    public DozeTrigger(int dozeDurationSeconds, Runnable sleepRunnable) {
+    public DozeTrigger(int dozeDurationMilliseconds, Runnable sleepRunnable) {
         mHandler = new Handler();
-        mDozeDurationSeconds = dozeDurationSeconds;
+        mDozeDurationMilliseconds = dozeDurationMilliseconds;
         mSleepRunnable = sleepRunnable;
     }
 
@@ -69,7 +69,7 @@
     /** Poke this dozer to wake it up for a little bit. */
     void forcePoke() {
         mHandler.removeCallbacks(mDozeRunnable);
-        mHandler.postDelayed(mDozeRunnable, mDozeDurationSeconds * 1000);
+        mHandler.postDelayed(mDozeRunnable, mDozeDurationMilliseconds);
         mIsDozing = true;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
index 0b01538..141562c 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -247,8 +247,10 @@
                 return true;
             }
 
+            // Note, this is only valid because we currently only allow the recents and home
+            // activities in the home stack
             if (isHomeTopMost != null) {
-                isHomeTopMost.value = isInHomeStack(topTask.id);
+                isHomeTopMost.value = SystemServicesProxy.isHomeStack(topTask.stackId);
             }
         }
         return false;
@@ -304,16 +306,18 @@
         }
     }
 
-    /** Returns whether the specified task is in the home stack */
-    public boolean isInHomeStack(int taskId) {
-        if (mAm == null) return false;
+    /**
+     * Returns whether the given stack id is the home stack id.
+     */
+    public static boolean isHomeStack(int stackId) {
+        return stackId == ActivityManager.HOME_STACK_ID;
+    }
 
-        // If we are mocking, then just return false
-        if (Constants.DebugFlags.App.EnableSystemServicesProxy) {
-            return false;
-        }
-
-        return mAm.isInHomeStack(taskId);
+    /**
+     * Returns whether the given stack id is the freeform workspace stack id.
+     */
+    public static boolean isFreeformStack(int stackId) {
+        return stackId == ActivityManager.FREEFORM_WORKSPACE_STACK_ID;
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
index 98e9c75..d5d0713 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -49,7 +49,7 @@
 import com.android.systemui.recents.events.EventBus;
 import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimationStarted;
 import com.android.systemui.recents.events.component.ScreenPinningRequestEvent;
-import com.android.systemui.recents.events.ui.DismissTaskEvent;
+import com.android.systemui.recents.events.ui.DismissTaskViewEvent;
 import com.android.systemui.recents.events.ui.dragndrop.DragDockStateChangedEvent;
 import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
 import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent;
@@ -69,6 +69,7 @@
 public class RecentsView extends FrameLayout implements TaskStackView.TaskStackViewCallbacks {
 
     private static final String TAG = "RecentsView";
+    private static final boolean DEBUG = false;
 
     private static final boolean ADD_HEADER_BITMAP = true;
 
@@ -404,22 +405,6 @@
         return super.verifyDrawable(who);
     }
 
-    /** Focuses the next task in the first stack view */
-    public void focusNextTask(boolean forward) {
-        // Get the first stack view
-        if (mTaskStackView != null) {
-            mTaskStackView.focusNextTask(forward, true);
-        }
-    }
-
-    /** Dismisses the focused task. */
-    public void dismissFocusedTask() {
-        // Get the first stack view
-        if (mTaskStackView != null) {
-            mTaskStackView.dismissFocusedTask();
-        }
-    }
-
     /** Unfilters any filtered stacks */
     public boolean unfilterFilteredStacks() {
         if (mStacks != null) {
@@ -562,7 +547,7 @@
         // Disable any focused state before we draw the header
         // Upfront the processing of the thumbnail
         if (tv.isFocusedTask()) {
-            tv.unsetFocusedTask();
+            tv.setFocusedState(false, false /* animated */, false /* requestViewFocus */);
         }
         TaskViewTransform transform = new TaskViewTransform();
         transform = stackView.getStackAlgorithm().getStackTransform(tv.mTask, stackScroll,
@@ -682,7 +667,7 @@
                     } else {
                         // Dismiss the task and return the user to home if we fail to
                         // launch the task
-                        EventBus.getDefault().send(new DismissTaskEvent(task, tv));
+                        EventBus.getDefault().send(new DismissTaskViewEvent(task, tv));
                         if (mCb != null) {
                             mCb.onTaskLaunchFailed();
                         }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
index 5928854..757e695 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -23,6 +23,8 @@
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.os.Bundle;
+import android.os.SystemService;
+import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
@@ -30,6 +32,7 @@
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.FrameLayout;
 import com.android.systemui.R;
+import com.android.systemui.recents.Constants;
 import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.RecentsActivity;
 import com.android.systemui.recents.RecentsActivityLaunchState;
@@ -37,8 +40,11 @@
 import com.android.systemui.recents.events.EventBus;
 import com.android.systemui.recents.events.activity.PackagesChangedEvent;
 import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
-import com.android.systemui.recents.events.ui.DismissTaskEvent;
+import com.android.systemui.recents.events.ui.DismissTaskViewEvent;
 import com.android.systemui.recents.events.ui.UserInteractionEvent;
+import com.android.systemui.recents.events.ui.focus.DismissFocusedTaskViewEvent;
+import com.android.systemui.recents.events.ui.focus.FocusNextTaskViewEvent;
+import com.android.systemui.recents.events.ui.focus.FocusPreviousTaskViewEvent;
 import com.android.systemui.recents.misc.DozeTrigger;
 import com.android.systemui.recents.misc.SystemServicesProxy;
 import com.android.systemui.recents.misc.Utilities;
@@ -60,6 +66,9 @@
         TaskView.TaskViewCallbacks, TaskStackViewScroller.TaskStackViewScrollerCallbacks,
         ViewPool.ViewPoolConsumer<TaskView, Task> {
 
+    private final static String TAG = "TaskStackView";
+    private final static boolean DEBUG = false;
+
     /** The TaskView callbacks */
     interface TaskStackViewCallbacks {
         public void onTaskViewClicked(TaskStackView stackView, TaskView tv, TaskStack stack, Task t,
@@ -80,14 +89,12 @@
     ArrayList<TaskViewTransform> mCurrentTaskTransforms = new ArrayList<>();
     DozeTrigger mUIDozeTrigger;
     int mFocusedTaskIndex = -1;
-    int mPrevAccessibilityFocusedIndex = -1;
     // Optimizations
     int mStackViewsAnimationDuration;
     boolean mStackViewsDirty = true;
     boolean mStackViewsClipDirty = true;
     boolean mAwaitingFirstLayout = true;
     boolean mStartEnterAnimationRequestedAfterLayout;
-    boolean mStartEnterAnimationCompleted;
     ViewAnimation.TaskViewEnterContext mStartEnterAnimationContext;
 
     Rect mTaskStackBounds = new Rect();
@@ -219,7 +226,6 @@
         mStackViewsDirty = true;
         mStackViewsClipDirty = true;
         mAwaitingFirstLayout = true;
-        mPrevAccessibilityFocusedIndex = -1;
         if (mUIDozeTrigger != null) {
             mUIDozeTrigger.stopDozing();
             mUIDozeTrigger.resetTrigger();
@@ -332,8 +338,6 @@
     /** Synchronizes the views with the model */
     boolean synchronizeStackViewsWithModel() {
         if (mStackViewsDirty) {
-            SystemServicesProxy ssp = Recents.getSystemServices();
-
             // Get all the task transforms
             ArrayList<Task> tasks = mStack.getTasks();
             float stackScroll = mStackScroller.getStackScroll();
@@ -344,8 +348,9 @@
             // Return all the invisible children to the pool
             mTmpTaskViewMap.clear();
             List<TaskView> taskViews = getTaskViews();
+            boolean wasLastFocusedTaskAnimated = false;
+            int lastFocusedTaskIndex = -1;
             int taskViewCount = taskViews.size();
-            boolean reaquireAccessibilityFocus = false;
             for (int i = taskViewCount - 1; i >= 0; i--) {
                 TaskView tv = taskViews.get(i);
                 Task task = tv.getTask();
@@ -353,8 +358,12 @@
                 if (visibleRange[1] <= taskIndex && taskIndex <= visibleRange[0]) {
                     mTmpTaskViewMap.put(task, tv);
                 } else {
+                    if (tv.isFocusedTask()) {
+                        wasLastFocusedTaskAnimated = tv.isFocusAnimated();
+                        lastFocusedTaskIndex = taskIndex;
+                        resetFocusedTask();
+                    }
                     mViewPool.returnViewToPool(tv);
-                    reaquireAccessibilityFocus |= (i == mPrevAccessibilityFocusedIndex);
                 }
             }
 
@@ -385,21 +394,14 @@
                 // Animate the task into place
                 tv.updateViewPropertiesToTaskTransform(mCurrentTaskTransforms.get(taskIndex),
                         mStackViewsAnimationDuration, mRequestUpdateClippingListener);
+            }
 
-                // Request accessibility focus on the next view if we removed the task
-                // that previously held accessibility focus
-                if (reaquireAccessibilityFocus) {
-                    taskViews = getTaskViews();
-                    taskViewCount = taskViews.size();
-                    if (taskViewCount > 0 && ssp.isTouchExplorationEnabled() &&
-                            mPrevAccessibilityFocusedIndex != -1) {
-                        TaskView atv = taskViews.get(taskViewCount - 1);
-                        int indexOfTask = mStack.indexOfTask(atv.getTask());
-                        if (mPrevAccessibilityFocusedIndex != indexOfTask) {
-                            tv.requestAccessibilityFocus();
-                            mPrevAccessibilityFocusedIndex = indexOfTask;
-                        }
-                    }
+            // Update the focus if the previous focused task was returned to the view pool
+            if (lastFocusedTaskIndex != -1) {
+                if (lastFocusedTaskIndex < visibleRange[1]) {
+                    setFocusedTask(visibleRange[1], false, wasLastFocusedTaskAnimated);
+                } else {
+                    setFocusedTask(visibleRange[0], false, wasLastFocusedTaskAnimated);
                 }
             }
 
@@ -473,118 +475,80 @@
         return mStackScroller;
     }
 
-    /** Focuses the task at the specified index in the stack */
-    void focusTask(int taskIndex, boolean scrollToNewPosition, final boolean animateFocusedState) {
-        // Return early if the task is already focused
-        if (taskIndex == mFocusedTaskIndex) return;
+    /**
+     * Sets the focused task to the provided (bounded taskIndex).
+     */
+    private void setFocusedTask(int taskIndex, boolean scrollToTask, final boolean animated) {
+        setFocusedTask(taskIndex, scrollToTask, animated, true);
+    }
 
-        if (0 <= taskIndex && taskIndex < mStack.getTaskCount()) {
-            mFocusedTaskIndex = taskIndex;
-            mPrevAccessibilityFocusedIndex = taskIndex;
+    /**
+     * Sets the focused task to the provided (bounded taskIndex).
+     */
+    private void setFocusedTask(int taskIndex, boolean scrollToTask, final boolean animated,
+                                final boolean requestViewFocus) {
+        // Find the next task to focus
+        int newFocusedTaskIndex = mStack.getTaskCount() > 0 ?
+                Math.max(0, Math.min(mStack.getTaskCount() - 1, taskIndex)) : -1;
+        final Task newFocusedTask = (newFocusedTaskIndex != -1) ?
+                mStack.getTasks().get(newFocusedTaskIndex) : null;
 
-            // Focus the view if possible, otherwise, focus the view after we scroll into position
-            final Task t = mStack.getTasks().get(mFocusedTaskIndex);
-            Runnable postScrollRunnable = new Runnable() {
+        // Reset the last focused task state if changed
+        if (mFocusedTaskIndex != -1) {
+            Task focusedTask = mStack.getTasks().get(mFocusedTaskIndex);
+            if (focusedTask != newFocusedTask) {
+                resetFocusedTask();
+            }
+        }
+
+        mFocusedTaskIndex = newFocusedTaskIndex;
+        if (mFocusedTaskIndex != -1) {
+            Runnable focusTaskRunnable = new Runnable() {
                 @Override
                 public void run() {
-                    TaskView tv = getChildViewForTask(t);
+                    TaskView tv = getChildViewForTask(newFocusedTask);
                     if (tv != null) {
-                        tv.setFocusedTask(animateFocusedState);
-                        tv.requestAccessibilityFocus();
+                        tv.setFocusedState(true, animated, requestViewFocus);
                     }
                 }
             };
 
-            // Scroll the view into position (just center it in the curve)
-            if (scrollToNewPosition) {
-                float newScroll = mLayoutAlgorithm.getStackScrollForTask(t) - 0.5f;
+            if (scrollToTask) {
+                // TODO: Center the newly focused task view
+                float newScroll = mLayoutAlgorithm.getStackScrollForTask(newFocusedTask) - 0.5f;
                 newScroll = mStackScroller.getBoundedStackScroll(newScroll);
-                mStackScroller.animateScroll(mStackScroller.getStackScroll(), newScroll, postScrollRunnable);
+                mStackScroller.animateScroll(mStackScroller.getStackScroll(), newScroll,
+                        focusTaskRunnable);
             } else {
-                if (postScrollRunnable != null) {
-                    postScrollRunnable.run();
-                }
+                focusTaskRunnable.run();
             }
-
         }
     }
 
     /**
-     * Ensures that there is a task focused, if nothing is focused, then we will use the task
-     * at the center of the visible stack.
+     * Sets the focused task relative to the currently focused task.
+     *
+     * @param animated determines whether to actually draw the highlight along with the change in
+     *                            focus.
      */
-    public boolean ensureFocusedTask(boolean findClosestToCenter) {
-        if (mFocusedTaskIndex < 0) {
-            List<TaskView> taskViews = getTaskViews();
-            int taskViewCount = taskViews.size();
-            if (findClosestToCenter) {
-                // If there is no task focused, then find the task that is closes to the center
-                // of the screen and use that as the currently focused task
-                int x = mLayoutAlgorithm.mStackRect.centerX();
-                int y = mLayoutAlgorithm.mStackRect.centerY();
-                for (int i = taskViewCount - 1; i >= 0; i--) {
-                    TaskView tv = taskViews.get(i);
-                    tv.getHitRect(mTmpRect);
-                    if (mTmpRect.contains(x, y)) {
-                        mFocusedTaskIndex = mStack.indexOfTask(tv.getTask());
-                        mPrevAccessibilityFocusedIndex = mFocusedTaskIndex;
-                        break;
-                    }
-                }
-            }
-            // If we can't find the center task, then use the front most index
-            if (mFocusedTaskIndex < 0 && taskViewCount > 0) {
-                TaskView tv = taskViews.get(taskViewCount - 1);
-                mFocusedTaskIndex = mStack.indexOfTask(tv.getTask());
-                mPrevAccessibilityFocusedIndex = mFocusedTaskIndex;
-            }
-        }
-        return mFocusedTaskIndex >= 0;
-    }
-
-    /**
-     * Focuses the next task in the stack.
-     * @param animateFocusedState determines whether to actually draw the highlight along with
-     *                            the change in focus, as well as whether to scroll to fit the
-     *                            task into view.
-     */
-    public void focusNextTask(boolean forward, boolean animateFocusedState) {
+    public void setRelativeFocusedTask(boolean forward, boolean animated) {
         // Find the next index to focus
-        int numTasks = mStack.getTaskCount();
-        if (numTasks == 0) return;
-
-        int direction = (forward ? -1 : 1);
-        int newIndex = mFocusedTaskIndex + direction;
-        if (newIndex >= 0 && newIndex <= (numTasks - 1)) {
-            newIndex = Math.max(0, Math.min(numTasks - 1, newIndex));
-            focusTask(newIndex, true, animateFocusedState);
-        }
+        int newIndex = mFocusedTaskIndex + (forward ? -1 : 1);
+        setFocusedTask(newIndex, true, animated);
     }
 
-    /** Dismisses the focused task. */
-    public void dismissFocusedTask() {
-        // Return early if the focused task index is invalid
-        if (mFocusedTaskIndex < 0 || mFocusedTaskIndex >= mStack.getTaskCount()) {
-            mFocusedTaskIndex = -1;
-            return;
-        }
-
-        Task t = mStack.getTasks().get(mFocusedTaskIndex);
-        TaskView tv = getChildViewForTask(t);
-        tv.dismissTask();
-    }
-
-    /** Resets the focused task. */
+    /**
+     * Resets the focused task.
+     */
     void resetFocusedTask() {
-        if ((0 <= mFocusedTaskIndex) && (mFocusedTaskIndex < mStack.getTaskCount())) {
+        if (mFocusedTaskIndex != -1) {
             Task t = mStack.getTasks().get(mFocusedTaskIndex);
             TaskView tv = getChildViewForTask(t);
             if (tv != null) {
-                tv.unsetFocusedTask();
+                tv.setFocusedState(false, false /* animated */, false /* requestViewFocus */);
             }
         }
         mFocusedTaskIndex = -1;
-        mPrevAccessibilityFocusedIndex = -1;
     }
 
     @Override
@@ -609,12 +573,12 @@
         super.onInitializeAccessibilityNodeInfo(info);
         List<TaskView> taskViews = getTaskViews();
         int taskViewCount = taskViews.size();
-        if (taskViewCount > 1 && mPrevAccessibilityFocusedIndex != -1) {
+        if (taskViewCount > 1 && mFocusedTaskIndex != -1) {
             info.setScrollable(true);
-            if (mPrevAccessibilityFocusedIndex > 0) {
+            if (mFocusedTaskIndex > 0) {
                 info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
             }
-            if (mPrevAccessibilityFocusedIndex < mStack.getTaskCount() - 1) {
+            if (mFocusedTaskIndex < mStack.getTaskCount() - 1) {
                 info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
             }
         }
@@ -630,22 +594,14 @@
         if (super.performAccessibilityAction(action, arguments)) {
             return true;
         }
-        if (ensureFocusedTask(false)) {
-            switch (action) {
-                case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: {
-                    if (mPrevAccessibilityFocusedIndex > 0) {
-                        focusNextTask(true, false);
-                        return true;
-                    }
-                }
-                break;
-                case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: {
-                    if (mPrevAccessibilityFocusedIndex < mStack.getTaskCount() - 1) {
-                        focusNextTask(false, false);
-                        return true;
-                    }
-                }
-                break;
+        switch (action) {
+            case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: {
+                setRelativeFocusedTask(true, false /* animated */);
+                return true;
+            }
+            case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: {
+                setRelativeFocusedTask(false, false /* animated */);
+                return true;
             }
         }
         return false;
@@ -678,7 +634,7 @@
 
     /** Computes the stack and task rects */
     public void computeRects(int windowWidth, int windowHeight, Rect taskStackBounds,
-            boolean launchedWithAltTab, boolean launchedFromHome) {
+                             boolean launchedWithAltTab, boolean launchedFromHome) {
         // Compute the rects in the stack algorithm
         mLayoutAlgorithm.computeRects(windowWidth, windowHeight, taskStackBounds);
 
@@ -741,12 +697,12 @@
                 mTmpRect.setEmpty();
             }
             tv.measure(
-                MeasureSpec.makeMeasureSpec(
-                        mLayoutAlgorithm.mTaskRect.width() + mTmpRect.left + mTmpRect.right,
-                        MeasureSpec.EXACTLY),
-                MeasureSpec.makeMeasureSpec(
-                        mLayoutAlgorithm.mTaskRect.height() + mTmpRect.top + mTmpRect.bottom,
-                        MeasureSpec.EXACTLY));
+                    MeasureSpec.makeMeasureSpec(
+                            mLayoutAlgorithm.mTaskRect.width() + mTmpRect.left + mTmpRect.right,
+                            MeasureSpec.EXACTLY),
+                    MeasureSpec.makeMeasureSpec(
+                            mLayoutAlgorithm.mTaskRect.height() + mTmpRect.top + mTmpRect.bottom,
+                            MeasureSpec.EXACTLY));
         }
 
         setMeasuredDimension(width, height);
@@ -815,17 +771,16 @@
             mStartEnterAnimationContext = null;
         }
 
-        // When Alt-Tabbing, focus the previous task (but leave the animation until we finish the
-        // enter animation).
-        RecentsActivityLaunchState launchState = mConfig.getLaunchState();
-        if (launchState.launchedWithAltTab) {
-            if (launchState.launchedFromAppWithThumbnail) {
-                focusTask(Math.max(0, mStack.getTaskCount() - 2), false,
-                        launchState.launchedHasConfigurationChanged);
-            } else {
-                focusTask(Math.max(0, mStack.getTaskCount() - 1), false,
-                        launchState.launchedHasConfigurationChanged);
-            }
+        // Set the task focused state without requesting view focus, and leave the focus animations
+        // until after the enter-animation
+        if (!Constants.DebugFlags.App.EnableFastToggleRecents && launchTargetTask != null) {
+            setFocusedTask(mStack.indexOfTask(launchTargetTask), false /* scrollToTask */,
+                    false /* animated */, false /* requestViewFocus */);
+        } else {
+            RecentsActivityLaunchState launchState = mConfig.getLaunchState();
+            int taskOffset = launchState.launchedFromHome ? -1 : -2;
+            setFocusedTask(mStack.getTaskCount() + taskOffset, false /* scrollToTask */,
+                    false /* animated */, false /* requestViewFocus */);
         }
 
         // Start dozing
@@ -874,32 +829,17 @@
             ctx.postAnimationTrigger.addLastDecrementRunnable(new Runnable() {
                 @Override
                 public void run() {
-                    mStartEnterAnimationCompleted = true;
                     // Poke the dozer to restart the trigger after the animation completes
                     mUIDozeTrigger.poke();
 
-                    SystemServicesProxy ssp = Recents.getSystemServices();
-                    List<TaskView> taskViews = getTaskViews();
-                    int taskViewCount = taskViews.size();
-                    if (taskViewCount > 0) {
-                        // Focus the first view if accessibility is enabled
-                        if (ssp.isTouchExplorationEnabled()) {
-                            TaskView tv = taskViews.get(taskViewCount - 1);
-                            tv.requestAccessibilityFocus();
-                            mPrevAccessibilityFocusedIndex = mStack.indexOfTask(tv.getTask());
-                        }
-                    }
-
-                    // Start the focus animation when alt-tabbing
-                    ArrayList<Task> tasks = mStack.getTasks();
-                    RecentsActivityLaunchState launchState = mConfig.getLaunchState();
-                    if (launchState.launchedWithAltTab &&
-                            !launchState.launchedHasConfigurationChanged &&
-                            0 <= mFocusedTaskIndex && mFocusedTaskIndex < tasks.size()) {
-                        TaskView tv = getChildViewForTask(tasks.get(mFocusedTaskIndex));
-                        if (tv != null) {
-                            tv.setFocusedTask(true);
-                        }
+                    // Update the focused state here -- since we only set the focused task without
+                    // requesting view focus in onFirstLayout(), actually request view focus and
+                    // animate the focused state if we are alt-tabbing now, after the window enter
+                    // animation is completed
+                    if (mFocusedTaskIndex != -1) {
+                        RecentsActivityLaunchState launchState = mConfig.getLaunchState();
+                        setFocusedTask(mFocusedTaskIndex, false /* scrollToTask */,
+                                launchState.launchedWithAltTab);
                     }
                 }
             });
@@ -1132,11 +1072,6 @@
     public void prepareViewToEnterPool(TaskView tv) {
         Task task = tv.getTask();
 
-        // Clear the accessibility focus for that view
-        if (tv.isAccessibilityFocused()) {
-            tv.clearAccessibilityFocus();
-        }
-
         // Report that this tasks's data is no longer being used
         Recents.getTaskLoader().unloadTaskData(task);
 
@@ -1167,11 +1102,6 @@
         // If the doze trigger has already fired, then update the state for this task view
         tv.setNoUserInteractionState();
 
-        // If we've finished the start animation, then ensure we always enable the focus animations
-        if (mStartEnterAnimationCompleted) {
-            tv.enableFocusAnimations();
-        }
-
         // Find the index where this task should be placed in the stack
         int insertIndex = -1;
         int taskIndex = mStack.indexOfTask(task);
@@ -1231,13 +1161,6 @@
         }
     }
 
-    @Override
-    public void onTaskViewFocusChanged(TaskView tv, boolean focused) {
-        if (focused) {
-            mFocusedTaskIndex = mStack.indexOfTask(tv.getTask());
-        }
-    }
-
     /**** TaskStackViewScroller.TaskStackViewScrollerCallbacks ****/
 
     @Override
@@ -1259,13 +1182,13 @@
         for (int i = tasks.size() - 1; i >= 0; i--) {
             final Task t = tasks.get(i);
             if (removedComponents.contains(t.key.getComponent())) {
-                TaskView tv = getChildViewForTask(t);
+                final TaskView tv = getChildViewForTask(t);
                 if (tv != null) {
                     // For visible children, defer removing the task until after the animation
                     tv.startDeleteTaskAnimation(new Runnable() {
                         @Override
                         public void run() {
-                            mStack.removeTask(t);
+                            removeTaskViewFromStack(tv);
                         }
                     }, 0);
                 } else {
@@ -1276,33 +1199,23 @@
         }
     }
 
-    public final void onBusEvent(DismissTaskEvent event) {
-        TaskView tv = event.taskView;
-        Task task = tv.getTask();
-        int taskIndex = mStack.indexOfTask(task);
-        boolean taskWasFocused = tv.isFocusedTask();
+    public final void onBusEvent(DismissTaskViewEvent event) {
+        removeTaskViewFromStack(event.taskView);
+    }
 
-        // Announce for accessibility
-        tv.announceForAccessibility(getContext().getString(R.string.accessibility_recents_item_dismissed,
-                tv.getTask().activityLabel));
+    public final void onBusEvent(FocusNextTaskViewEvent event) {
+        setRelativeFocusedTask(true, true);
+    }
 
-        // Remove the task from the view
-        mStack.removeTask(task);
+    public final void onBusEvent(FocusPreviousTaskViewEvent event) {
+        setRelativeFocusedTask(false, true);
+    }
 
-        // If the dismissed task was focused, then we should focus the new task in the same index
-        if (taskWasFocused) {
-            ArrayList<Task> tasks = mStack.getTasks();
-            int nextTaskIndex = Math.min(tasks.size() - 1, taskIndex - 1);
-            if (nextTaskIndex >= 0) {
-                Task nextTask = tasks.get(nextTaskIndex);
-                TaskView nextTv = getChildViewForTask(nextTask);
-                if (nextTv != null) {
-                    // Focus the next task, and only animate the visible state if we are launched
-                    // from Alt-Tab
-                    RecentsActivityLaunchState launchState = mConfig.getLaunchState();
-                    nextTv.setFocusedTask(launchState.launchedWithAltTab);
-                }
-            }
+    public final void onBusEvent(DismissFocusedTaskViewEvent event) {
+        if (mFocusedTaskIndex != -1) {
+            Task t = mStack.getTasks().get(mFocusedTaskIndex);
+            TaskView tv = getChildViewForTask(t);
+            tv.dismissTask();
         }
     }
 
@@ -1316,4 +1229,32 @@
             reset();
         }
     }
+
+    /**
+     * Removes the task from the stack, and updates the focus to the next task in the stack if the
+     * removed TaskView was focused.
+     */
+    private void removeTaskViewFromStack(TaskView tv) {
+        SystemServicesProxy ssp = Recents.getSystemServices();
+        Task task = tv.getTask();
+        int taskIndex = mStack.indexOfTask(task);
+        boolean taskWasFocused = tv.isFocusedTask();
+
+        // Reset the previously focused task before it is removed from the stack
+        resetFocusedTask();
+
+        // Announce for accessibility
+        tv.announceForAccessibility(getContext().getString(
+                R.string.accessibility_recents_item_dismissed, tv.getTask().activityLabel));
+
+        // Remove the task from the stack
+        mStack.removeTask(task);
+
+        if (taskWasFocused || ssp.isTouchExplorationEnabled()) {
+            // If the dismissed task was focused or if we are in touch exploration mode, then focus
+            // the next task
+            RecentsActivityLaunchState launchState = mConfig.getLaunchState();
+            setFocusedTask(taskIndex - 1, true /* scrollToTask */, launchState.launchedWithAltTab);
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
index 1274318..3a1a987 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
@@ -28,7 +28,7 @@
 import com.android.systemui.recents.Constants;
 import com.android.systemui.recents.events.EventBus;
 import com.android.systemui.recents.events.activity.HideRecentsEvent;
-import com.android.systemui.recents.events.ui.DismissTaskEvent;
+import com.android.systemui.recents.events.ui.DismissTaskViewEvent;
 
 import java.util.List;
 
@@ -408,13 +408,9 @@
                     // Find the front most task and scroll the next task to the front
                     float vScroll = ev.getAxisValue(MotionEvent.AXIS_VSCROLL);
                     if (vScroll > 0) {
-                        if (mSv.ensureFocusedTask(true)) {
-                            mSv.focusNextTask(true, false);
-                        }
+                        mSv.setRelativeFocusedTask(true, false /* animated */);
                     } else {
-                        if (mSv.ensureFocusedTask(true)) {
-                            mSv.focusNextTask(false, false);
-                        }
+                        mSv.setRelativeFocusedTask(false, false /* animated */);
                     }
                     return true;
             }
@@ -461,7 +457,7 @@
         // Re-enable touch events from this task view
         tv.setTouchEnabled(true);
         // Remove the task view from the stack
-        EventBus.getDefault().send(new DismissTaskEvent(tv.getTask(), tv));
+        EventBus.getDefault().send(new DismissTaskViewEvent(tv.getTask(), tv));
         // Keep track of deletions by keyboard
         MetricsLogger.histogram(tv.getContext(), "overview_task_dismissed_source",
                 Constants.Metrics.DismissSourceSwipeGesture);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
index bab4da7..0a5ee79 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -29,8 +29,8 @@
 import android.graphics.Point;
 import android.graphics.PorterDuff;
 import android.graphics.PorterDuffColorFilter;
-import android.graphics.Rect;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewOutlineProvider;
@@ -40,13 +40,15 @@
 import android.widget.FrameLayout;
 import com.android.systemui.R;
 import com.android.systemui.recents.Constants;
+import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.RecentsActivity;
 import com.android.systemui.recents.RecentsActivityLaunchState;
 import com.android.systemui.recents.RecentsConfiguration;
 import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.events.ui.DismissTaskEvent;
+import com.android.systemui.recents.events.ui.DismissTaskViewEvent;
 import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
 import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent;
+import com.android.systemui.recents.misc.SystemServicesProxy;
 import com.android.systemui.recents.misc.Utilities;
 import com.android.systemui.recents.model.Task;
 import com.android.systemui.statusbar.phone.PhoneStatusBar;
@@ -55,11 +57,13 @@
 public class TaskView extends FrameLayout implements Task.TaskCallbacks,
         View.OnClickListener, View.OnLongClickListener {
 
+    private final static String TAG = "TaskView";
+    private final static boolean DEBUG = false;
+
     /** The TaskView callbacks */
     interface TaskViewCallbacks {
         public void onTaskViewClicked(TaskView tv, Task task, boolean lockToTask);
         public void onTaskViewClipStateChanged(TaskView tv);
-        public void onTaskViewFocusChanged(TaskView tv, boolean focused);
     }
 
     RecentsConfiguration mConfig;
@@ -76,6 +80,7 @@
     Task mTask;
     boolean mTaskDataLoaded;
     boolean mIsFocused;
+    boolean mIsFocusAnimated;
     boolean mFocusAnimationsEnabled;
     boolean mClipViewInStack;
     AnimateableViewBounds mViewBounds;
@@ -397,15 +402,6 @@
             ctx.postAnimationTrigger.increment();
             startDelay = delay;
         }
-
-        // Enable the focus animations from this point onwards so that they aren't affected by the
-        // window transitions
-        postDelayed(new Runnable() {
-            @Override
-            public void run() {
-                enableFocusAnimations();
-            }
-        }, startDelay);
     }
 
     public void fadeInActionButton(int delay, int duration) {
@@ -547,7 +543,7 @@
         startDeleteTaskAnimation(new Runnable() {
             @Override
             public void run() {
-                EventBus.getDefault().send(new DismissTaskEvent(mTask, tv));
+                EventBus.getDefault().send(new DismissTaskViewEvent(mTask, tv));
             }
         }, 0);
     }
@@ -620,6 +616,8 @@
                 anim.addListener(postAnimRunnable);
             }
             anim.start();
+        } else {
+            postAnimRunnable.onAnimationEnd(null);
         }
     }
 
@@ -641,58 +639,32 @@
     /**** View focus state ****/
 
     /**
-     * Sets the focused task explicitly. We need a separate flag because requestFocus() won't happen
-     * if the view is not currently visible, or we are in touch state (where we still want to keep
-     * track of focus).
+     * Explicitly sets the focused state of this task.
      */
-    public void setFocusedTask(boolean animateFocusedState) {
-        mIsFocused = true;
-        if (mFocusAnimationsEnabled) {
-            // Focus the header bar
-            mHeaderView.onTaskViewFocusChanged(true, animateFocusedState);
-        }
-        // Update the thumbnail alpha with the focus
-        mThumbnailView.onFocusChanged(true);
-        // Call the callback
-        if (mCb != null) {
-            mCb.onTaskViewFocusChanged(this, true);
-        }
-        // Workaround, we don't always want it focusable in touch mode, but we want the first task
-        // to be focused after the enter-recents animation, which can be triggered from either touch
-        // or keyboard
-        setFocusableInTouchMode(true);
-        requestFocus();
-        setFocusableInTouchMode(false);
-        invalidate();
-    }
-
-    /**
-     * Unsets the focused task explicitly.
-     */
-    void unsetFocusedTask() {
-        mIsFocused = false;
-        if (mFocusAnimationsEnabled) {
-            // Un-focus the header bar
-            mHeaderView.onTaskViewFocusChanged(false, true);
+    public void setFocusedState(boolean isFocused, boolean animated, boolean requestViewFocus) {
+        if (DEBUG) {
+            Log.d(TAG, "setFocusedState: " + mTask.activityLabel + " focused: " + isFocused +
+                    " mIsFocused: " + mIsFocused + " animated: " + animated +
+                    " requestViewFocus: " + requestViewFocus + " isFocused(): " + isFocused() +
+                    " isAccessibilityFocused(): " + isAccessibilityFocused());
         }
 
-        // Update the thumbnail alpha with the focus
-        mThumbnailView.onFocusChanged(false);
-        // Call the callback
-        if (mCb != null) {
-            mCb.onTaskViewFocusChanged(this, false);
-        }
-        invalidate();
-    }
-
-    /**
-     * Updates the explicitly focused state when the view focus changes.
-     */
-    @Override
-    protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
-        super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
-        if (!gainFocus) {
-            unsetFocusedTask();
+        SystemServicesProxy ssp = Recents.getSystemServices();
+        mIsFocused = isFocused;
+        mIsFocusAnimated = animated;
+        mHeaderView.onTaskViewFocusChanged(isFocused, animated);
+        mThumbnailView.onFocusChanged(isFocused);
+        if (isFocused) {
+            if (requestViewFocus && !isFocused()) {
+                requestFocus();
+            }
+            if (requestViewFocus && !isAccessibilityFocused() && ssp.isTouchExplorationEnabled()) {
+                requestAccessibilityFocus();
+            }
+        } else {
+            if (isAccessibilityFocused() && ssp.isTouchExplorationEnabled()) {
+                clearAccessibilityFocus();
+            }
         }
     }
 
@@ -700,17 +672,14 @@
      * Returns whether we have explicitly been focused.
      */
     public boolean isFocusedTask() {
-        return mIsFocused || isFocused();
+        return mIsFocused;
     }
 
-    /** Enables all focus animations. */
-    void enableFocusAnimations() {
-        boolean wasFocusAnimationsEnabled = mFocusAnimationsEnabled;
-        mFocusAnimationsEnabled = true;
-        if (mIsFocused && !wasFocusAnimationsEnabled) {
-            // Re-notify the header if we were focused and animations were not previously enabled
-            mHeaderView.onTaskViewFocusChanged(true, true);
-        }
+    /**
+     * Returns whether this focused task is animated.
+     */
+    public boolean isFocusAnimated() {
+        return mIsFocusAnimated;
     }
 
     public void disableLayersForOneFrame() {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
index e1e07ef..f6353f8 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
@@ -39,7 +39,6 @@
 import android.util.AttributeSet;
 import android.view.View;
 import android.view.ViewOutlineProvider;
-import android.view.accessibility.AccessibilityManager;
 import android.view.animation.AnimationUtils;
 import android.view.animation.Interpolator;
 import android.widget.FrameLayout;
@@ -244,9 +243,8 @@
         mMoveTaskButton.setOnClickListener(this);
 
         // In accessibility, a single click on the focused app info button will show it
-        AccessibilityManager am = (AccessibilityManager) getContext().
-                getSystemService(Context.ACCESSIBILITY_SERVICE);
-        if (am != null && am.isEnabled()) {
+        SystemServicesProxy ssp = Recents.getSystemServices();
+        if (ssp.isTouchExplorationEnabled()) {
             mApplicationIcon.setOnClickListener(this);
         }
     }
@@ -369,9 +367,6 @@
 
     /** Notifies the associated TaskView has been focused. */
     void onTaskViewFocusChanged(boolean focused, boolean animateFocusedState) {
-        // If we are not animating the visible state, just return
-        if (!animateFocusedState) return;
-
         boolean isRunning = false;
         if (mFocusAnimator != null) {
             isRunning = mFocusAnimator.isRunning();
@@ -379,6 +374,9 @@
         }
 
         if (focused) {
+            // If we are not animating the visible state, just return
+            if (!animateFocusedState) return;
+
             int currentColor = mBackgroundColor;
             int secondaryColor = getSecondaryColor(mCurrentPrimaryColor, mCurrentPrimaryColorIsDark);
             int[][] states = new int[][] {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index 0def599..2c16f81 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -1275,20 +1275,22 @@
 
         int maxHeight = mRowMaxHeight;
         final StatusBarNotification sbn = entry.notification;
-        RemoteViews contentView = sbn.getNotification().contentView;
-        RemoteViews bigContentView = sbn.getNotification().bigContentView;
-        RemoteViews headsUpContentView = sbn.getNotification().headsUpContentView;
+        entry.cacheContentViews(mContext, null);
+
+        final RemoteViews contentView = entry.cachedContentView;
+        final RemoteViews bigContentView = entry.cachedBigContentView;
+        final RemoteViews headsUpContentView = entry.cachedHeadsUpContentView;
+        final RemoteViews publicContentView = entry.cachedPublicContentView;
 
         if (contentView == null) {
+            Log.v(TAG, "no contentView for: " + sbn.getNotification());
             return false;
         }
 
         if (DEBUG) {
-            Log.v(TAG, "publicNotification: " + sbn.getNotification().publicVersion);
+            Log.v(TAG, "publicContentView: " + publicContentView);
         }
 
-        Notification publicNotification = sbn.getNotification().publicVersion;
-
         ExpandableNotificationRow row;
 
         // Stash away previous user expansion state so we can restore it at
@@ -1377,9 +1379,9 @@
 
         // now the public version
         View publicViewLocal = null;
-        if (publicNotification != null) {
+        if (publicContentView != null) {
             try {
-                publicViewLocal = publicNotification.contentView.apply(
+                publicViewLocal = publicContentView.apply(
                         sbn.getPackageContext(mContext),
                         contentContainerPublic, mOnClickHandler);
 
@@ -1537,30 +1539,9 @@
             }
 
             if (viableAction != null) {
-                Notification stripped = n.clone();
-                Notification.Builder.stripForDelivery(stripped);
-                stripped.extras.putBoolean("android.rebuild", true);
-                stripped.actions = new Notification.Action[] { viableAction };
-                stripped.extras.putBoolean("android.rebuild.contentView", true);
-                stripped.contentView = null;
-                stripped.extras.putBoolean("android.rebuild.bigView", true);
-                stripped.bigContentView = null;
-                stripped.extras.putBoolean("android.rebuild.hudView", true);
-                stripped.headsUpContentView = null;
-
-                stripped.extras.putParcelable(Notification.EXTRA_LARGE_ICON,
-                        stripped.getLargeIcon());
-                if (SystemProperties.getBoolean("debug.strip_third_line", false)) {
-                    stripped.extras.putCharSequence(Notification.EXTRA_INFO_TEXT, null);
-                    stripped.extras.putCharSequence(Notification.EXTRA_SUMMARY_TEXT, null);
-                }
-
-                Notification rebuilt = Notification.Builder.rebuild(mContext, stripped);
-
-                n.actions = rebuilt.actions;
-                n.bigContentView = rebuilt.bigContentView;
-                n.headsUpContentView = rebuilt.headsUpContentView;
-                n.publicVersion = rebuilt.publicVersion;
+                Notification.Builder rebuilder = Notification.Builder.recoverBuilder(mContext, n);
+                rebuilder.setActions(viableAction);
+                rebuilder.build(); // will rewrite n
             }
         }
     }
@@ -2034,12 +2015,15 @@
         }
 
         Notification n = notification.getNotification();
-        if (DEBUG) {
-            logUpdate(entry, n);
-        }
-        boolean applyInPlace = shouldApplyInPlace(entry, n);
+
+        boolean applyInPlace = !entry.cacheContentViews(mContext, notification.getNotification());
         boolean shouldInterrupt = shouldInterrupt(entry, notification);
         boolean alertAgain = alertAgain(entry, n);
+        if (DEBUG) {
+            Log.d(TAG, "applyInPlace=" + applyInPlace
+                    + " shouldInterrupt=" + shouldInterrupt
+                    + " alertAgain=" + alertAgain);
+        }
 
         entry.notification = notification;
         mGroupManager.onEntryUpdated(entry, entry.notification);
@@ -2104,101 +2088,32 @@
     protected abstract void updateHeadsUp(String key, Entry entry, boolean shouldInterrupt,
             boolean alertAgain);
 
-    private void logUpdate(Entry oldEntry, Notification n) {
-        StatusBarNotification oldNotification = oldEntry.notification;
-        Log.d(TAG, "old notification: when=" + oldNotification.getNotification().when
-                + " ongoing=" + oldNotification.isOngoing()
-                + " expanded=" + oldEntry.getContentView()
-                + " contentView=" + oldNotification.getNotification().contentView
-                + " bigContentView=" + oldNotification.getNotification().bigContentView
-                + " publicView=" + oldNotification.getNotification().publicVersion
-                + " rowParent=" + oldEntry.row.getParent());
-        Log.d(TAG, "new notification: when=" + n.when
-                + " ongoing=" + oldNotification.isOngoing()
-                + " contentView=" + n.contentView
-                + " bigContentView=" + n.bigContentView
-                + " publicView=" + n.publicVersion);
-    }
-
-    /**
-     * @return whether we can just reapply the RemoteViews from a notification in-place when it is
-     * updated
-     */
-    private boolean shouldApplyInPlace(Entry entry, Notification n) {
-        StatusBarNotification oldNotification = entry.notification;
-        // XXX: modify when we do something more intelligent with the two content views
-        final RemoteViews oldContentView = oldNotification.getNotification().contentView;
-        final RemoteViews contentView = n.contentView;
-        final RemoteViews oldBigContentView = oldNotification.getNotification().bigContentView;
-        final RemoteViews bigContentView = n.bigContentView;
-        final RemoteViews oldHeadsUpContentView
-                = oldNotification.getNotification().headsUpContentView;
-        final RemoteViews headsUpContentView = n.headsUpContentView;
-        final Notification oldPublicNotification = oldNotification.getNotification().publicVersion;
-        final RemoteViews oldPublicContentView = oldPublicNotification != null
-                ? oldPublicNotification.contentView : null;
-        final Notification publicNotification = n.publicVersion;
-        final RemoteViews publicContentView = publicNotification != null
-                ? publicNotification.contentView : null;
-        boolean contentsUnchanged = entry.getContentView() != null
-                && contentView.getPackage() != null
-                && oldContentView.getPackage() != null
-                && oldContentView.getPackage().equals(contentView.getPackage())
-                && oldContentView.getLayoutId() == contentView.getLayoutId();
-        // large view may be null
-        boolean bigContentsUnchanged =
-                (entry.getExpandedContentView() == null && bigContentView == null)
-                || ((entry.getExpandedContentView() != null && bigContentView != null)
-                    && bigContentView.getPackage() != null
-                    && oldBigContentView.getPackage() != null
-                    && oldBigContentView.getPackage().equals(bigContentView.getPackage())
-                    && oldBigContentView.getLayoutId() == bigContentView.getLayoutId());
-        boolean headsUpContentsUnchanged =
-                (oldHeadsUpContentView == null && headsUpContentView == null)
-                || ((oldHeadsUpContentView != null && headsUpContentView != null)
-                    && headsUpContentView.getPackage() != null
-                    && oldHeadsUpContentView.getPackage() != null
-                    && oldHeadsUpContentView.getPackage().equals(headsUpContentView.getPackage())
-                    && oldHeadsUpContentView.getLayoutId() == headsUpContentView.getLayoutId());
-        boolean publicUnchanged  =
-                (oldPublicContentView == null && publicContentView == null)
-                || ((oldPublicContentView != null && publicContentView != null)
-                        && publicContentView.getPackage() != null
-                        && oldPublicContentView.getPackage() != null
-                        && oldPublicContentView.getPackage().equals(publicContentView.getPackage())
-                        && oldPublicContentView.getLayoutId() == publicContentView.getLayoutId());
-        return contentsUnchanged && bigContentsUnchanged && headsUpContentsUnchanged
-                && publicUnchanged;
-    }
-
-    private void updateNotificationViews(Entry entry, StatusBarNotification notification) {
-        final RemoteViews contentView = notification.getNotification().contentView;
-        final RemoteViews bigContentView = notification.getNotification().bigContentView;
-        final RemoteViews headsUpContentView = notification.getNotification().headsUpContentView;
-        final Notification publicVersion = notification.getNotification().publicVersion;
-        final RemoteViews publicContentView = publicVersion != null ? publicVersion.contentView
-                : null;
+    private void updateNotificationViews(Entry entry, StatusBarNotification sbn) {
+        final RemoteViews contentView = entry.cachedContentView;
+        final RemoteViews bigContentView = entry.cachedBigContentView;
+        final RemoteViews headsUpContentView = entry.cachedHeadsUpContentView;
+        final RemoteViews publicContentView = entry.cachedPublicContentView;
 
         // Reapply the RemoteViews
         contentView.reapply(mContext, entry.getContentView(), mOnClickHandler);
         if (bigContentView != null && entry.getExpandedContentView() != null) {
-            bigContentView.reapply(notification.getPackageContext(mContext),
+            bigContentView.reapply(sbn.getPackageContext(mContext),
                     entry.getExpandedContentView(),
                     mOnClickHandler);
         }
         View headsUpChild = entry.getHeadsUpContentView();
         if (headsUpContentView != null && headsUpChild != null) {
-            headsUpContentView.reapply(notification.getPackageContext(mContext),
+            headsUpContentView.reapply(sbn.getPackageContext(mContext),
                     headsUpChild, mOnClickHandler);
         }
         if (publicContentView != null && entry.getPublicContentView() != null) {
-            publicContentView.reapply(notification.getPackageContext(mContext),
+            publicContentView.reapply(sbn.getPackageContext(mContext),
                     entry.getPublicContentView(), mOnClickHandler);
         }
         // update the contentIntent
-        mNotificationClicker.register(entry.row, notification);
+        mNotificationClicker.register(entry.row, sbn);
 
-        entry.row.setStatusBarNotification(notification);
+        entry.row.setStatusBarNotification(sbn);
         entry.row.notifyContentUpdated();
         entry.row.resetHeight();
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
index aedae52..6a90d8e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar;
 
 import android.app.Notification;
+import android.content.Context;
 import android.os.SystemClock;
 import android.service.notification.NotificationListenerService;
 import android.service.notification.NotificationListenerService.Ranking;
@@ -24,6 +25,7 @@
 import android.service.notification.StatusBarNotification;
 import android.util.ArrayMap;
 import android.view.View;
+import android.widget.RemoteViews;
 
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
@@ -53,6 +55,10 @@
         public boolean legacy; // whether the notification has a legacy, dark background
         public int targetSdk;
         private long lastFullScreenIntentLaunchTime = NOT_LAUNCHED_YET;
+        public RemoteViews cachedContentView;
+        public RemoteViews cachedBigContentView;
+        public RemoteViews cachedHeadsUpContentView;
+        public RemoteViews cachedPublicContentView;
 
         public Entry(StatusBarNotification n, StatusBarIconView ic) {
             this.key = n.getKey();
@@ -98,6 +104,67 @@
             return row.getPublicLayout().getContractedChild();
         }
 
+        public boolean cacheContentViews(Context ctx, Notification updatedNotification) {
+            boolean cached = false;
+            if (updatedNotification != null) {
+                final Notification.Builder updatedNotificationBuilder
+                        = Notification.Builder.recoverBuilder(ctx, updatedNotification);
+                final RemoteViews newContentView = updatedNotificationBuilder.makeContentView();
+                if (!compareRemoteViews(cachedContentView, newContentView)) {
+                    cachedContentView = newContentView;
+                    cached |= true;
+                }
+                final RemoteViews newBigContentView =
+                        updatedNotificationBuilder.makeBigContentView();
+                if (!compareRemoteViews(cachedBigContentView, newBigContentView)) {
+                    cachedBigContentView = newBigContentView;
+                    cached |= true;
+                }
+                final RemoteViews newHeadsUpContentView =
+                        updatedNotificationBuilder.makeHeadsUpContentView();
+                if (!compareRemoteViews(cachedHeadsUpContentView, newBigContentView)) {
+                    cachedHeadsUpContentView = newHeadsUpContentView;
+                    cached |= true;
+                }
+                final Notification updatedPublicNotification = updatedNotification.publicVersion;
+                final RemoteViews newPubContentView = (updatedPublicNotification != null)
+                        ? Notification.Builder.recoverBuilder(
+                                ctx, updatedPublicNotification).makeContentView()
+                        : null;
+                if (!compareRemoteViews(cachedPublicContentView, newPubContentView)) {
+                    cachedPublicContentView = newPubContentView;
+                    cached |= true;
+                }
+            } else {
+                final Notification.Builder builder
+                        = Notification.Builder.recoverBuilder(ctx, notification.getNotification());
+
+                cachedContentView = builder.makeContentView();
+                cachedBigContentView = builder.makeBigContentView();
+                cachedHeadsUpContentView = builder.makeHeadsUpContentView();
+
+                final Notification publicNotification =
+                        notification.getNotification().publicVersion;
+                if (publicNotification != null) {
+                    final Notification.Builder publicBuilder
+                            = Notification.Builder.recoverBuilder(ctx, publicNotification);
+                    cachedPublicContentView = publicBuilder.makeContentView();
+                }
+                cached = true;
+            }
+            return cached;
+        }
+
+        // Returns true if the RemoteViews are the same.
+        private boolean compareRemoteViews(final RemoteViews a, final RemoteViews b) {
+            return (a == null && b == null) ||
+                    (a != null && b != null
+                    && b.getPackage() != null
+                    && a.getPackage() != null
+                    && a.getPackage().equals(b.getPackage())
+                    && a.getLayoutId() == b.getLayoutId());
+        }
+
         public void notifyFullScreenIntentLaunched() {
             lastFullScreenIntentLaunchTime = SystemClock.elapsedRealtime();
         }
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 99539e4..1cd3758 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -21,6 +21,7 @@
 import static android.app.ActivityManager.FREEFORM_WORKSPACE_STACK_ID;
 import static android.app.ActivityManager.FULLSCREEN_WORKSPACE_STACK_ID;
 import static android.app.ActivityManager.HOME_STACK_ID;
+import static android.app.ActivityManager.INVALID_STACK_ID;
 import static android.app.ActivityManager.LAST_STATIC_STACK_ID;
 import static android.app.ActivityManager.PINNED_STACK_ID;
 import static android.content.pm.ActivityInfo.FLAG_SHOW_FOR_ALL_USERS;
@@ -4375,6 +4376,7 @@
 
             RunningTaskInfo ci = new RunningTaskInfo();
             ci.id = task.taskId;
+            ci.stackId = (task.stack == null) ? INVALID_STACK_ID : task.stack.getStackId();
             ci.baseActivity = r.intent.getComponent();
             ci.topActivity = top.intent.getComponent();
             ci.lastActiveTime = task.lastActiveTime;
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index e8e46ef..361bbf9 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -2146,13 +2146,6 @@
                     + " id=" + id + " notification=" + notification);
         }
 
-        if (notification.getSmallIcon() != null) {
-            if (!notification.isValid()) {
-                throw new IllegalArgumentException("Invalid notification (): pkg=" + pkg
-                        + " id=" + id + " notification=" + notification);
-            }
-        }
-
         mHandler.post(new Runnable() {
             @Override
             public void run() {
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index b36a22e..4dd7388 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -16,14 +16,15 @@
 
 package com.android.server.pm;
 
-import android.accounts.Account;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.Activity;
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
 import android.app.ActivityManagerNative;
 import android.app.IStopUserCallback;
 import android.app.admin.DevicePolicyManager;
+import android.app.admin.DevicePolicyManagerInternal;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -46,6 +47,7 @@
 import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.os.UserManagerInternal;
 import android.os.storage.StorageManager;
 import android.os.storage.VolumeInfo;
 import android.system.ErrnoException;
@@ -59,9 +61,11 @@
 import android.util.TimeUtils;
 import android.util.Xml;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IAppOpsService;
 import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.Preconditions;
 import com.android.internal.util.XmlUtils;
 import com.android.server.LocalServices;
 
@@ -83,11 +87,18 @@
 
 import libcore.io.IoUtils;
 
+/**
+ * Service for {@link UserManager}.
+ *
+ * Method naming convention:
+ * - Methods suffixed with "Locked" should be called within the {@code this} lock.
+ * - Methods suffixed with "RL" should be called within the {@link #mRestrictionsLock} lock.
+ */
 public class UserManagerService extends IUserManager.Stub {
 
     private static final String LOG_TAG = "UserManagerService";
 
-    private static final boolean DBG = false;
+    private static final boolean DBG = false; // DO NOT SUBMIT WITH TRUE
 
     private static final String TAG_NAME = "name";
     private static final String ATTR_FLAGS = "flags";
@@ -160,7 +171,38 @@
     private final File mUserListFile;
 
     private final SparseArray<UserInfo> mUsers = new SparseArray<UserInfo>();
-    private final SparseArray<Bundle> mUserRestrictions = new SparseArray<Bundle>();
+
+    private final Object mRestrictionsLock = new Object();
+
+    /**
+     * User restrictions set via UserManager.  This doesn't include restrictions set by
+     * device owner / profile owners.
+     *
+     * DO NOT Change existing {@link Bundle} in it.  When changing a restriction for a user,
+     * a new {@link Bundle} should always be created and set.  This is because a {@link Bundle}
+     * maybe shared between {@link #mBaseUserRestrictions} and
+     * {@link #mCachedEffectiveUserRestrictions}, but they should always updated separately.
+     * (Otherwise we won't be able to detect what restrictions have changed in
+     * {@link #updateUserRestrictionsInternalRL).
+     */
+    @GuardedBy("mRestrictionsLock")
+    private final SparseArray<Bundle> mBaseUserRestrictions = new SparseArray<>();
+
+    /**
+     * Cached user restrictions that are in effect -- i.e. {@link #mBaseUserRestrictions} combined
+     * with device / profile owner restrictions.  We'll initialize it lazily; use
+     * {@link #getEffectiveUserRestrictions} to access it.
+     *
+     * DO NOT Change existing {@link Bundle} in it.  When changing a restriction for a user,
+     * a new {@link Bundle} should always be created and set.  This is because a {@link Bundle}
+     * maybe shared between {@link #mBaseUserRestrictions} and
+     * {@link #mCachedEffectiveUserRestrictions}, but they should always updated separately.
+     * (Otherwise we won't be able to detect what restrictions have changed in
+     * {@link #updateUserRestrictionsInternalRL).
+     */
+    @GuardedBy("mRestrictionsLock")
+    private final SparseArray<Bundle> mCachedEffectiveUserRestrictions = new SparseArray<>();
+
     private final Bundle mGuestRestrictions = new Bundle();
 
     /**
@@ -176,6 +218,8 @@
 
     private IAppOpsService mAppOpsService;
 
+    private final LocalService mLocalService;
+
     private static UserManagerService sInstance;
 
     public static UserManagerService getInstance() {
@@ -231,6 +275,8 @@
                 sInstance = this;
             }
         }
+        mLocalService = new LocalService();
+        LocalServices.addService(UserManagerInternal.class, mLocalService);
     }
 
     void systemReady() {
@@ -258,8 +304,9 @@
         mAppOpsService = IAppOpsService.Stub.asInterface(
                 ServiceManager.getService(Context.APP_OPS_SERVICE));
         for (int i = 0; i < mUserIds.length; ++i) {
+            final int userId = mUserIds[i];
             try {
-                mAppOpsService.setUserRestrictions(mUserRestrictions.get(mUserIds[i]), mUserIds[i]);
+                mAppOpsService.setUserRestrictions(getEffectiveUserRestrictions(userId), userId);
             } catch (RemoteException e) {
                 Log.w(LOG_TAG, "Unable to notify AppOpsService of UserRestrictions");
             }
@@ -588,75 +635,171 @@
         }
     }
 
-    @Override
-    public boolean hasUserRestriction(String restrictionKey, int userId) {
-        synchronized (mPackagesLock) {
-            Bundle restrictions = mUserRestrictions.get(userId);
-            return restrictions != null && restrictions.getBoolean(restrictionKey);
+    @GuardedBy("mRestrictionsLock")
+    private Bundle computeEffectiveUserRestrictionsRL(int userId) {
+        final DevicePolicyManagerInternal dpmi =
+                LocalServices.getService(DevicePolicyManagerInternal.class);
+        final Bundle systemRestrictions = mBaseUserRestrictions.get(userId);
+
+        final Bundle effective;
+        if (dpmi == null) {
+            // TODO Make sure it's because DPMS is disabled and not because we called it too early.
+            effective = systemRestrictions;
+        } else {
+            effective = dpmi.getComposedUserRestrictions(userId, systemRestrictions);
+        }
+        return effective;
+    }
+
+    @GuardedBy("mRestrictionsLock")
+    private void invalidateEffectiveUserRestrictionsRL(int userId) {
+        if (DBG) {
+            Log.d(LOG_TAG, "invalidateEffectiveUserRestrictions userId=" + userId);
+        }
+        mCachedEffectiveUserRestrictions.remove(userId);
+    }
+
+    private Bundle getEffectiveUserRestrictions(int userId) {
+        synchronized (mRestrictionsLock) {
+            Bundle restrictions = mCachedEffectiveUserRestrictions.get(userId);
+            if (restrictions == null) {
+                restrictions = computeEffectiveUserRestrictionsRL(userId);
+                mCachedEffectiveUserRestrictions.put(userId, restrictions);
+            }
+            return restrictions;
         }
     }
 
+    /** @return a specific user restriction that's in effect currently. */
+    @Override
+    public boolean hasUserRestriction(String restrictionKey, int userId) {
+        Bundle restrictions = getEffectiveUserRestrictions(userId);
+        return restrictions != null && restrictions.getBoolean(restrictionKey);
+    }
+
+    /**
+     * @return UserRestrictions that are in effect currently.  This always returns a new
+     * {@link Bundle}.
+     */
     @Override
     public Bundle getUserRestrictions(int userId) {
-        synchronized (mPackagesLock) {
-            Bundle restrictions = mUserRestrictions.get(userId);
-            return restrictions != null ? new Bundle(restrictions) : new Bundle();
-        }
+        Bundle restrictions = getEffectiveUserRestrictions(userId);
+        return restrictions != null ? new Bundle(restrictions) : new Bundle();
     }
 
     @Override
     public void setUserRestriction(String key, boolean value, int userId) {
         checkManageUsersPermission("setUserRestriction");
-        synchronized (mPackagesLock) {
-            if (!UserRestrictionsUtils.SYSTEM_CONTROLLED_USER_RESTRICTIONS.contains(key)) {
-                Bundle restrictions = getUserRestrictions(userId);
-                restrictions.putBoolean(key, value);
-                setUserRestrictionsInternalLocked(restrictions, userId);
-            }
+        if (!UserRestrictionsUtils.SYSTEM_CONTROLLED_USER_RESTRICTIONS.contains(key)) {
+            setUserRestrictionNoCheck(key, value, userId);
         }
     }
 
     @Override
     public void setSystemControlledUserRestriction(String key, boolean value, int userId) {
         checkSystemOrRoot("setSystemControlledUserRestriction");
-        synchronized (mPackagesLock) {
-            Bundle restrictions = getUserRestrictions(userId);
-            restrictions.putBoolean(key, value);
-            setUserRestrictionsInternalLocked(restrictions, userId);
+        setUserRestrictionNoCheck(key, value, userId);
+    }
+
+    private void setUserRestrictionNoCheck(String key, boolean value, int userId) {
+        synchronized (mRestrictionsLock) {
+            // Note we can't modify Bundles stored in mBaseUserRestrictions directly, so create
+            // a copy.
+            final Bundle newRestrictions = new Bundle();
+            UserRestrictionsUtils.merge(newRestrictions, mBaseUserRestrictions.get(userId));
+            newRestrictions.putBoolean(key, value);
+
+            updateUserRestrictionsInternalRL(newRestrictions, userId);
         }
     }
 
-    @Override
-    public void setUserRestrictions(Bundle restrictions, int userId) {
-        checkManageUsersPermission("setUserRestrictions");
-        if (restrictions == null) return;
-
-        synchronized (mPackagesLock) {
-            final Bundle oldUserRestrictions = mUserRestrictions.get(userId);
-            // Restore the original state of system controlled restrictions from oldUserRestrictions
-            for (String key : UserRestrictionsUtils.SYSTEM_CONTROLLED_USER_RESTRICTIONS) {
-                restrictions.remove(key);
-                if (oldUserRestrictions.containsKey(key)) {
-                    restrictions.putBoolean(key, oldUserRestrictions.getBoolean(key));
-                }
-            }
-            setUserRestrictionsInternalLocked(restrictions, userId);
+    /**
+     * Optionally updating user restrictions, calculate the effective user restrictions by
+     * consulting {@link com.android.server.devicepolicy.DevicePolicyManagerService} and also
+     * apply it to {@link com.android.server.AppOpsService}.
+     * TODO applyUserRestrictionsLocked() should also apply to system settings.
+     *
+     * @param newRestrictions User restrictions to set.  If null, only the effective restrictions
+     *     will be updated.  Note don't pass an existing Bundle in {@link #mBaseUserRestrictions}
+     *     or {@link #mCachedEffectiveUserRestrictions}; that'll most likely cause a sub
+     * @param userId target user ID.
+     */
+    @GuardedBy("mRestrictionsLock")
+    private void updateUserRestrictionsInternalRL(
+            @Nullable Bundle newRestrictions, int userId) {
+        if (DBG) {
+            Log.d(LOG_TAG, "updateUserRestrictionsInternalLocked userId=" + userId
+                    + " bundle=" + newRestrictions);
         }
+        final Bundle prevRestrictions = getEffectiveUserRestrictions(userId);
+
+        // Update system restrictions.
+        if (newRestrictions != null) {
+            // If newRestrictions == the current one, it's probably a bug.
+            Preconditions.checkState(mBaseUserRestrictions.get(userId) != newRestrictions);
+            Preconditions.checkState(mCachedEffectiveUserRestrictions.get(userId)
+                    != newRestrictions);
+            mBaseUserRestrictions.put(userId, newRestrictions);
+        }
+
+        mCachedEffectiveUserRestrictions.put(
+                userId, computeEffectiveUserRestrictionsRL(userId));
+
+        applyUserRestrictionsRL(userId, mBaseUserRestrictions.get(userId), prevRestrictions);
     }
 
-    private void setUserRestrictionsInternalLocked(Bundle restrictions, int userId) {
-        final Bundle userRestrictions = mUserRestrictions.get(userId);
-        userRestrictions.clear();
-        userRestrictions.putAll(restrictions);
-        long token = Binder.clearCallingIdentity();
+    @GuardedBy("mRestrictionsLock")
+    private void applyUserRestrictionsRL(int userId,
+            Bundle newRestrictions, Bundle prevRestrictions) {
+        final long token = Binder.clearCallingIdentity();
         try {
-        mAppOpsService.setUserRestrictions(userRestrictions, userId);
+            mAppOpsService.setUserRestrictions(newRestrictions, userId);
         } catch (RemoteException e) {
             Log.w(LOG_TAG, "Unable to notify AppOpsService of UserRestrictions");
         } finally {
             Binder.restoreCallingIdentity(token);
         }
-        scheduleWriteUserLocked(mUsers.get(userId));
+
+        // TODO Move the code from DPMS.setUserRestriction().
+    }
+
+    @GuardedBy("mRestrictionsLock")
+    private void updateEffectiveUserRestrictionsRL(int userId) {
+        updateUserRestrictionsInternalRL(null, userId);
+    }
+
+    @GuardedBy("mRestrictionsLock")
+    private void updateEffectiveUserRestrictionsForAllUsersRL() {
+        // First, invalidate all cached values.
+        synchronized (mRestrictionsLock) {
+            mCachedEffectiveUserRestrictions.clear();
+        }
+        // We don't want to call into ActivityManagerNative while taking a lock, so we'll call
+        // it on a handler.
+        final Runnable r = new Runnable() {
+            @Override
+            public void run() {
+                // Then get the list of running users.
+                final int[] runningUsers;
+                try {
+                    runningUsers = ActivityManagerNative.getDefault().getRunningUserIds();
+                } catch (RemoteException e) {
+                    Log.w(LOG_TAG, "Unable to access ActivityManagerNative");
+                    return;
+                }
+                // Then re-calculate the effective restrictions and apply, only for running users.
+                // It's okay if a new user has started after the getRunningUserIds() call,
+                // because we'll do the same thing (re-calculate the restrictions and apply)
+                // when we start a user.
+                // TODO: "Apply restrictions upon user start hasn't been implemented.  Implement it.
+                synchronized (mRestrictionsLock) {
+                    for (int i = 0; i < runningUsers.length; i++) {
+                        updateUserRestrictionsInternalRL(null, runningUsers[i]);
+                    }
+                }
+            }
+        };
+        mHandler.post(r);
     }
 
     /**
@@ -926,7 +1069,9 @@
         mUserVersion = USER_VERSION;
 
         Bundle restrictions = new Bundle();
-        mUserRestrictions.append(UserHandle.USER_SYSTEM, restrictions);
+        synchronized (mRestrictionsLock) {
+            mBaseUserRestrictions.append(UserHandle.USER_SYSTEM, restrictions);
+        }
 
         updateUserIdsLocked();
         initDefaultGuestRestrictions();
@@ -989,9 +1134,13 @@
             serializer.startTag(null, TAG_NAME);
             serializer.text(userInfo.name);
             serializer.endTag(null, TAG_NAME);
-            Bundle restrictions = mUserRestrictions.get(userInfo.id);
+            Bundle restrictions;
+            synchronized (mRestrictionsLock) {
+                restrictions = mBaseUserRestrictions.get(userInfo.id);
+            }
             if (restrictions != null) {
-                UserRestrictionsUtils.writeRestrictions(serializer, restrictions, TAG_RESTRICTIONS);
+                UserRestrictionsUtils
+                        .writeRestrictions(serializer, restrictions, TAG_RESTRICTIONS);
             }
             serializer.endTag(null, TAG_USER);
 
@@ -1131,7 +1280,9 @@
             userInfo.guestToRemove = guestToRemove;
             userInfo.profileGroupId = profileGroupId;
             userInfo.restrictedProfileParentId = restrictedProfileParentId;
-            mUserRestrictions.append(id, restrictions);
+            synchronized (mRestrictionsLock) {
+                mBaseUserRestrictions.append(id, restrictions);
+            }
             return userInfo;
 
         } catch (IOException ioe) {
@@ -1333,7 +1484,9 @@
                     scheduleWriteUserLocked(userInfo);
                     updateUserIdsLocked();
                     Bundle restrictions = new Bundle();
-                    mUserRestrictions.append(userId, restrictions);
+                    synchronized (mRestrictionsLock) {
+                        mBaseUserRestrictions.append(userId, restrictions);
+                    }
                 }
             }
             mPm.newUserCreated(userId);
@@ -1616,25 +1769,6 @@
         }
     }
 
-    @Override
-    public void removeRestrictions() {
-        checkManageUsersPermission("remove restrictions");
-        final int userHandle = UserHandle.getCallingUserId();
-        removeRestrictionsForUser(userHandle, true);
-    }
-
-    private void removeRestrictionsForUser(final int userHandle, boolean unhideApps) {
-        synchronized (mPackagesLock) {
-            // Remove all user restrictions
-            setUserRestrictions(new Bundle(), userHandle);
-            // Remove any app restrictions
-            cleanAppRestrictions(userHandle);
-        }
-        if (unhideApps) {
-            unhideAllInstalledAppsForUser(userHandle);
-        }
-    }
-
     private void unhideAllInstalledAppsForUser(final int userHandle) {
         mHandler.post(new Runnable() {
             @Override
@@ -2062,7 +2196,10 @@
                 }
                 pw.println("    Restrictions:");
                 UserRestrictionsUtils.dumpRestrictions(
-                        pw, "      ", mUserRestrictions.get(user.id));
+                        pw, "      ", mBaseUserRestrictions.get(user.id));
+                pw.println("    Effective restrictions:");
+                UserRestrictionsUtils.dumpRestrictions(
+                        pw, "      ", mCachedEffectiveUserRestrictions.get(user.id));
             }
             pw.println();
             pw.println("Guest restrictions:");
@@ -2095,4 +2232,49 @@
     boolean isInitialized(int userId) {
         return (getUserInfo(userId).flags & UserInfo.FLAG_INITIALIZED) != 0;
     }
+
+    private class LocalService extends UserManagerInternal {
+
+        @Override
+        public Object getUserRestrictionsLock() {
+            return mRestrictionsLock;
+        }
+
+        @Override
+        @GuardedBy("mRestrictionsLock")
+        public void updateEffectiveUserRestrictionsRL(int userId) {
+            UserManagerService.this.updateEffectiveUserRestrictionsRL(userId);
+        }
+
+        @Override
+        @GuardedBy("mRestrictionsLock")
+        public void updateEffectiveUserRestrictionsForAllUsersRL() {
+            UserManagerService.this.updateEffectiveUserRestrictionsForAllUsersRL();
+        }
+
+        @Override
+        public Bundle getBaseUserRestrictions(int userId) {
+            synchronized (mRestrictionsLock) {
+                return mBaseUserRestrictions.get(userId);
+            }
+        }
+
+        @Override
+        public void setBaseUserRestrictionsByDpmsForMigration(
+                int userId, Bundle baseRestrictions) {
+            synchronized (mRestrictionsLock) {
+                mBaseUserRestrictions.put(userId, new Bundle(baseRestrictions));
+                invalidateEffectiveUserRestrictionsRL(userId);
+            }
+
+            synchronized (mPackagesLock) {
+                final UserInfo userInfo = mUsers.get(userId);
+                if (userInfo != null) {
+                    writeUserLocked(userInfo);
+                } else {
+                    Slog.w(LOG_TAG, "UserInfo not found for " + userId);
+                }
+            }
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index db1fd2e..23e3b35 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -86,7 +86,6 @@
             String tag) throws IOException {
         serializer.startTag(null, tag);
         for (String key : USER_RESTRICTIONS) {
-            //
             if (restrictions.getBoolean(key)
                     && !NON_PERSIST_USER_RESTRICTIONS.contains(key)) {
                 serializer.attribute(null, key, "true");
@@ -105,6 +104,17 @@
         }
     }
 
+    public static void merge(Bundle dest, Bundle in) {
+        if (in == null) {
+            return;
+        }
+        for (String key : in.keySet()) {
+            if (in.getBoolean(key, false)) {
+                dest.putBoolean(key, true);
+            }
+        }
+    }
+
     public static void dumpRestrictions(PrintWriter pw, String prefix, Bundle restrictions) {
         boolean noneSet = true;
         if (restrictions != null) {
@@ -114,9 +124,11 @@
                     noneSet = false;
                 }
             }
-        }
-        if (noneSet) {
-            pw.println(prefix + "none");
+            if (noneSet) {
+                pw.println(prefix + "none");
+            }
+        } else {
+            pw.println(prefix + "null");
         }
     }
 }
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index be86e2f..f4a4140 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -31,6 +31,7 @@
 
 import android.content.Context;
 import android.os.RemoteException;
+import android.os.Trace;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.TimeUtils;
@@ -731,8 +732,15 @@
             mWindowPlacerLocked.requestTraversal();
         }
 
+        if (mAnimating && !wasAnimating && Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) {
+            Trace.asyncTraceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "animating", 0);
+        }
+
         if (!mAnimating && wasAnimating) {
             mWindowPlacerLocked.requestTraversal();
+            if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) {
+                Trace.asyncTraceEnd(Trace.TRACE_TAG_WINDOW_MANAGER, "animating", 0);
+            }
         }
 
         mService.destroyPreservedSurfaceLocked();
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 8385685..b4c8f96 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -16,6 +16,8 @@
 
 package com.android.server.devicepolicy;
 
+import com.google.android.collect.Sets;
+
 import static android.Manifest.permission.MANAGE_CA_CERTIFICATES;
 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
 import static android.app.admin.DevicePolicyManager.WIPE_EXTERNAL_STORAGE;
@@ -88,6 +90,7 @@
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.os.UserManagerInternal;
 import android.os.storage.StorageManager;
 import android.provider.ContactsContract.QuickContact;
 import android.provider.ContactsInternal;
@@ -183,8 +186,6 @@
     private static final int MONITORING_CERT_NOTIFICATION_ID = R.string.ssl_ca_cert_warning;
     private static final int PROFILE_WIPED_NOTIFICATION_ID = 1001;
 
-    private static final boolean DBG = false;
-
     private static final String ATTR_PERMISSION_PROVIDER = "permission-provider";
     private static final String ATTR_SETUP_COMPLETE = "setup-complete";
     private static final String ATTR_PERMISSION_POLICY = "permission-policy";
@@ -274,6 +275,7 @@
     final Injector mInjector;
     final IPackageManager mIPackageManager;
     final UserManager mUserManager;
+    final UserManagerInternal mUserManagerInternal;
 
     final LocalService mLocalService;
 
@@ -357,8 +359,10 @@
                     getSendingUserId());
             if (Intent.ACTION_BOOT_COMPLETED.equals(action)
                     || ACTION_EXPIRED_PASSWORD_NOTIFICATION.equals(action)) {
-                if (DBG) Slog.v(LOG_TAG, "Sending password expiration notifications for action "
-                        + action + " for user " + userHandle);
+                if (VERBOSE_LOG) {
+                    Slog.v(LOG_TAG, "Sending password expiration notifications for action "
+                            + action + " for user " + userHandle);
+                }
                 mHandler.post(new Runnable() {
                     @Override
                     public void run() {
@@ -1014,7 +1018,7 @@
 
     private void handlePackagesChanged(String packageName, int userHandle) {
         boolean removed = false;
-        if (DBG) Slog.d(LOG_TAG, "Handling package changes for user " + userHandle);
+        if (VERBOSE_LOG) Slog.d(LOG_TAG, "Handling package changes for user " + userHandle);
         DevicePolicyData policy = getUserData(userHandle);
         synchronized (this) {
             for (int i = policy.mAdminList.size() - 1; i >= 0; i--) {
@@ -1079,6 +1083,10 @@
             return UserManager.get(mContext);
         }
 
+        UserManagerInternal getUserManagerInternal() {
+            return LocalServices.getService(UserManagerInternal.class);
+        }
+
         NotificationManager getNotificationManager() {
             return mContext.getSystemService(NotificationManager.class);
         }
@@ -1233,6 +1241,7 @@
         mOwners = Preconditions.checkNotNull(injector.newOwners());
 
         mUserManager = Preconditions.checkNotNull(injector.getUserManager());
+        mUserManagerInternal = Preconditions.checkNotNull(injector.getUserManagerInternal());
         mIPackageManager = Preconditions.checkNotNull(injector.getIPackageManager());
 
         mLocalService = new LocalService();
@@ -1327,10 +1336,13 @@
         synchronized (this) {
             mOwners.load();
             findOwnerComponentIfNecessaryLocked();
+            migrateUserRestrictionsIfNecessaryLocked();
 
             // TODO PO may not have a class name either due to b/17652534.  Address that too.
 
             updateDeviceOwnerLocked();
+
+            // TODO Notify UM to update restrictions (?)
         }
     }
 
@@ -1350,14 +1362,113 @@
         if (doComponent == null) {
             Slog.e(LOG_TAG, "Device-owner isn't registered as device-admin");
         } else {
-            mOwners.setDeviceOwner(
+            mOwners.setDeviceOwnerWithRestrictionsMigrated(
                     doComponent,
                     mOwners.getDeviceOwnerName(),
-                    mOwners.getDeviceOwnerUserId());
+                    mOwners.getDeviceOwnerUserId(),
+                    !mOwners.getDeviceOwnerUserRestrictionsNeedsMigration());
             mOwners.writeDeviceOwner();
+            if (VERBOSE_LOG) {
+                Log.v(LOG_TAG, "Device owner component filled in");
+            }
         }
     }
 
+    /**
+     * We didn't use to persist user restrictions for each owners but only persisted in user
+     * manager.
+     */
+    private void migrateUserRestrictionsIfNecessaryLocked() {
+        boolean migrated = false;
+        // Migrate for the DO.  Basically all restrictions should be considered to be set by DO,
+        // except for the "system controlled" ones.
+        if (mOwners.getDeviceOwnerUserRestrictionsNeedsMigration()) {
+            if (VERBOSE_LOG) {
+                Log.v(LOG_TAG, "Migrating DO user restrictions");
+            }
+            migrated = true;
+
+            // Migrate user 0 restrictions to DO, except for "system" restrictions.
+            final ActiveAdmin deviceOwnerAdmin = getDeviceOwnerAdminLocked();
+
+            migrateUserRestrictionsForUser(UserHandle.SYSTEM, deviceOwnerAdmin,
+                    /* exceptionList =*/ UserRestrictionsUtils.SYSTEM_CONTROLLED_USER_RESTRICTIONS);
+
+            mOwners.setDeviceOwnerUserRestrictionsMigrated();
+        }
+
+        // Migrate for POs.  We have a few more exceptions.
+        final Set<String> normalExceptionList = Sets.newArraySet(
+                UserManager.DISALLOW_OUTGOING_CALLS,
+                UserManager.DISALLOW_SMS);
+        normalExceptionList.addAll(UserRestrictionsUtils.SYSTEM_CONTROLLED_USER_RESTRICTIONS);
+
+        final Set<String> managedExceptionList = new ArraySet<>(normalExceptionList.size() + 1);
+        managedExceptionList.addAll(normalExceptionList);
+        managedExceptionList.add(UserManager.DISALLOW_WALLPAPER);
+
+        for (UserInfo ui : mUserManager.getUsers()) {
+            final int userId = ui.id;
+            if (mOwners.getProfileOwnerUserRestrictionsNeedsMigration(userId)) {
+                if (userId != UserHandle.USER_SYSTEM) {
+                    if (VERBOSE_LOG) {
+                        Log.v(LOG_TAG, "Migrating PO user restrictions for user " + userId);
+                    }
+                    migrated = true;
+
+                    final ActiveAdmin profileOwnerAdmin = getProfileOwnerAdminLocked(userId);
+
+                    final Set<String> exceptionList =
+                            ui.isManagedProfile() ? managedExceptionList : normalExceptionList;
+
+                    migrateUserRestrictionsForUser(ui.getUserHandle(), profileOwnerAdmin,
+                            exceptionList);
+                }
+
+                mOwners.setProfileOwnerUserRestrictionsMigrated(userId);
+            }
+        }
+        if (VERBOSE_LOG && migrated) {
+            Log.v(LOG_TAG, "User restrictions migrated.");
+        }
+    }
+
+    private void migrateUserRestrictionsForUser(UserHandle user, ActiveAdmin admin,
+            Set<String> exceptionList) {
+        final Bundle origRestrictions = mUserManagerInternal.getBaseUserRestrictions(
+                user.getIdentifier());
+
+        final Bundle newSystemRestrictions = new Bundle();
+        final Bundle newOwnerRestrictions = new Bundle();
+
+        for (String key : origRestrictions.keySet()) {
+            if (!origRestrictions.getBoolean(key)) {
+                continue;
+            }
+            if (exceptionList.contains(key)) {
+                newSystemRestrictions.putBoolean(key, true);
+            } else {
+                newOwnerRestrictions.putBoolean(key, true);
+            }
+        }
+
+        if (VERBOSE_LOG) {
+            Log.v(LOG_TAG, "origRestrictions=" + origRestrictions);
+            Log.v(LOG_TAG, "newSystemRestrictions=" + newSystemRestrictions);
+            Log.v(LOG_TAG, "newOwnerRestrictions=" + newOwnerRestrictions);
+        }
+        mUserManagerInternal.setBaseUserRestrictionsByDpmsForMigration(user.getIdentifier(),
+                newSystemRestrictions);
+
+        if (admin != null) {
+            admin.ensureUserRestrictions().clear();
+            admin.ensureUserRestrictions().putAll(newOwnerRestrictions);
+        } else {
+            Slog.w(LOG_TAG, "ActiveAdmin for DO/PO not found. user=" + user.getIdentifier());
+        }
+        saveSettingsLocked(user.getIdentifier());
+    }
+
     private ComponentName findAdminComponentWithPackageLocked(String packageName, int userId) {
         final DevicePolicyData policy = getUserData(userId);
         final int n = policy.mAdminList.size();
@@ -1373,7 +1484,7 @@
                 nFound++;
             }
         }
-        if (nFound > 0) {
+        if (nFound > 1) {
             Slog.w(LOG_TAG, "Multiple DA found; assume the first one is DO.");
         }
         return found;
@@ -1636,6 +1747,9 @@
                 ? mInjector.getDevicePolicyFilePathForSystemUser() + DEVICE_POLICIES_XML
                 : new File(mInjector.environmentGetUserSystemDirectory(userHandle),
                         DEVICE_POLICIES_XML).getAbsolutePath();
+        if (VERBOSE_LOG) {
+            Log.v(LOG_TAG, "Opening " + base);
+        }
         return new JournaledFile(new File(base), new File(base + ".tmp"));
     }
 
@@ -1808,7 +1922,8 @@
                     try {
                         DeviceAdminInfo dai = findAdmin(
                                 ComponentName.unflattenFromString(name), userHandle);
-                        if (DBG && (UserHandle.getUserId(dai.getActivityInfo().applicationInfo.uid)
+                        if (VERBOSE_LOG
+                                && (UserHandle.getUserId(dai.getActivityInfo().applicationInfo.uid)
                                 != userHandle)) {
                             Slog.w(LOG_TAG, "findAdmin returned an incorrect uid "
                                     + dai.getActivityInfo().applicationInfo.uid + " for user "
@@ -1988,8 +2103,10 @@
             long token = mInjector.binderClearCallingIdentity();
             try {
                 String value = cameraDisabled ? "1" : "0";
-                if (DBG) Slog.v(LOG_TAG, "Change in camera state ["
-                        + cameraPropertyForUser + "] = " + value);
+                if (VERBOSE_LOG) {
+                    Slog.v(LOG_TAG, "Change in camera state ["
+                            + cameraPropertyForUser + "] = " + value);
+                }
                 mInjector.systemPropertiesSet(cameraPropertyForUser, value);
             } finally {
                 mInjector.binderRestoreCallingIdentity(token);
@@ -4513,8 +4630,10 @@
         }
         UserHandle callingUser = mInjector.binderGetCallingUserHandle();
         // Check if this is the profile owner who is calling
-        getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+        final ActiveAdmin admin =
+                getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
         synchronized (this) {
+            admin.userRestrictions = null;
             clearUserPoliciesLocked(callingUser);
             final int userId = callingUser.getIdentifier();
             mOwners.removeProfileOwner(userId);
@@ -4533,38 +4652,19 @@
 
         final long ident = mInjector.binderClearCallingIdentity();
         try {
-            clearUserRestrictions(userHandle);
             mIPackageManager.updatePermissionFlagsForAllApps(
                     PackageManager.FLAG_PERMISSION_POLICY_FIXED,
                     0  /* flagValues */, userHandle.getIdentifier());
+            // TODO This will not revert audio mute restrictions if they were set.  b/24981972
+            synchronized (mUserManagerInternal.getUserRestrictionsLock()) {
+                mUserManagerInternal.updateEffectiveUserRestrictionsRL(userHandle.getIdentifier());
+            }
         } catch (RemoteException re) {
         } finally {
             mInjector.binderRestoreCallingIdentity(ident);
         }
     }
 
-
-    private void clearUserRestrictions(UserHandle userHandle) {
-        Bundle userRestrictions = mUserManager.getUserRestrictions();
-        mUserManager.setUserRestrictions(new Bundle(), userHandle);
-        if (userRestrictions.getBoolean(UserManager.DISALLOW_ADJUST_VOLUME)) {
-            try {
-                mInjector.getIAudioService().setMasterMute(true, 0, mContext.getPackageName(),
-                        userHandle.getIdentifier());
-            } catch (RemoteException e) {
-                // Not much we can do here.
-            }
-        }
-        if (userRestrictions.getBoolean(UserManager.DISALLOW_UNMUTE_MICROPHONE)) {
-            try {
-                mInjector.getIAudioService().setMicrophoneMute(true, mContext.getPackageName(),
-                        userHandle.getIdentifier());
-            } catch (RemoteException e) {
-                // Not much we can do here.
-            }
-        }
-    }
-
     @Override
     public boolean hasUserSetupCompleted() {
         return hasUserSetupCompleted(UserHandle.getCallingUserId());
@@ -5503,95 +5603,123 @@
     }
 
     @Override
-    public void setUserRestriction(ComponentName who, String key, boolean enabled) {
+    public void setUserRestriction(ComponentName who, String key, boolean enabledFromThisOwner) {
         Preconditions.checkNotNull(who, "ComponentName is null");
         final int userHandle = mInjector.userHandleGetCallingUserId();
         final UserHandle user = new UserHandle(userHandle);
-        synchronized (this) {
-            ActiveAdmin activeAdmin =
-                    getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
-            boolean isDeviceOwner = isDeviceOwner(who);
-            if (!isDeviceOwner && userHandle != UserHandle.USER_SYSTEM
-                    && DEVICE_OWNER_USER_RESTRICTIONS.contains(key)) {
-                throw new SecurityException("Profile owners cannot set user restriction " + key);
-            }
-            if (IMMUTABLE_USER_RESTRICTIONS.contains(key)) {
-                throw new SecurityException("User restriction " + key + " cannot be changed");
-            }
-            boolean alreadyRestricted = mUserManager.hasUserRestriction(key, user);
+        synchronized (mUserManagerInternal.getUserRestrictionsLock()) {
+            synchronized (this) {
+                ActiveAdmin activeAdmin =
+                        getActiveAdminForCallerLocked(who,
+                                DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+                boolean isDeviceOwner = isDeviceOwner(who);
+                if (!isDeviceOwner && userHandle != UserHandle.USER_SYSTEM
+                        && DEVICE_OWNER_USER_RESTRICTIONS.contains(key)) {
+                    throw new SecurityException(
+                            "Profile owners cannot set user restriction " + key);
+                }
+                if (IMMUTABLE_USER_RESTRICTIONS.contains(key)) {
+                    throw new SecurityException("User restriction " + key + " cannot be changed");
+                }
 
-            long id = mInjector.binderClearCallingIdentity();
-            try {
-                if (enabled && !alreadyRestricted) {
-                    if (UserManager.DISALLOW_UNMUTE_MICROPHONE.equals(key)) {
-                        mInjector.getIAudioService()
-                                .setMicrophoneMute(true, mContext.getPackageName(), userHandle);
-                    } else if (UserManager.DISALLOW_ADJUST_VOLUME.equals(key)) {
-                        mInjector.getIAudioService()
-                                .setMasterMute(true, 0, mContext.getPackageName(), userHandle);
-                    } else if (UserManager.DISALLOW_CONFIG_WIFI.equals(key)) {
-                        mInjector.settingsSecurePutIntForUser(
-                                Settings.Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, 0,
-                                userHandle);
-                    } else if (UserManager.DISALLOW_SHARE_LOCATION.equals(key)) {
-                        mInjector.settingsSecurePutIntForUser(
-                                Settings.Secure.LOCATION_MODE, Settings.Secure.LOCATION_MODE_OFF,
-                                userHandle);
-                        mInjector.settingsSecurePutStringForUser(
-                                Settings.Secure.LOCATION_PROVIDERS_ALLOWED, "",
-                                userHandle);
-                    } else if (UserManager.DISALLOW_DEBUGGING_FEATURES.equals(key)) {
-                        // Only disable adb if changing for system user, since it is global
-                        // TODO: should this be admin user?
-                        if (userHandle == UserHandle.USER_SYSTEM) {
+                final long id = mInjector.binderClearCallingIdentity();
+                try {
+                    // Original value.
+                    final boolean alreadyRestricted = mUserManager.hasUserRestriction(key, user);
+
+                    // Save the restriction to ActiveAdmin.
+                    // TODO When DO sets a restriction, it'll always be treated as device-wide.
+                    // If there'll be a policy that can be set by both, we'll need scoping support,
+                    // and need to have another Bundle in DO active admin to hold restrictions as
+                    // PO.
+                    activeAdmin.ensureUserRestrictions().putBoolean(key, enabledFromThisOwner);
+                    saveSettingsLocked(userHandle);
+
+                    // Tell UserManager the new value.  Note this needs to be done before calling
+                    // into AudioService, because AS will check AppOps that'll be updated by UM.
+                    if (isDeviceOwner) {
+                        mUserManagerInternal.updateEffectiveUserRestrictionsForAllUsersRL();
+                    } else {
+                        mUserManagerInternal.updateEffectiveUserRestrictionsRL(userHandle);
+                    }
+
+                    // New value.
+                    final boolean enabled = mUserManager.hasUserRestriction(key, user);
+
+                    // TODO The rest of the code should move to UserManagerService.
+
+                    if (enabled && !alreadyRestricted) {
+                        if (UserManager.DISALLOW_UNMUTE_MICROPHONE.equals(key)) {
+                            mInjector.getIAudioService()
+                                    .setMicrophoneMute(true, mContext.getPackageName(), userHandle);
+                        } else if (UserManager.DISALLOW_ADJUST_VOLUME.equals(key)) {
+                            mInjector.getIAudioService()
+                                    .setMasterMute(true, 0, mContext.getPackageName(), userHandle);
+                        } else if (UserManager.DISALLOW_CONFIG_WIFI.equals(key)) {
+                            mInjector.settingsSecurePutIntForUser(
+                                    Settings.Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, 0,
+                                    userHandle);
+                        } else if (UserManager.DISALLOW_SHARE_LOCATION.equals(key)) {
+                            mInjector.settingsSecurePutIntForUser(
+                                    Settings.Secure.LOCATION_MODE,
+                                    Settings.Secure.LOCATION_MODE_OFF,
+                                    userHandle);
+                            mInjector.settingsSecurePutStringForUser(
+                                    Settings.Secure.LOCATION_PROVIDERS_ALLOWED, "",
+                                    userHandle);
+                        } else if (UserManager.DISALLOW_DEBUGGING_FEATURES.equals(key)) {
+                            // Only disable adb if changing for system user, since it is global
+                            // TODO: should this be admin user?
+                            if (userHandle == UserHandle.USER_SYSTEM) {
+                                mInjector.settingsGlobalPutStringForUser(
+                                        Settings.Global.ADB_ENABLED, "0", userHandle);
+                            }
+                        } else if (UserManager.ENSURE_VERIFY_APPS.equals(key)) {
                             mInjector.settingsGlobalPutStringForUser(
-                                    Settings.Global.ADB_ENABLED, "0", userHandle);
+                                    Settings.Global.PACKAGE_VERIFIER_ENABLE, "1",
+                                    userHandle);
+                            mInjector.settingsGlobalPutStringForUser(
+                                    Settings.Global.PACKAGE_VERIFIER_INCLUDE_ADB, "1",
+                                    userHandle);
+                        } else if (UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES.equals(key)) {
+                            mInjector.settingsSecurePutIntForUser(
+                                    Settings.Secure.INSTALL_NON_MARKET_APPS, 0,
+                                    userHandle);
                         }
-                    } else if (UserManager.ENSURE_VERIFY_APPS.equals(key)) {
-                        mInjector.settingsGlobalPutStringForUser(
-                                Settings.Global.PACKAGE_VERIFIER_ENABLE, "1",
-                                userHandle);
-                        mInjector.settingsGlobalPutStringForUser(
-                                Settings.Global.PACKAGE_VERIFIER_INCLUDE_ADB, "1",
-                                userHandle);
-                    } else if (UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES.equals(key)) {
-                        mInjector.settingsSecurePutIntForUser(
-                                Settings.Secure.INSTALL_NON_MARKET_APPS, 0,
-                                userHandle);
                     }
-                }
-                mUserManager.setUserRestriction(key, enabled, user);
-                activeAdmin.ensureUserRestrictions().putBoolean(key, enabled);
-                saveSettingsLocked(userHandle);
 
-                if (enabled != alreadyRestricted) {
-                    if (UserManager.DISALLOW_SHARE_LOCATION.equals(key)) {
-                        // Send out notifications however as some clients may want to reread the
-                        // value which actually changed due to a restriction having been applied.
-                        final String property = Settings.Secure.SYS_PROP_SETTING_VERSION;
-                        long version = mInjector.systemPropertiesGetLong(property, 0) + 1;
-                        mInjector.systemPropertiesSet(property, Long.toString(version));
+                    if (enabled != alreadyRestricted) {
+                        if (UserManager.DISALLOW_SHARE_LOCATION.equals(key)) {
+                            // Send out notifications however as some clients may want to reread the
+                            // value which actually changed due to a restriction having been
+                            // applied.
+                            final String property = Settings.Secure.SYS_PROP_SETTING_VERSION;
+                            long version = mInjector.systemPropertiesGetLong(property, 0) + 1;
+                            mInjector.systemPropertiesSet(property, Long.toString(version));
 
-                        final String name = Settings.Secure.LOCATION_PROVIDERS_ALLOWED;
-                        Uri url = Uri.withAppendedPath(Settings.Secure.CONTENT_URI, name);
-                        mContext.getContentResolver().notifyChange(url, null, true, userHandle);
+                            final String name = Settings.Secure.LOCATION_PROVIDERS_ALLOWED;
+                            Uri url = Uri.withAppendedPath(Settings.Secure.CONTENT_URI, name);
+                            mContext.getContentResolver().notifyChange(url, null, true, userHandle);
+                        }
                     }
-                }
-                if (!enabled && alreadyRestricted) {
-                    if (UserManager.DISALLOW_UNMUTE_MICROPHONE.equals(key)) {
-                        mInjector.getIAudioService()
-                                .setMicrophoneMute(false, mContext.getPackageName(), userHandle);
-                    } else if (UserManager.DISALLOW_ADJUST_VOLUME.equals(key)) {
-                        mInjector.getIAudioService()
-                                .setMasterMute(false, 0, mContext.getPackageName(), userHandle);
+                    if (!enabled && alreadyRestricted) {
+                        if (UserManager.DISALLOW_UNMUTE_MICROPHONE.equals(key)) {
+                            mInjector.getIAudioService()
+                                    .setMicrophoneMute(false, mContext.getPackageName(),
+                                            userHandle);
+                        } else if (UserManager.DISALLOW_ADJUST_VOLUME.equals(key)) {
+                            mInjector.getIAudioService()
+                                    .setMasterMute(false, 0, mContext.getPackageName(), userHandle);
+                        }
                     }
+                } catch (RemoteException re) {
+                    Slog.e(LOG_TAG, "Failed to talk to AudioService.", re);
+                } finally {
+                    mInjector.binderRestoreCallingIdentity(id);
                 }
-            } catch (RemoteException re) {
-                Slog.e(LOG_TAG, "Failed to talk to AudioService.", re);
-            } finally {
-                mInjector.binderRestoreCallingIdentity(id);
+
+                sendChangedNotification(userHandle);
             }
-            sendChangedNotification(userHandle);
         }
     }
 
@@ -5650,7 +5778,7 @@
             long id = mInjector.binderClearCallingIdentity();
 
             try {
-                if (DBG) {
+                if (VERBOSE_LOG) {
                     Slog.v(LOG_TAG, "installing " + packageName + " for "
                             + userId);
                 }
@@ -5705,7 +5833,9 @@
                         0, // no flags
                         primaryUser.id);
 
-                if (DBG) Slog.d(LOG_TAG, "Enabling system activities: " + activitiesToEnable);
+                if (VERBOSE_LOG) {
+                    Slog.d(LOG_TAG, "Enabling system activities: " + activitiesToEnable);
+                }
                 int numberOfAppsInstalled = 0;
                 if (activitiesToEnable != null) {
                     for (ResolveInfo info : activitiesToEnable) {
@@ -6275,7 +6405,8 @@
         }
     }
 
-    private final class LocalService extends DevicePolicyManagerInternal {
+    @VisibleForTesting
+    final class LocalService extends DevicePolicyManagerInternal {
         private List<OnCrossProfileWidgetProvidersChangeListener> mWidgetProviderListeners;
 
         @Override
@@ -6322,6 +6453,30 @@
             }
         }
 
+        @Override
+        public Bundle getComposedUserRestrictions(int userId, Bundle inBundle) {
+            synchronized (DevicePolicyManagerService.this) {
+                final ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
+                final ActiveAdmin profileOwner = getProfileOwnerAdminLocked(userId);
+
+                final Bundle deviceOwnerRestrictions =
+                        deviceOwner == null ? null : deviceOwner.userRestrictions;
+                final Bundle profileOwnerRestrictions =
+                        profileOwner == null ? null : profileOwner.userRestrictions;
+
+                if (deviceOwnerRestrictions == null && profileOwnerRestrictions == null) {
+                    // No restrictions to merge.
+                    return inBundle;
+                }
+
+                final Bundle composed = new Bundle(inBundle);
+                UserRestrictionsUtils.merge(composed, deviceOwnerRestrictions);
+                UserRestrictionsUtils.merge(composed, profileOwnerRestrictions);
+
+                return composed;
+            }
+        }
+
         private void notifyCrossProfileProvidersChanged(int userId, List<String> packages) {
             final List<OnCrossProfileWidgetProvidersChangeListener> listeners;
             synchronized (DevicePolicyManagerService.this) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
index 799267d..12b3775 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
@@ -30,7 +30,6 @@
 import android.util.Xml;
 
 import com.android.internal.util.FastXmlSerializer;
-import com.android.internal.util.Preconditions;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -75,6 +74,7 @@
     private static final String ATTR_PACKAGE = "package";
     private static final String ATTR_COMPONENT_NAME = "component";
     private static final String ATTR_USERID = "userId";
+    private static final String ATTR_USER_RESTRICTIONS_MIGRATED = "userRestrictionsMigrated";
 
     private static final String TAG_SYSTEM_UPDATE_POLICY = "system-update-policy";
 
@@ -155,7 +155,16 @@
             Slog.e(TAG, "Invalid user id for device owner user: " + userId);
             return;
         }
-        mDeviceOwner = new OwnerInfo(ownerName, admin);
+        // For a newly set DO, there's no need for migration.
+        setDeviceOwnerWithRestrictionsMigrated(admin, ownerName, userId,
+                /* userRestrictionsMigrated =*/ true);
+    }
+
+    // Note this should be only called during migration.  Normally when DO is set,
+    // userRestrictionsMigrated should always be true.
+    void setDeviceOwnerWithRestrictionsMigrated(ComponentName admin, String ownerName, int userId,
+            boolean userRestrictionsMigrated) {
+        mDeviceOwner = new OwnerInfo(ownerName, admin, userRestrictionsMigrated);
         mDeviceOwnerUserId = userId;
     }
 
@@ -165,7 +174,9 @@
     }
 
     void setProfileOwner(ComponentName admin, String ownerName, int userId) {
-        mProfileOwners.put(userId, new OwnerInfo(ownerName, admin));
+        // For a newly set PO, there's no need for migration.
+        mProfileOwners.put(userId, new OwnerInfo(ownerName, admin,
+                /* userRestrictionsMigrated =*/ true));
     }
 
     void removeProfileOwner(int userId) {
@@ -207,6 +218,38 @@
         return mDeviceOwner != null;
     }
 
+    /**
+     * @return true if user restrictions need to be migrated for DO.
+     */
+    boolean getDeviceOwnerUserRestrictionsNeedsMigration() {
+        return mDeviceOwner != null && !mDeviceOwner.userRestrictionsMigrated;
+    }
+
+    /**
+     * @return true if user restrictions need to be migrated for PO.
+     */
+    boolean getProfileOwnerUserRestrictionsNeedsMigration(int userId) {
+        OwnerInfo profileOwner = mProfileOwners.get(userId);
+        return profileOwner != null && !profileOwner.userRestrictionsMigrated;
+    }
+
+    /** Sets the user restrictions migrated flag, and also writes to the file. */
+    void setDeviceOwnerUserRestrictionsMigrated() {
+        if (mDeviceOwner != null) {
+            mDeviceOwner.userRestrictionsMigrated = true;
+        }
+        writeDeviceOwner();
+    }
+
+    /** Sets the user restrictions migrated flag, and also writes to the file.  */
+    void setProfileOwnerUserRestrictionsMigrated(int userId) {
+        OwnerInfo profileOwner = mProfileOwners.get(userId);
+        if (profileOwner != null) {
+            profileOwner.userRestrictionsMigrated = true;
+        }
+        writeProfileOwner(userId);
+    }
+
     private boolean readLegacyOwnerFile(File file) {
         if (!file.exists()) {
             // Already migrated or the device has no owners.
@@ -226,7 +269,8 @@
                 if (tag.equals(TAG_DEVICE_OWNER)) {
                     String name = parser.getAttributeValue(null, ATTR_NAME);
                     String packageName = parser.getAttributeValue(null, ATTR_PACKAGE);
-                    mDeviceOwner = new OwnerInfo(name, packageName);
+                    mDeviceOwner = new OwnerInfo(name, packageName,
+                            /* userRestrictionsMigrated =*/ false);
                     mDeviceOwnerUserId = UserHandle.USER_SYSTEM;
                 } else if (tag.equals(TAG_DEVICE_INITIALIZER)) {
                     // Deprecated tag
@@ -241,7 +285,8 @@
                         ComponentName admin = ComponentName.unflattenFromString(
                                 profileOwnerComponentStr);
                         if (admin != null) {
-                            profileOwnerInfo = new OwnerInfo(profileOwnerName, admin);
+                            profileOwnerInfo = new OwnerInfo(profileOwnerName, admin,
+                                /* userRestrictionsMigrated =*/ false);
                         } else {
                             // This shouldn't happen but switch from package name -> component name
                             // might have written bad device owner files. b/17652534
@@ -250,7 +295,8 @@
                         }
                     }
                     if (profileOwnerInfo == null) {
-                        profileOwnerInfo = new OwnerInfo(profileOwnerName, profileOwnerPackageName);
+                        profileOwnerInfo = new OwnerInfo(profileOwnerName, profileOwnerPackageName,
+                                /* userRestrictionsMigrated =*/ false);
                     }
                     mProfileOwners.put(userId, profileOwnerInfo);
                 } else if (TAG_SYSTEM_UPDATE_POLICY.equals(tag)) {
@@ -503,21 +549,24 @@
         }
     }
 
-    private static class OwnerInfo {
+    static class OwnerInfo {
         public final String name;
         public final String packageName;
         public final ComponentName admin;
+        public boolean userRestrictionsMigrated;
 
-        public OwnerInfo(String name, String packageName) {
+        public OwnerInfo(String name, String packageName, boolean userRestrictionsMigrated) {
             this.name = name;
             this.packageName = packageName;
             this.admin = new ComponentName(packageName, "");
+            this.userRestrictionsMigrated = userRestrictionsMigrated;
         }
 
-        public OwnerInfo(String name, ComponentName admin) {
+        public OwnerInfo(String name, ComponentName admin, boolean userRestrictionsMigrated) {
             this.name = name;
             this.admin = admin;
             this.packageName = admin.getPackageName();
+            this.userRestrictionsMigrated = userRestrictionsMigrated;
         }
 
         public void writeToXml(XmlSerializer out, String tag) throws IOException {
@@ -529,6 +578,8 @@
             if (admin != null) {
                 out.attribute(null, ATTR_COMPONENT_NAME, admin.flattenToString());
             }
+            out.attribute(null, ATTR_USER_RESTRICTIONS_MIGRATED,
+                    String.valueOf(userRestrictionsMigrated));
             out.endTag(null, tag);
         }
 
@@ -537,12 +588,16 @@
             final String name = parser.getAttributeValue(null, ATTR_NAME);
             final String componentName =
                     parser.getAttributeValue(null, ATTR_COMPONENT_NAME);
+            final String userRestrictionsMigratedStr =
+                    parser.getAttributeValue(null, ATTR_USER_RESTRICTIONS_MIGRATED);
+            final boolean userRestrictionsMigrated =
+                    ("true".equals(userRestrictionsMigratedStr));
 
             // Has component name?  If so, return [name, component]
             if (componentName != null) {
                 final ComponentName admin = ComponentName.unflattenFromString(componentName);
                 if (admin != null) {
-                    return new OwnerInfo(name, admin);
+                    return new OwnerInfo(name, admin, userRestrictionsMigrated);
                 } else {
                     // This shouldn't happen but switch from package name -> component name
                     // might have written bad device owner files. b/17652534
@@ -552,7 +607,7 @@
             }
 
             // Else, build with [name, package]
-            return new OwnerInfo(name, packageName);
+            return new OwnerInfo(name, packageName, userRestrictionsMigrated);
         }
 
         public void dump(String prefix, PrintWriter pw) {
diff --git a/services/tests/servicestests/assets/DevicePolicyManagerServiceMigrationTest/legacy_device_owner.xml b/services/tests/servicestests/assets/DevicePolicyManagerServiceMigrationTest/legacy_device_owner.xml
new file mode 100644
index 0000000..9564969
--- /dev/null
+++ b/services/tests/servicestests/assets/DevicePolicyManagerServiceMigrationTest/legacy_device_owner.xml
@@ -0,0 +1,4 @@
+<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+<device-owner package="com.android.frameworks.servicestests" />
+<profile-owner package="com.android.frameworks.servicestests" name="0" userId="10" component="com.android.frameworks.servicestests/com.android.server.devicepolicy.DummyDeviceAdmins$Admin2" />
+<profile-owner package="com.android.frameworks.servicestests" name="0" userId="11" component="com.android.frameworks.servicestests/com.android.server.devicepolicy.DummyDeviceAdmins$Admin3" />
diff --git a/services/tests/servicestests/assets/DevicePolicyManagerServiceMigrationTest/legacy_device_policies.xml b/services/tests/servicestests/assets/DevicePolicyManagerServiceMigrationTest/legacy_device_policies.xml
new file mode 100644
index 0000000..48cb814
--- /dev/null
+++ b/services/tests/servicestests/assets/DevicePolicyManagerServiceMigrationTest/legacy_device_policies.xml
@@ -0,0 +1,7 @@
+<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+<policies setup-complete="true">
+    <admin name="com.google.android.gms/com.google.android.gms.mdm.receivers.MdmDeviceAdminReceiver">
+    </admin>
+    <admin name="com.android.frameworks.servicestests/com.android.server.devicepolicy.DummyDeviceAdmins$Admin1">
+    </admin>
+</policies>
diff --git a/services/tests/servicestests/assets/DevicePolicyManagerServiceMigrationTest/legacy_device_policies_10.xml b/services/tests/servicestests/assets/DevicePolicyManagerServiceMigrationTest/legacy_device_policies_10.xml
new file mode 100644
index 0000000..6b53840
--- /dev/null
+++ b/services/tests/servicestests/assets/DevicePolicyManagerServiceMigrationTest/legacy_device_policies_10.xml
@@ -0,0 +1,5 @@
+<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+<policies setup-complete="true">
+    <admin name="com.android.frameworks.servicestests/com.android.server.devicepolicy.DummyDeviceAdmins$Admin2">
+    </admin>
+</policies>
diff --git a/services/tests/servicestests/assets/DevicePolicyManagerServiceMigrationTest/legacy_device_policies_11.xml b/services/tests/servicestests/assets/DevicePolicyManagerServiceMigrationTest/legacy_device_policies_11.xml
new file mode 100644
index 0000000..2bcc5d4
--- /dev/null
+++ b/services/tests/servicestests/assets/DevicePolicyManagerServiceMigrationTest/legacy_device_policies_11.xml
@@ -0,0 +1,5 @@
+<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+<policies setup-complete="true">
+    <admin name="com.android.frameworks.servicestests/com.android.server.devicepolicy.DummyDeviceAdmins$Admin3">
+    </admin>
+</policies>
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java
new file mode 100644
index 0000000..dfa9f8f
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.devicepolicy;
+
+import com.android.server.LocalServices;
+import com.android.server.SystemService;
+import com.android.server.devicepolicy.DevicePolicyManagerServiceTestable.OwnersTestable;
+
+import android.app.admin.DevicePolicyManager;
+import android.app.admin.DevicePolicyManagerInternal;
+import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.Pair;
+
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.when;
+
+public class DevicePolicyManagerServiceMigrationTest extends DpmTestBase {
+    private DpmMockContext mContext;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        mContext = getContext();
+
+        when(mContext.packageManager.hasSystemFeature(eq(PackageManager.FEATURE_DEVICE_ADMIN)))
+                .thenReturn(true);
+    }
+
+    public void testMigration() throws Exception {
+        final File user10dir = mMockContext.addUser(10, 0);
+        final File user11dir = mMockContext.addUser(11, UserInfo.FLAG_MANAGED_PROFILE);
+        final File user12dir = mMockContext.addUser(12, 0);
+
+        setUpPackageManagerForAdmin(admin1, DpmMockContext.CALLER_SYSTEM_USER_UID);
+        setUpPackageManagerForAdmin(admin2, UserHandle.getUid(10, 123));
+        setUpPackageManagerForAdmin(admin3, UserHandle.getUid(11, 456));
+
+        // Create the legacy owners & policies file.
+        DpmTestUtils.writeToFile(
+                (new File(mContext.dataDir, OwnersTestable.LEGACY_FILE)).getAbsoluteFile(),
+                DpmTestUtils.readAsset(mRealTestContext,
+                        "DevicePolicyManagerServiceMigrationTest/legacy_device_owner.xml"));
+
+        DpmTestUtils.writeToFile(
+                (new File(mContext.systemUserDataDir, "device_policies.xml")).getAbsoluteFile(),
+                DpmTestUtils.readAsset(mRealTestContext,
+                        "DevicePolicyManagerServiceMigrationTest/legacy_device_policies.xml"));
+
+        DpmTestUtils.writeToFile(
+                (new File(user10dir, "device_policies.xml")).getAbsoluteFile(),
+                DpmTestUtils.readAsset(mRealTestContext,
+                        "DevicePolicyManagerServiceMigrationTest/legacy_device_policies_10.xml"));
+        DpmTestUtils.writeToFile(
+                (new File(user11dir, "device_policies.xml")).getAbsoluteFile(),
+                DpmTestUtils.readAsset(mRealTestContext,
+                        "DevicePolicyManagerServiceMigrationTest/legacy_device_policies_11.xml"));
+
+        // Set up UserManager
+        when(mMockContext.userManagerInternal.getBaseUserRestrictions(
+                eq(UserHandle.USER_SYSTEM))).thenReturn(DpmTestUtils.newRestrictions(
+                UserManager.DISALLOW_ADD_USER,
+                UserManager.DISALLOW_RECORD_AUDIO));
+
+        when(mMockContext.userManagerInternal.getBaseUserRestrictions(
+                eq(10))).thenReturn(DpmTestUtils.newRestrictions(
+                UserManager.DISALLOW_REMOVE_USER,
+                UserManager.DISALLOW_SMS,
+                UserManager.DISALLOW_OUTGOING_CALLS,
+                UserManager.DISALLOW_WALLPAPER,
+                UserManager.DISALLOW_RECORD_AUDIO));
+
+        when(mMockContext.userManagerInternal.getBaseUserRestrictions(
+                eq(11))).thenReturn(DpmTestUtils.newRestrictions(
+                UserManager.DISALLOW_REMOVE_USER,
+                UserManager.DISALLOW_SMS,
+                UserManager.DISALLOW_OUTGOING_CALLS,
+                UserManager.DISALLOW_WALLPAPER,
+                UserManager.DISALLOW_RECORD_AUDIO));
+
+        final Map<Integer, Bundle> newBaseRestrictions = new HashMap<>();
+
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) throws Throwable {
+                Integer userId = (Integer) invocation.getArguments()[0];
+                Bundle bundle = (Bundle) invocation.getArguments()[1];
+
+                newBaseRestrictions.put(userId, bundle);
+
+                return null;
+            }
+        }).when(mContext.userManagerInternal).setBaseUserRestrictionsByDpmsForMigration(
+                anyInt(), any(Bundle.class));
+
+        // Initialize DPM/DPMS and let it migrate the persisted information.
+        // (Need clearCallingIdentity() to pass permission checks.)
+
+        final DevicePolicyManagerServiceTestable dpms;
+
+        final long ident = mContext.binder.clearCallingIdentity();
+        try {
+            LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
+
+            dpms = new DevicePolicyManagerServiceTestable(mContext, dataDir);
+
+            dpms.systemReady(SystemService.PHASE_LOCK_SETTINGS_READY);
+            dpms.systemReady(SystemService.PHASE_BOOT_COMPLETED);
+        } finally {
+            mContext.binder.restoreCallingIdentity(ident);
+        }
+
+        // Now all information should be migrated.
+        assertFalse(dpms.mOwners.getDeviceOwnerUserRestrictionsNeedsMigration());
+        assertFalse(dpms.mOwners.getProfileOwnerUserRestrictionsNeedsMigration(10));
+        assertFalse(dpms.mOwners.getProfileOwnerUserRestrictionsNeedsMigration(11));
+        assertFalse(dpms.mOwners.getProfileOwnerUserRestrictionsNeedsMigration(12));
+
+        // Check the new base restrictions.
+        DpmTestUtils.assertRestrictions(
+                DpmTestUtils.newRestrictions(
+                        UserManager.DISALLOW_RECORD_AUDIO
+                ),
+                newBaseRestrictions.get(UserHandle.USER_SYSTEM));
+
+        DpmTestUtils.assertRestrictions(
+                DpmTestUtils.newRestrictions(
+                        UserManager.DISALLOW_SMS,
+                        UserManager.DISALLOW_OUTGOING_CALLS,
+                        UserManager.DISALLOW_RECORD_AUDIO
+                ),
+                newBaseRestrictions.get(10));
+
+        DpmTestUtils.assertRestrictions(
+                DpmTestUtils.newRestrictions(
+                        UserManager.DISALLOW_SMS,
+                        UserManager.DISALLOW_OUTGOING_CALLS,
+                        UserManager.DISALLOW_WALLPAPER,
+                        UserManager.DISALLOW_RECORD_AUDIO
+                ),
+                newBaseRestrictions.get(11));
+
+        // Check the new owner restrictions.
+        DpmTestUtils.assertRestrictions(
+                DpmTestUtils.newRestrictions(
+                        UserManager.DISALLOW_ADD_USER
+                ),
+                dpms.getDeviceOwnerAdminLocked().ensureUserRestrictions());
+
+        DpmTestUtils.assertRestrictions(
+                DpmTestUtils.newRestrictions(
+                        UserManager.DISALLOW_REMOVE_USER,
+                        UserManager.DISALLOW_WALLPAPER
+                ),
+                dpms.getProfileOwnerAdminLocked(10).ensureUserRestrictions());
+
+        DpmTestUtils.assertRestrictions(
+                DpmTestUtils.newRestrictions(
+                        UserManager.DISALLOW_REMOVE_USER
+                ),
+                dpms.getProfileOwnerAdminLocked(11).ensureUserRestrictions());
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
index b109e7b..2c01b8a 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
@@ -27,6 +27,7 @@
 import android.os.PowerManagerInternal;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.os.UserManagerInternal;
 import android.view.IWindowManager;
 
 import java.io.File;
@@ -107,6 +108,11 @@
         }
 
         @Override
+        UserManagerInternal getUserManagerInternal() {
+            return context.userManagerInternal;
+        }
+
+        @Override
         PowerManagerInternal getPowerManagerInternal() {
             return context.powerManagerInternal;
         }
@@ -153,7 +159,7 @@
 
         @Override
         String getDevicePolicyFilePathForSystemUser() {
-            return context.systemUserDataDir.getAbsolutePath();
+            return context.systemUserDataDir.getAbsolutePath() + "/";
         }
 
         @Override
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index d6a60c7..727858b 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -27,7 +27,6 @@
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
-import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.os.Bundle;
@@ -70,9 +69,6 @@
     private DpmMockContext mContext;
     public DevicePolicyManager dpm;
     public DevicePolicyManagerServiceTestable dpms;
-    public ComponentName admin1;
-    public ComponentName admin2;
-    public ComponentName admin3;
 
     @Override
     protected void setUp() throws Exception {
@@ -85,10 +81,6 @@
 
         initializeDpms();
 
-        admin1 = new ComponentName(mRealTestContext, DummyDeviceAdmins.Admin1.class);
-        admin2 = new ComponentName(mRealTestContext, DummyDeviceAdmins.Admin2.class);
-        admin3 = new ComponentName(mRealTestContext, DummyDeviceAdmins.Admin3.class);
-
         setUpPackageManagerForAdmin(admin1, DpmMockContext.CALLER_UID);
         setUpPackageManagerForAdmin(admin2, DpmMockContext.CALLER_UID);
         setUpPackageManagerForAdmin(admin3, DpmMockContext.CALLER_UID);
@@ -113,67 +105,6 @@
         }
     }
 
-    private void setUpPackageManagerForAdmin(ComponentName admin, int packageUid) throws Exception {
-        setUpPackageManagerForAdmin(admin, packageUid,
-                PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED);
-    }
-
-    private void setUpPackageManagerForAdmin(ComponentName admin, int packageUid,
-            int enabledSetting) throws Exception {
-
-        // Set up queryBroadcastReceivers().
-
-        final Intent resolveIntent = new Intent();
-        resolveIntent.setComponent(admin);
-        final List<ResolveInfo> realResolveInfo =
-                mRealTestContext.getPackageManager().queryBroadcastReceivers(
-                        resolveIntent,
-                        PackageManager.GET_META_DATA);
-        assertNotNull(realResolveInfo);
-        assertEquals(1, realResolveInfo.size());
-
-        // We need to change AI, so set a clone.
-        realResolveInfo.set(0, DpmTestUtils.cloneParcelable(realResolveInfo.get(0)));
-
-        // We need to rewrite the UID in the activity info.
-        realResolveInfo.get(0).activityInfo.applicationInfo.uid = packageUid;
-
-        doReturn(realResolveInfo).when(mContext.packageManager).queryBroadcastReceivers(
-                MockUtils.checkIntentComponent(admin),
-                eq(PackageManager.GET_META_DATA
-                        | PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS),
-                eq(UserHandle.getUserId(packageUid)));
-
-        // Set up getApplicationInfo().
-
-        final ApplicationInfo ai = DpmTestUtils.cloneParcelable(
-                mRealTestContext.getPackageManager().getApplicationInfo(
-                        admin1.getPackageName(),
-                        PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS));
-
-        ai.enabledSetting = enabledSetting;
-        ai.uid = packageUid;
-
-        doReturn(ai).when(mContext.ipackageManager).getApplicationInfo(
-                eq(admin1.getPackageName()),
-                eq(PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS),
-                eq(UserHandle.getUserId(packageUid)));
-
-        // Set up getPackageInfo().
-
-        final PackageInfo pi = DpmTestUtils.cloneParcelable(
-                mRealTestContext.getPackageManager().getPackageInfo(
-                        admin1.getPackageName(), 0));
-        assertTrue(pi.applicationInfo.flags != 0);
-
-        pi.applicationInfo.uid = packageUid;
-
-        doReturn(pi).when(mContext.ipackageManager).getPackageInfo(
-                eq(admin1.getPackageName()),
-                eq(0),
-                eq(UserHandle.getUserId(packageUid)));
-    }
-
     private void setUpUserManager() {
         // Emulate UserManager.set/getApplicationRestriction().
         final Map<Pair<String, UserHandle>, Bundle> appRestrictions = new HashMap<>();
@@ -220,7 +151,7 @@
         assertTrue(dpm.setProfileOwner(admin, "owner-name", DpmMockContext.CALLER_USER_HANDLE));
 
         // Check
-        assertEquals(admin1, dpm.getProfileOwnerAsUser(DpmMockContext.CALLER_USER_HANDLE));
+        assertEquals(admin, dpm.getProfileOwnerAsUser(DpmMockContext.CALLER_USER_HANDLE));
     }
 
     public void testHasNoFeature() throws Exception {
@@ -743,6 +674,8 @@
         assertEquals("", dpms.getDeviceOwner().getClassName());
 
         // Then create a new DPMS to have it load the settings from files.
+        when(mContext.userManager.getUserRestrictions(any(UserHandle.class)))
+                .thenReturn(new Bundle());
         initializeDpms();
 
         // Now the DO component name is a full name.
@@ -802,32 +735,33 @@
         assertTrue(dpm.setDeviceOwner(admin1, "owner-name",
                 UserHandle.USER_SYSTEM));
 
-        assertFalse(dpms.getDeviceOwnerAdminLocked().ensureUserRestrictions()
-                .getBoolean(UserManager.DISALLOW_SMS));
-        assertFalse(dpms.getDeviceOwnerAdminLocked().ensureUserRestrictions()
-                .getBoolean(UserManager.DISALLOW_OUTGOING_CALLS));
+        DpmTestUtils.assertRestrictions(
+                DpmTestUtils.newRestrictions(),
+                dpms.getDeviceOwnerAdminLocked().ensureUserRestrictions()
+        );
 
         dpm.addUserRestriction(admin1, UserManager.DISALLOW_SMS);
         dpm.addUserRestriction(admin1, UserManager.DISALLOW_OUTGOING_CALLS);
 
-        assertTrue(dpms.getDeviceOwnerAdminLocked().ensureUserRestrictions()
-                .getBoolean(UserManager.DISALLOW_SMS));
-        assertTrue(dpms.getDeviceOwnerAdminLocked().ensureUserRestrictions()
-                .getBoolean(UserManager.DISALLOW_OUTGOING_CALLS));
+        DpmTestUtils.assertRestrictions(
+                DpmTestUtils.newRestrictions(
+                        UserManager.DISALLOW_SMS, UserManager.DISALLOW_OUTGOING_CALLS),
+                dpms.getDeviceOwnerAdminLocked().ensureUserRestrictions()
+        );
 
         dpm.clearUserRestriction(admin1, UserManager.DISALLOW_SMS);
 
-        assertFalse(dpms.getDeviceOwnerAdminLocked().ensureUserRestrictions()
-                .getBoolean(UserManager.DISALLOW_SMS));
-        assertTrue(dpms.getDeviceOwnerAdminLocked().ensureUserRestrictions()
-                .getBoolean(UserManager.DISALLOW_OUTGOING_CALLS));
+        DpmTestUtils.assertRestrictions(
+                DpmTestUtils.newRestrictions(UserManager.DISALLOW_OUTGOING_CALLS),
+                dpms.getDeviceOwnerAdminLocked().ensureUserRestrictions()
+        );
 
         dpm.clearUserRestriction(admin1, UserManager.DISALLOW_OUTGOING_CALLS);
 
-        assertFalse(dpms.getDeviceOwnerAdminLocked().ensureUserRestrictions()
-                .getBoolean(UserManager.DISALLOW_SMS));
-        assertFalse(dpms.getDeviceOwnerAdminLocked().ensureUserRestrictions()
-                .getBoolean(UserManager.DISALLOW_OUTGOING_CALLS));
+        DpmTestUtils.assertRestrictions(
+                DpmTestUtils.newRestrictions(),
+                dpms.getDeviceOwnerAdminLocked().ensureUserRestrictions()
+        );
 
         // TODO Check inner calls.
         // TODO Make sure restrictions are written to the file.
@@ -836,42 +770,106 @@
     public void testSetUserRestriction_asPo() {
         setAsProfileOwner(admin1);
 
-        assertFalse(dpms.getProfileOwnerAdminLocked(DpmMockContext.CALLER_USER_HANDLE)
-                .ensureUserRestrictions()
-                .getBoolean(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES));
-        assertFalse(dpms.getProfileOwnerAdminLocked(DpmMockContext.CALLER_USER_HANDLE)
-                .ensureUserRestrictions()
-                .getBoolean(UserManager.DISALLOW_OUTGOING_CALLS));
+        DpmTestUtils.assertRestrictions(
+                DpmTestUtils.newRestrictions(),
+                dpms.getProfileOwnerAdminLocked(DpmMockContext.CALLER_USER_HANDLE)
+                        .ensureUserRestrictions()
+        );
 
         dpm.addUserRestriction(admin1, UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES);
         dpm.addUserRestriction(admin1, UserManager.DISALLOW_OUTGOING_CALLS);
 
-        assertTrue(dpms.getProfileOwnerAdminLocked(DpmMockContext.CALLER_USER_HANDLE)
-                .ensureUserRestrictions()
-                .getBoolean(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES));
-        assertTrue(dpms.getProfileOwnerAdminLocked(DpmMockContext.CALLER_USER_HANDLE)
-                .ensureUserRestrictions()
-                .getBoolean(UserManager.DISALLOW_OUTGOING_CALLS));
+        DpmTestUtils.assertRestrictions(
+                DpmTestUtils.newRestrictions(
+                        UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES,
+                        UserManager.DISALLOW_OUTGOING_CALLS
+                ),
+                dpms.getProfileOwnerAdminLocked(DpmMockContext.CALLER_USER_HANDLE)
+                        .ensureUserRestrictions()
+        );
 
         dpm.clearUserRestriction(admin1, UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES);
 
-        assertFalse(dpms.getProfileOwnerAdminLocked(DpmMockContext.CALLER_USER_HANDLE)
-                .ensureUserRestrictions()
-                .getBoolean(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES));
-        assertTrue(dpms.getProfileOwnerAdminLocked(DpmMockContext.CALLER_USER_HANDLE)
-                .ensureUserRestrictions()
-                .getBoolean(UserManager.DISALLOW_OUTGOING_CALLS));
+
+        DpmTestUtils.assertRestrictions(
+                DpmTestUtils.newRestrictions(
+                        UserManager.DISALLOW_OUTGOING_CALLS
+                ),
+                dpms.getProfileOwnerAdminLocked(DpmMockContext.CALLER_USER_HANDLE)
+                        .ensureUserRestrictions()
+        );
 
         dpm.clearUserRestriction(admin1, UserManager.DISALLOW_OUTGOING_CALLS);
 
-        assertFalse(dpms.getProfileOwnerAdminLocked(DpmMockContext.CALLER_USER_HANDLE)
-                .ensureUserRestrictions()
-                .getBoolean(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES));
-        assertFalse(dpms.getProfileOwnerAdminLocked(DpmMockContext.CALLER_USER_HANDLE)
-                .ensureUserRestrictions()
-                .getBoolean(UserManager.DISALLOW_OUTGOING_CALLS));
+        DpmTestUtils.assertRestrictions(
+                DpmTestUtils.newRestrictions(),
+                dpms.getProfileOwnerAdminLocked(DpmMockContext.CALLER_USER_HANDLE)
+                        .ensureUserRestrictions()
+        );
 
         // TODO Check inner calls.
         // TODO Make sure restrictions are written to the file.
     }
+
+    public void testGetComposedUserRestrictions_noDoNoPo() throws Exception {
+        final Bundle in = DpmTestUtils.newRestrictions(UserManager.DISALLOW_OUTGOING_CALLS);
+
+        Bundle actual = dpms.mLocalService.getComposedUserRestrictions(
+                UserHandle.USER_SYSTEM, in);
+        assertTrue(in == actual);
+
+        actual = dpms.mLocalService.getComposedUserRestrictions(
+                DpmMockContext.CALLER_USER_HANDLE, in);
+        assertTrue(in == actual);
+    }
+
+    public void testGetComposedUserRestrictions() throws Exception {
+        mContext.callerPermissions.add(permission.MANAGE_DEVICE_ADMINS);
+        mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
+        mContext.callerPermissions.add(permission.INTERACT_ACROSS_USERS_FULL);
+
+        // First, set DO.
+
+        // Call from a process on the system user.
+        mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
+
+        // Make sure admin1 is installed on system user.
+        setUpPackageManagerForAdmin(admin1, DpmMockContext.CALLER_SYSTEM_USER_UID);
+
+        // Call.
+        dpm.setActiveAdmin(admin1, /* replace =*/ false, UserHandle.USER_SYSTEM);
+        assertTrue(dpm.setDeviceOwner(admin1, "owner-name",
+                UserHandle.USER_SYSTEM));
+
+        dpm.addUserRestriction(admin1, "rest1");
+        dpm.addUserRestriction(admin1, "rest2");
+
+        // Set PO on CALLER_USER_HANDLE.
+        mContext.binder.callingUid = DpmMockContext.CALLER_UID;
+
+        setAsProfileOwner(admin2);
+
+        dpm.addUserRestriction(admin2, "restA");
+        dpm.addUserRestriction(admin2, "restB");
+
+        final Bundle in = DpmTestUtils.newRestrictions("abc");
+
+        Bundle actual = dpms.mLocalService.getComposedUserRestrictions(
+                UserHandle.USER_SYSTEM, in);
+        DpmTestUtils.assertRestrictions(
+                DpmTestUtils.newRestrictions("abc", "rest1", "rest2"),
+                actual);
+
+        actual = dpms.mLocalService.getComposedUserRestrictions(
+                DpmMockContext.CALLER_USER_HANDLE, in);
+        DpmTestUtils.assertRestrictions(
+                DpmTestUtils.newRestrictions("abc", "rest1", "rest2", "restA", "restB"),
+                actual);
+
+        actual = dpms.mLocalService.getComposedUserRestrictions(
+                DpmMockContext.CALLER_USER_HANDLE + 1, in);
+        DpmTestUtils.assertRestrictions(
+                DpmTestUtils.newRestrictions("abc", "rest1", "rest2"),
+                actual);
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
index d1b4835..cc337b0 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
@@ -36,6 +36,7 @@
 import android.os.PowerManagerInternal;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.os.UserManagerInternal;
 import android.test.mock.MockContentResolver;
 import android.test.mock.MockContext;
 import android.view.IWindowManager;
@@ -203,6 +204,7 @@
     public final EnvironmentForMock environment;
     public final SystemPropertiesForMock systemProperties;
     public final UserManager userManager;
+    public final UserManagerInternal userManagerInternal;
     public final UserManagerForMock userManagerForMock;
     public final PowerManagerForMock powerManager;
     public final PowerManagerInternal powerManagerInternal;
@@ -233,6 +235,7 @@
         environment = mock(EnvironmentForMock.class);
         systemProperties= mock(SystemPropertiesForMock.class);
         userManager = mock(UserManager.class);
+        userManagerInternal = mock(UserManagerInternal.class);
         userManagerForMock = mock(UserManagerForMock.class);
         powerManager = mock(PowerManagerForMock.class);
         powerManagerInternal = mock(PowerManagerInternal.class);
@@ -257,6 +260,9 @@
 
         // System user is always running.
         setUserRunning(UserHandle.USER_SYSTEM, true);
+
+        // This method must return an object.
+        when(userManagerInternal.getUserRestrictionsLock()).thenReturn(new Object());
     }
 
     public File addUser(int userId, int flags) {
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java
index 63bf125..e11f3fb 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java
@@ -16,10 +16,21 @@
 
 package com.android.server.devicepolicy;
 
+import android.content.ComponentName;
 import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.UserHandle;
 import android.test.AndroidTestCase;
 
 import java.io.File;
+import java.util.List;
+
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doReturn;
 
 public abstract class DpmTestBase extends AndroidTestCase {
     public static final String TAG = "DpmTest";
@@ -29,6 +40,10 @@
 
     public File dataDir;
 
+    public ComponentName admin1;
+    public ComponentName admin2;
+    public ComponentName admin3;
+
     @Override
     protected void setUp() throws Exception {
         super.setUp();
@@ -37,10 +52,77 @@
 
         mMockContext = new DpmMockContext(
                 mRealTestContext, new File(mRealTestContext.getCacheDir(), "test-data"));
+
+        admin1 = new ComponentName(mRealTestContext, DummyDeviceAdmins.Admin1.class);
+        admin2 = new ComponentName(mRealTestContext, DummyDeviceAdmins.Admin2.class);
+        admin3 = new ComponentName(mRealTestContext, DummyDeviceAdmins.Admin3.class);
     }
 
     @Override
     public DpmMockContext getContext() {
         return mMockContext;
     }
+
+
+    protected void setUpPackageManagerForAdmin(ComponentName admin, int packageUid)
+            throws Exception {
+        setUpPackageManagerForAdmin(admin, packageUid,
+                PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED);
+    }
+
+    protected void setUpPackageManagerForAdmin(ComponentName admin, int packageUid,
+            int enabledSetting) throws Exception {
+
+        // Set up queryBroadcastReceivers().
+
+        final Intent resolveIntent = new Intent();
+        resolveIntent.setComponent(admin);
+        final List<ResolveInfo> realResolveInfo =
+                mRealTestContext.getPackageManager().queryBroadcastReceivers(
+                        resolveIntent,
+                        PackageManager.GET_META_DATA);
+        assertNotNull(realResolveInfo);
+        assertEquals(1, realResolveInfo.size());
+
+        // We need to change AI, so set a clone.
+        realResolveInfo.set(0, DpmTestUtils.cloneParcelable(realResolveInfo.get(0)));
+
+        // We need to rewrite the UID in the activity info.
+        realResolveInfo.get(0).activityInfo.applicationInfo.uid = packageUid;
+
+        doReturn(realResolveInfo).when(mMockContext.packageManager).queryBroadcastReceivers(
+                MockUtils.checkIntentComponent(admin),
+                eq(PackageManager.GET_META_DATA
+                        | PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS),
+                eq(UserHandle.getUserId(packageUid)));
+
+        // Set up getApplicationInfo().
+
+        final ApplicationInfo ai = DpmTestUtils.cloneParcelable(
+                mRealTestContext.getPackageManager().getApplicationInfo(
+                        admin.getPackageName(),
+                        PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS));
+
+        ai.enabledSetting = enabledSetting;
+        ai.uid = packageUid;
+
+        doReturn(ai).when(mMockContext.ipackageManager).getApplicationInfo(
+                eq(admin.getPackageName()),
+                eq(PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS),
+                eq(UserHandle.getUserId(packageUid)));
+
+        // Set up getPackageInfo().
+
+        final PackageInfo pi = DpmTestUtils.cloneParcelable(
+                mRealTestContext.getPackageManager().getPackageInfo(
+                        admin.getPackageName(), 0));
+        assertTrue(pi.applicationInfo.flags != 0);
+
+        pi.applicationInfo.uid = packageUid;
+
+        doReturn(pi).when(mMockContext.ipackageManager).getPackageInfo(
+                eq(admin.getPackageName()),
+                eq(0),
+                eq(UserHandle.getUserId(packageUid)));
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestUtils.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestUtils.java
index 7506273..cceb2d2 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestUtils.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestUtils.java
@@ -16,21 +16,35 @@
 
 package com.android.server.devicepolicy;
 
+import com.google.android.collect.Lists;
+import com.google.android.collect.Sets;
+
+import android.content.Context;
+import android.os.Bundle;
 import android.os.FileUtils;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.test.AndroidTestCase;
 import android.util.Log;
 import android.util.Printer;
 
 import org.junit.Assert;
 
+import java.io.BufferedReader;
 import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
+import java.util.Objects;
+import java.util.Set;
 
-public class DpmTestUtils {
-    private DpmTestUtils() {
-    }
+import junit.framework.AssertionFailedError;
 
+public class DpmTestUtils extends AndroidTestCase {
     public static void clearDir(File dir) {
         if (dir.exists()) {
             Assert.assertTrue("failed to delete dir", FileUtils.deleteContents(dir));
@@ -43,6 +57,44 @@
         return list == null ? 0 : list.size();
     }
 
+    public static Bundle newRestrictions(String... restrictions) {
+        final Bundle ret = new Bundle();
+        for (String restriction : restrictions) {
+            ret.putBoolean(restriction, true);
+        }
+        return ret;
+    }
+
+    public static void assertRestrictions(Bundle expected, Bundle actual) {
+        final ArrayList<String> elist;
+        if (expected == null) {
+            elist = null;
+        } else {
+            elist = Lists.newArrayList();
+            for (String key : expected.keySet()) {
+                if (expected.getBoolean(key)) {
+                    elist.add(key);
+                }
+            }
+            Collections.sort(elist);
+        }
+
+        final ArrayList<String> alist;
+        if (actual == null) {
+            alist = null;
+        } else {
+            alist = Lists.newArrayList();
+            for (String key : actual.keySet()) {
+                if (actual.getBoolean(key)) {
+                    alist.add(key);
+                }
+            }
+            Collections.sort(alist);
+        }
+
+        assertEquals(elist, alist);
+    }
+
     public static <T extends Parcelable> T cloneParcelable(T source) {
         Parcel p = Parcel.obtain();
         p.writeParcelable(source, 0);
@@ -58,4 +110,57 @@
             Log.i(DpmTestBase.TAG, x);
         }
     };
+
+    public static String readAsset(Context context, String assetPath) throws IOException {
+        final StringBuilder sb = new StringBuilder();
+        try (BufferedReader br = new BufferedReader(
+                new InputStreamReader(
+                        context.getResources().getAssets().open(assetPath)))) {
+            String line;
+            while ((line = br.readLine()) != null) {
+                sb.append(line);
+                sb.append(System.lineSeparator());
+            }
+        }
+        return sb.toString();
+    }
+
+    public static void writeToFile(File path, String content)
+            throws IOException {
+        path.getParentFile().mkdirs();
+
+        try (FileWriter writer = new FileWriter(path)) {
+            Log.i(DpmTestBase.TAG, "Writing to " + path);
+            Log.i(DpmTestBase.TAG, content);
+            writer.write(content);
+        }
+    }
+
+    private static boolean checkAssertRestrictions(Bundle a, Bundle b) {
+        try {
+            assertRestrictions(a, b);
+            return true;
+        } catch (AssertionFailedError e) {
+            return false;
+        }
+    }
+
+    public void testAssertRestrictions() {
+        final Bundle a = newRestrictions();
+        final Bundle b = newRestrictions("a");
+        final Bundle c = newRestrictions("a");
+        final Bundle d = newRestrictions("b", "c");
+        final Bundle e = newRestrictions("b", "c");
+
+        assertTrue(checkAssertRestrictions(null, null));
+        assertFalse(checkAssertRestrictions(null, a));
+        assertFalse(checkAssertRestrictions(a, null));
+        assertTrue(checkAssertRestrictions(a, a));
+
+        assertFalse(checkAssertRestrictions(a, b));
+        assertTrue(checkAssertRestrictions(b, c));
+
+        assertFalse(checkAssertRestrictions(c, d));
+        assertTrue(checkAssertRestrictions(d, e));
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java
index 79845d2..4e11762 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java
@@ -47,31 +47,6 @@
  (mmma frameworks/base/services/tests/servicestests/ for non-ninja build)
  */
 public class OwnersTest extends DpmTestBase {
-    private String readAsset(String assetPath) throws IOException {
-        final StringBuilder sb = new StringBuilder();
-        try (BufferedReader br = new BufferedReader(
-                new InputStreamReader(
-                        mRealTestContext.getResources().getAssets().open(assetPath)))) {
-            String line;
-            while ((line = br.readLine()) != null) {
-                sb.append(line);
-                sb.append(System.lineSeparator());
-            }
-        }
-        return sb.toString();
-    }
-
-    private void createLegacyFile(File path, String content)
-            throws IOException {
-        path.getParentFile().mkdirs();
-
-        try (FileWriter writer = new FileWriter(path)) {
-            Log.i(TAG, "Writing to " + path);
-            Log.i(TAG, content);
-            writer.write(content);
-        }
-    }
-
     public void testUpgrade01() throws Exception {
         getContext().addUsers(10, 11, 20, 21);
 
@@ -79,8 +54,8 @@
         {
             final OwnersTestable owners = new OwnersTestable(getContext());
 
-            createLegacyFile(owners.getLegacyConfigFileWithTestOverride(),
-                    readAsset("OwnersTest/test01/input.xml"));
+            DpmTestUtils.writeToFile(owners.getLegacyConfigFileWithTestOverride(),
+                    DpmTestUtils.readAsset(mRealTestContext, "OwnersTest/test01/input.xml"));
 
             owners.load();
 
@@ -99,6 +74,12 @@
             assertEquals(UserHandle.USER_NULL, owners.getDeviceOwnerUserId());
             assertNull(owners.getSystemUpdatePolicy());
             assertEquals(0, owners.getProfileOwnerKeys().size());
+
+            assertFalse(owners.getDeviceOwnerUserRestrictionsNeedsMigration());
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(10));
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(11));
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(20));
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(21));
         }
 
         // Then re-read and check.
@@ -110,6 +91,12 @@
             assertEquals(UserHandle.USER_NULL, owners.getDeviceOwnerUserId());
             assertNull(owners.getSystemUpdatePolicy());
             assertEquals(0, owners.getProfileOwnerKeys().size());
+
+            assertFalse(owners.getDeviceOwnerUserRestrictionsNeedsMigration());
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(10));
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(11));
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(20));
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(21));
         }
     }
 
@@ -120,8 +107,8 @@
         {
             final OwnersTestable owners = new OwnersTestable(getContext());
 
-            createLegacyFile(owners.getLegacyConfigFileWithTestOverride(),
-                    readAsset("OwnersTest/test02/input.xml"));
+            DpmTestUtils.writeToFile(owners.getLegacyConfigFileWithTestOverride(),
+                    DpmTestUtils.readAsset(mRealTestContext, "OwnersTest/test02/input.xml"));
 
             owners.load();
 
@@ -142,6 +129,12 @@
 
             assertNull(owners.getSystemUpdatePolicy());
             assertEquals(0, owners.getProfileOwnerKeys().size());
+
+            assertTrue(owners.getDeviceOwnerUserRestrictionsNeedsMigration());
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(10));
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(11));
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(20));
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(21));
         }
 
         // Then re-read and check.
@@ -156,6 +149,12 @@
 
             assertNull(owners.getSystemUpdatePolicy());
             assertEquals(0, owners.getProfileOwnerKeys().size());
+
+            assertTrue(owners.getDeviceOwnerUserRestrictionsNeedsMigration());
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(10));
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(11));
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(20));
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(21));
         }
     }
 
@@ -166,8 +165,8 @@
         {
             final OwnersTestable owners = new OwnersTestable(getContext());
 
-            createLegacyFile(owners.getLegacyConfigFileWithTestOverride(),
-                    readAsset("OwnersTest/test03/input.xml"));
+            DpmTestUtils.writeToFile(owners.getLegacyConfigFileWithTestOverride(),
+                    DpmTestUtils.readAsset(mRealTestContext, "OwnersTest/test03/input.xml"));
 
             owners.load();
 
@@ -196,6 +195,12 @@
                     owners.getProfileOwnerComponent(11));
             assertEquals("1", owners.getProfileOwnerName(11));
             assertEquals("com.google.android.testdpc1", owners.getProfileOwnerPackage(11));
+
+            assertFalse(owners.getDeviceOwnerUserRestrictionsNeedsMigration());
+            assertTrue(owners.getProfileOwnerUserRestrictionsNeedsMigration(10));
+            assertTrue(owners.getProfileOwnerUserRestrictionsNeedsMigration(11));
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(20));
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(21));
         }
 
         // Then re-read and check.
@@ -218,9 +223,19 @@
                     owners.getProfileOwnerComponent(11));
             assertEquals("1", owners.getProfileOwnerName(11));
             assertEquals("com.google.android.testdpc1", owners.getProfileOwnerPackage(11));
+
+            assertFalse(owners.getDeviceOwnerUserRestrictionsNeedsMigration());
+            assertTrue(owners.getProfileOwnerUserRestrictionsNeedsMigration(10));
+            assertTrue(owners.getProfileOwnerUserRestrictionsNeedsMigration(11));
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(20));
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(21));
         }
     }
 
+    /**
+     * Note this also tests {@link Owners#setDeviceOwnerUserRestrictionsMigrated()}
+     * and {@link  Owners#setProfileOwnerUserRestrictionsMigrated(int)}.
+     */
     public void testUpgrade04() throws Exception {
         getContext().addUsers(10, 11, 20, 21);
 
@@ -228,8 +243,8 @@
         {
             final OwnersTestable owners = new OwnersTestable(getContext());
 
-            createLegacyFile(owners.getLegacyConfigFileWithTestOverride(),
-                    readAsset("OwnersTest/test04/input.xml"));
+            DpmTestUtils.writeToFile(owners.getLegacyConfigFileWithTestOverride(),
+                    DpmTestUtils.readAsset(mRealTestContext, "OwnersTest/test04/input.xml"));
 
             owners.load();
 
@@ -262,6 +277,12 @@
                     owners.getProfileOwnerComponent(11));
             assertEquals("1", owners.getProfileOwnerName(11));
             assertEquals("com.google.android.testdpc1", owners.getProfileOwnerPackage(11));
+
+            assertTrue(owners.getDeviceOwnerUserRestrictionsNeedsMigration());
+            assertTrue(owners.getProfileOwnerUserRestrictionsNeedsMigration(10));
+            assertTrue(owners.getProfileOwnerUserRestrictionsNeedsMigration(11));
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(20));
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(21));
         }
 
         // Then re-read and check.
@@ -288,6 +309,40 @@
                     owners.getProfileOwnerComponent(11));
             assertEquals("1", owners.getProfileOwnerName(11));
             assertEquals("com.google.android.testdpc1", owners.getProfileOwnerPackage(11));
+
+            assertTrue(owners.getDeviceOwnerUserRestrictionsNeedsMigration());
+            assertTrue(owners.getProfileOwnerUserRestrictionsNeedsMigration(10));
+            assertTrue(owners.getProfileOwnerUserRestrictionsNeedsMigration(11));
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(20));
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(21));
+
+            owners.setDeviceOwnerUserRestrictionsMigrated();
+        }
+
+        {
+            final OwnersTestable owners = new OwnersTestable(getContext());
+            owners.load();
+
+            assertFalse(owners.getDeviceOwnerUserRestrictionsNeedsMigration());
+            assertTrue(owners.getProfileOwnerUserRestrictionsNeedsMigration(10));
+            assertTrue(owners.getProfileOwnerUserRestrictionsNeedsMigration(11));
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(20));
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(21));
+
+            owners.setProfileOwnerUserRestrictionsMigrated(11);
+        }
+
+        {
+            final OwnersTestable owners = new OwnersTestable(getContext());
+            owners.load();
+
+            assertFalse(owners.getDeviceOwnerUserRestrictionsNeedsMigration());
+            assertTrue(owners.getProfileOwnerUserRestrictionsNeedsMigration(10));
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(11));
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(20));
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(21));
+
+            owners.setProfileOwnerUserRestrictionsMigrated(11);
         }
     }
 
@@ -298,8 +353,8 @@
         {
             final OwnersTestable owners = new OwnersTestable(getContext());
 
-            createLegacyFile(owners.getLegacyConfigFileWithTestOverride(),
-                    readAsset("OwnersTest/test05/input.xml"));
+            DpmTestUtils.writeToFile(owners.getLegacyConfigFileWithTestOverride(),
+                    DpmTestUtils.readAsset(mRealTestContext, "OwnersTest/test05/input.xml"));
 
             owners.load();
 
@@ -319,6 +374,12 @@
 
             assertNull(owners.getSystemUpdatePolicy());
             assertEquals(0, owners.getProfileOwnerKeys().size());
+
+            assertFalse(owners.getDeviceOwnerUserRestrictionsNeedsMigration());
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(10));
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(11));
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(20));
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(21));
         }
 
         // Then re-read and check.
@@ -332,6 +393,12 @@
 
             assertNull(owners.getSystemUpdatePolicy());
             assertEquals(0, owners.getProfileOwnerKeys().size());
+
+            assertFalse(owners.getDeviceOwnerUserRestrictionsNeedsMigration());
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(10));
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(11));
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(20));
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(21));
         }
     }
 
@@ -342,8 +409,8 @@
         {
             final OwnersTestable owners = new OwnersTestable(getContext());
 
-            createLegacyFile(owners.getLegacyConfigFileWithTestOverride(),
-                    readAsset("OwnersTest/test06/input.xml"));
+            DpmTestUtils.writeToFile(owners.getLegacyConfigFileWithTestOverride(),
+                    DpmTestUtils.readAsset(mRealTestContext, "OwnersTest/test06/input.xml"));
 
             owners.load();
 
@@ -362,6 +429,12 @@
 
             assertNotNull(owners.getSystemUpdatePolicy());
             assertEquals(5, owners.getSystemUpdatePolicy().getPolicyType());
+
+            assertFalse(owners.getDeviceOwnerUserRestrictionsNeedsMigration());
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(10));
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(11));
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(20));
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(21));
         }
 
         // Then re-read and check.
@@ -375,6 +448,12 @@
 
             assertNotNull(owners.getSystemUpdatePolicy());
             assertEquals(5, owners.getSystemUpdatePolicy().getPolicyType());
+
+            assertFalse(owners.getDeviceOwnerUserRestrictionsNeedsMigration());
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(10));
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(11));
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(20));
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(21));
         }
     }
 
@@ -384,8 +463,8 @@
         final OwnersTestable owners = new OwnersTestable(getContext());
 
         // First, migrate to create new-style config files.
-        createLegacyFile(owners.getLegacyConfigFileWithTestOverride(),
-                readAsset("OwnersTest/test04/input.xml"));
+        DpmTestUtils.writeToFile(owners.getLegacyConfigFileWithTestOverride(),
+                DpmTestUtils.readAsset(mRealTestContext, "OwnersTest/test04/input.xml"));
 
         owners.load();
 
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index 86f5ed7..66c7dbb 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -211,11 +211,14 @@
 
     public void testRestrictions() {
         UserInfo testUser = createUser("User 1", 0);
-        Bundle restrictions = new Bundle();
-        restrictions.putBoolean(UserManager.DISALLOW_INSTALL_APPS, true);
-        restrictions.putBoolean(UserManager.DISALLOW_CONFIG_WIFI, false);
-        mUserManager.setUserRestrictions(restrictions, new UserHandle(testUser.id));
+
+        mUserManager.setUserRestriction(
+                UserManager.DISALLOW_INSTALL_APPS, true, new UserHandle(testUser.id));
+        mUserManager.setUserRestriction(
+                UserManager.DISALLOW_CONFIG_WIFI, false, new UserHandle(testUser.id));
+
         Bundle stored = mUserManager.getUserRestrictions(new UserHandle(testUser.id));
+        // Note this will fail if DO already sets those restrictions.
         assertEquals(stored.getBoolean(UserManager.DISALLOW_CONFIG_WIFI), false);
         assertEquals(stored.getBoolean(UserManager.DISALLOW_UNINSTALL_APPS), false);
         assertEquals(stored.getBoolean(UserManager.DISALLOW_INSTALL_APPS), true);