Import Android SDK Platform P [4413397]

/google/data/ro/projects/android/fetch_artifact \
    --bid 4413397 \
    --target sdk_phone_armv7-win_sdk \
    sdk-repo-linux-sources-4413397.zip

AndroidVersion.ApiLevel has been modified to appear as 28

Change-Id: I3cf1f7c36e61c090dcc7de7bcfa812ef2bf96c00
diff --git a/android/accessibilityservice/GestureDescription.java b/android/accessibilityservice/GestureDescription.java
index 92567d7..56f4ae2 100644
--- a/android/accessibilityservice/GestureDescription.java
+++ b/android/accessibilityservice/GestureDescription.java
@@ -428,6 +428,18 @@
         }
 
         @Override
+        public String toString() {
+            return "TouchPoint{"
+                    + "mStrokeId=" + mStrokeId
+                    + ", mContinuedStrokeId=" + mContinuedStrokeId
+                    + ", mIsStartOfPath=" + mIsStartOfPath
+                    + ", mIsEndOfPath=" + mIsEndOfPath
+                    + ", mX=" + mX
+                    + ", mY=" + mY
+                    + '}';
+        }
+
+        @Override
         public int describeContents() {
             return 0;
         }
diff --git a/android/app/Activity.java b/android/app/Activity.java
index e0ac911..85f73bb 100644
--- a/android/app/Activity.java
+++ b/android/app/Activity.java
@@ -542,9 +542,9 @@
  * <ul>
  *     <li> <p>When creating a new document, the backing database entry or file for
  *             it is created immediately.  For example, if the user chooses to write
- *             a new e-mail, a new entry for that e-mail is created as soon as they
+ *             a new email, a new entry for that email is created as soon as they
  *             start entering data, so that if they go to any other activity after
- *             that point this e-mail will now appear in the list of drafts.</p>
+ *             that point this email will now appear in the list of drafts.</p>
  *     <li> <p>When an activity's <code>onPause()</code> method is called, it should
  *             commit to the backing content provider or file any changes the user
  *             has made.  This ensures that those changes will be seen by any other
@@ -1879,7 +1879,7 @@
 
         if (isFinishing()) {
             if (mAutoFillResetNeeded) {
-                getAutofillManager().commit();
+                getAutofillManager().onActivityFinished();
             } else if (mIntent != null
                     && mIntent.hasExtra(AutofillManager.EXTRA_RESTORE_SESSION_TOKEN)) {
                 // Activity was launched when user tapped a link in the Autofill Save UI - since
@@ -6259,6 +6259,8 @@
         final AutofillManager afm = getAutofillManager();
         if (afm != null) {
             afm.dump(prefix, writer);
+        } else {
+            writer.print(prefix); writer.println("No AutofillManager");
         }
     }
 
diff --git a/android/app/ActivityManager.java b/android/app/ActivityManager.java
index 5e61727..fc4c8d7 100644
--- a/android/app/ActivityManager.java
+++ b/android/app/ActivityManager.java
@@ -16,14 +16,8 @@
 
 package android.app;
 
-import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-
 import android.Manifest;
+import android.annotation.DrawableRes;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -670,138 +664,6 @@
         /** Invalid stack ID. */
         public static final int INVALID_STACK_ID = -1;
 
-        /** First static stack ID.
-         * @hide */
-        private  static final int FIRST_STATIC_STACK_ID = 0;
-
-        /** ID of stack where fullscreen activities are normally launched into.
-         * @hide */
-        public static final int FULLSCREEN_WORKSPACE_STACK_ID = 1;
-
-        /** ID of stack where freeform/resized activities are normally launched into.
-         * @hide */
-        public static final int FREEFORM_WORKSPACE_STACK_ID = FULLSCREEN_WORKSPACE_STACK_ID + 1;
-
-        /** ID of stack that occupies a dedicated region of the screen.
-         * @hide */
-        public static final int DOCKED_STACK_ID = FREEFORM_WORKSPACE_STACK_ID + 1;
-
-        /** ID of stack that always on top (always visible) when it exist.
-         * @hide */
-        public static final int PINNED_STACK_ID = DOCKED_STACK_ID + 1;
-
-        /** Last static stack stack ID.
-         * @hide */
-        private static final int LAST_STATIC_STACK_ID = PINNED_STACK_ID;
-
-        /** Start of ID range used by stacks that are created dynamically.
-         * @hide */
-        public static final int FIRST_DYNAMIC_STACK_ID = LAST_STATIC_STACK_ID + 1;
-
-        // TODO: Figure-out a way to remove this.
-        /** @hide */
-        public static boolean isStaticStack(int stackId) {
-            return stackId >= FIRST_STATIC_STACK_ID && stackId <= LAST_STATIC_STACK_ID;
-        }
-
-        // TODO: It seems this mostly means a stack on a secondary display now. Need to see if
-        // there are other meanings. If not why not just use information from the display?
-        /** @hide */
-        public static boolean isDynamicStack(int stackId) {
-            return stackId >= FIRST_DYNAMIC_STACK_ID;
-        }
-
-        /**
-         * Returns true if we try to maintain focus in the current stack when the top activity
-         * finishes.
-         * @hide
-         */
-        // TODO: Figure-out a way to remove. Probably isn't needed in the new world...
-        public static boolean keepFocusInStackIfPossible(int stackId) {
-            return stackId == FREEFORM_WORKSPACE_STACK_ID
-                    || stackId == DOCKED_STACK_ID || stackId == PINNED_STACK_ID;
-        }
-
-        /**
-         * Returns true if the windows of tasks being moved to the target stack from the source
-         * stack should be replaced, meaning that window manager will keep the old window around
-         * until the new is ready.
-         * @hide
-         */
-        public static boolean replaceWindowsOnTaskMove(int sourceStackId, int targetStackId) {
-            return sourceStackId == FREEFORM_WORKSPACE_STACK_ID
-                    || targetStackId == FREEFORM_WORKSPACE_STACK_ID;
-        }
-
-        /**
-         * Returns true if the top task in the task is allowed to return home when finished and
-         * there are other tasks in the stack.
-         * @hide
-         */
-        public static boolean allowTopTaskToReturnHome(int stackId) {
-            return stackId != PINNED_STACK_ID;
-        }
-
-        /**
-         * Returns true if the stack should be resized to match the bounds specified by
-         * {@link ActivityOptions#setLaunchBounds} when launching an activity into the stack.
-         * @hide
-         */
-        public static boolean resizeStackWithLaunchBounds(int stackId) {
-            return stackId == PINNED_STACK_ID;
-        }
-
-        /**
-         * Returns true if a window from the specified stack with {@param stackId} are normally
-         * fullscreen, i. e. they can become the top opaque fullscreen window, meaning that it
-         * controls system bars, lockscreen occluded/dismissing state, screen rotation animation,
-         * etc.
-         * @hide
-         */
-        // TODO: What about the other side of docked stack if we move this to WindowConfiguration?
-        public static boolean normallyFullscreenWindows(int stackId) {
-            return stackId != PINNED_STACK_ID && stackId != FREEFORM_WORKSPACE_STACK_ID
-                    && stackId != DOCKED_STACK_ID;
-        }
-
-        /** Returns the stack id for the input windowing mode.
-         * @hide */
-        // TODO: To be removed once we are not using stack id for stuff...
-        public static int getStackIdForWindowingMode(int windowingMode) {
-            switch (windowingMode) {
-                case WINDOWING_MODE_PINNED: return PINNED_STACK_ID;
-                case WINDOWING_MODE_FREEFORM: return FREEFORM_WORKSPACE_STACK_ID;
-                case WINDOWING_MODE_SPLIT_SCREEN_PRIMARY: return DOCKED_STACK_ID;
-                case WINDOWING_MODE_SPLIT_SCREEN_SECONDARY: return FULLSCREEN_WORKSPACE_STACK_ID;
-                case WINDOWING_MODE_FULLSCREEN: return FULLSCREEN_WORKSPACE_STACK_ID;
-                default: return INVALID_STACK_ID;
-            }
-        }
-
-        /** Returns the windowing mode that should be used for this input stack id.
-         * @hide */
-        // TODO: To be removed once we are not using stack id for stuff...
-        public static int getWindowingModeForStackId(int stackId, boolean inSplitScreenMode) {
-            final int windowingMode;
-            switch (stackId) {
-                case FULLSCREEN_WORKSPACE_STACK_ID:
-                    windowingMode = inSplitScreenMode
-                            ? WINDOWING_MODE_SPLIT_SCREEN_SECONDARY : WINDOWING_MODE_FULLSCREEN;
-                    break;
-                case PINNED_STACK_ID:
-                    windowingMode = WINDOWING_MODE_PINNED;
-                    break;
-                case DOCKED_STACK_ID:
-                    windowingMode = WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
-                    break;
-                case FREEFORM_WORKSPACE_STACK_ID:
-                    windowingMode = WINDOWING_MODE_FREEFORM;
-                    break;
-                default :
-                    windowingMode = WINDOWING_MODE_UNDEFINED;
-            }
-            return windowingMode;
-        }
     }
 
     /**
@@ -1080,11 +942,14 @@
                 ATTR_TASKDESCRIPTION_PREFIX + "color";
         private static final String ATTR_TASKDESCRIPTIONCOLOR_BACKGROUND =
                 ATTR_TASKDESCRIPTION_PREFIX + "colorBackground";
-        private static final String ATTR_TASKDESCRIPTIONICONFILENAME =
+        private static final String ATTR_TASKDESCRIPTIONICON_FILENAME =
                 ATTR_TASKDESCRIPTION_PREFIX + "icon_filename";
+        private static final String ATTR_TASKDESCRIPTIONICON_RESOURCE =
+                ATTR_TASKDESCRIPTION_PREFIX + "icon_resource";
 
         private String mLabel;
         private Bitmap mIcon;
+        private int mIconRes;
         private String mIconFilename;
         private int mColorPrimary;
         private int mColorBackground;
@@ -1098,9 +963,27 @@
          * @param icon An icon that represents the current state of this task.
          * @param colorPrimary A color to override the theme's primary color.  This color must be
          *                     opaque.
+         * @deprecated use TaskDescription constructor with icon resource instead
          */
+        @Deprecated
         public TaskDescription(String label, Bitmap icon, int colorPrimary) {
-            this(label, icon, null, colorPrimary, 0, 0, 0);
+            this(label, icon, 0, null, colorPrimary, 0, 0, 0);
+            if ((colorPrimary != 0) && (Color.alpha(colorPrimary) != 255)) {
+                throw new RuntimeException("A TaskDescription's primary color should be opaque");
+            }
+        }
+
+        /**
+         * Creates the TaskDescription to the specified values.
+         *
+         * @param label A label and description of the current state of this task.
+         * @param iconRes A drawable resource of an icon that represents the current state of this
+         *                activity.
+         * @param colorPrimary A color to override the theme's primary color.  This color must be
+         *                     opaque.
+         */
+        public TaskDescription(String label, @DrawableRes int iconRes, int colorPrimary) {
+            this(label, null, iconRes, null, colorPrimary, 0, 0, 0);
             if ((colorPrimary != 0) && (Color.alpha(colorPrimary) != 255)) {
                 throw new RuntimeException("A TaskDescription's primary color should be opaque");
             }
@@ -1111,9 +994,22 @@
          *
          * @param label A label and description of the current state of this activity.
          * @param icon An icon that represents the current state of this activity.
+         * @deprecated use TaskDescription constructor with icon resource instead
          */
+        @Deprecated
         public TaskDescription(String label, Bitmap icon) {
-            this(label, icon, null, 0, 0, 0, 0);
+            this(label, icon, 0, null, 0, 0, 0, 0);
+        }
+
+        /**
+         * Creates the TaskDescription to the specified values.
+         *
+         * @param label A label and description of the current state of this activity.
+         * @param iconRes A drawable resource of an icon that represents the current state of this
+         *                activity.
+         */
+        public TaskDescription(String label, @DrawableRes int iconRes) {
+            this(label, null, iconRes, null, 0, 0, 0, 0);
         }
 
         /**
@@ -1122,21 +1018,22 @@
          * @param label A label and description of the current state of this activity.
          */
         public TaskDescription(String label) {
-            this(label, null, null, 0, 0, 0, 0);
+            this(label, null, 0, null, 0, 0, 0, 0);
         }
 
         /**
          * Creates an empty TaskDescription.
          */
         public TaskDescription() {
-            this(null, null, null, 0, 0, 0, 0);
+            this(null, null, 0, null, 0, 0, 0, 0);
         }
 
         /** @hide */
-        public TaskDescription(String label, Bitmap icon, String iconFilename, int colorPrimary,
-                int colorBackground, int statusBarColor, int navigationBarColor) {
+        public TaskDescription(String label, Bitmap bitmap, int iconRes, String iconFilename,
+                int colorPrimary, int colorBackground, int statusBarColor, int navigationBarColor) {
             mLabel = label;
-            mIcon = icon;
+            mIcon = bitmap;
+            mIconRes = iconRes;
             mIconFilename = iconFilename;
             mColorPrimary = colorPrimary;
             mColorBackground = colorBackground;
@@ -1158,6 +1055,7 @@
         public void copyFrom(TaskDescription other) {
             mLabel = other.mLabel;
             mIcon = other.mIcon;
+            mIconRes = other.mIconRes;
             mIconFilename = other.mIconFilename;
             mColorPrimary = other.mColorPrimary;
             mColorBackground = other.mColorBackground;
@@ -1173,6 +1071,7 @@
         public void copyFromPreserveHiddenFields(TaskDescription other) {
             mLabel = other.mLabel;
             mIcon = other.mIcon;
+            mIconRes = other.mIconRes;
             mIconFilename = other.mIconFilename;
             mColorPrimary = other.mColorPrimary;
             if (other.mColorBackground != 0) {
@@ -1245,6 +1144,14 @@
         }
 
         /**
+         * Sets the icon resource for this task description.
+         * @hide
+         */
+        public void setIcon(int iconRes) {
+            mIconRes = iconRes;
+        }
+
+        /**
          * Moves the icon bitmap reference from an actual Bitmap to a file containing the
          * bitmap.
          * @hide
@@ -1272,6 +1179,13 @@
         }
 
         /** @hide */
+        @TestApi
+        public int getIconResource() {
+            return mIconRes;
+        }
+
+        /** @hide */
+        @TestApi
         public String getIconFilename() {
             return mIconFilename;
         }
@@ -1337,7 +1251,10 @@
                         Integer.toHexString(mColorBackground));
             }
             if (mIconFilename != null) {
-                out.attribute(null, ATTR_TASKDESCRIPTIONICONFILENAME, mIconFilename);
+                out.attribute(null, ATTR_TASKDESCRIPTIONICON_FILENAME, mIconFilename);
+            }
+            if (mIconRes != 0) {
+                out.attribute(null, ATTR_TASKDESCRIPTIONICON_RESOURCE, Integer.toString(mIconRes));
             }
         }
 
@@ -1349,8 +1266,10 @@
                 setPrimaryColor((int) Long.parseLong(attrValue, 16));
             } else if (ATTR_TASKDESCRIPTIONCOLOR_BACKGROUND.equals(attrName)) {
                 setBackgroundColor((int) Long.parseLong(attrValue, 16));
-            } else if (ATTR_TASKDESCRIPTIONICONFILENAME.equals(attrName)) {
+            } else if (ATTR_TASKDESCRIPTIONICON_FILENAME.equals(attrName)) {
                 setIconFilename(attrValue);
+            } else if (ATTR_TASKDESCRIPTIONICON_RESOURCE.equals(attrName)) {
+                setIcon(Integer.parseInt(attrValue, 10));
             }
         }
 
@@ -1373,6 +1292,7 @@
                 dest.writeInt(1);
                 mIcon.writeToParcel(dest, 0);
             }
+            dest.writeInt(mIconRes);
             dest.writeInt(mColorPrimary);
             dest.writeInt(mColorBackground);
             dest.writeInt(mStatusBarColor);
@@ -1388,6 +1308,7 @@
         public void readFromParcel(Parcel source) {
             mLabel = source.readInt() > 0 ? source.readString() : null;
             mIcon = source.readInt() > 0 ? Bitmap.CREATOR.createFromParcel(source) : null;
+            mIconRes = source.readInt();
             mColorPrimary = source.readInt();
             mColorBackground = source.readInt();
             mStatusBarColor = source.readInt();
@@ -1408,8 +1329,8 @@
         @Override
         public String toString() {
             return "TaskDescription Label: " + mLabel + " Icon: " + mIcon +
-                    " IconFilename: " + mIconFilename + " colorPrimary: " + mColorPrimary +
-                    " colorBackground: " + mColorBackground +
+                    " IconRes: " + mIconRes + " IconFilename: " + mIconFilename +
+                    " colorPrimary: " + mColorPrimary + " colorBackground: " + mColorBackground +
                     " statusBarColor: " + mColorBackground +
                     " navigationBarColor: " + mNavigationBarColor;
         }
@@ -1571,7 +1492,6 @@
             }
             dest.writeInt(stackId);
             dest.writeInt(userId);
-            dest.writeLong(firstActiveTime);
             dest.writeLong(lastActiveTime);
             dest.writeInt(affiliatedTaskId);
             dest.writeInt(affiliatedTaskColor);
@@ -1600,7 +1520,6 @@
                     TaskDescription.CREATOR.createFromParcel(source) : null;
             stackId = source.readInt();
             userId = source.readInt();
-            firstActiveTime = source.readLong();
             lastActiveTime = source.readLong();
             affiliatedTaskId = source.readInt();
             affiliatedTaskColor = source.readInt();
@@ -1643,31 +1562,6 @@
     public static final int RECENT_IGNORE_UNAVAILABLE = 0x0002;
 
     /**
-     * Provides a list that contains recent tasks for all
-     * profiles of a user.
-     * @hide
-     */
-    public static final int RECENT_INCLUDE_PROFILES = 0x0004;
-
-    /**
-     * Ignores all tasks that are on the home stack.
-     * @hide
-     */
-    public static final int RECENT_IGNORE_HOME_AND_RECENTS_STACK_TASKS = 0x0008;
-
-    /**
-     * Ignores the top task in the docked stack.
-     * @hide
-     */
-    public static final int RECENT_INGORE_DOCKED_STACK_TOP_TASK = 0x0010;
-
-    /**
-     * Ignores all tasks that are on the pinned stack.
-     * @hide
-     */
-    public static final int RECENT_INGORE_PINNED_STACK_TASKS = 0x0020;
-
-    /**
      * <p></p>Return a list of the tasks that the user has recently launched, with
      * the most recent being first and older ones after in order.
      *
@@ -1702,33 +1596,7 @@
     public List<RecentTaskInfo> getRecentTasks(int maxNum, int flags)
             throws SecurityException {
         try {
-            return getService().getRecentTasks(maxNum,
-                    flags, UserHandle.myUserId()).getList();
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-     * Same as {@link #getRecentTasks(int, int)} but returns the recent tasks for a
-     * specific user. It requires holding
-     * the {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} permission.
-     * @param maxNum The maximum number of entries to return in the list.  The
-     * actual number returned may be smaller, depending on how many tasks the
-     * user has started and the maximum number the system can remember.
-     * @param flags Information about what to return.  May be any combination
-     * of {@link #RECENT_WITH_EXCLUDED} and {@link #RECENT_IGNORE_UNAVAILABLE}.
-     *
-     * @return Returns a list of RecentTaskInfo records describing each of
-     * the recent tasks. Most recently activated tasks go first.
-     *
-     * @hide
-     */
-    public List<RecentTaskInfo> getRecentTasksForUser(int maxNum, int flags, int userId)
-            throws SecurityException {
-        try {
-            return getService().getRecentTasks(maxNum,
-                    flags, userId).getList();
+            return getService().getRecentTasks(maxNum, flags, UserHandle.myUserId()).getList();
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -2021,22 +1889,6 @@
     }
 
     /**
-     * Completely remove the given task.
-     *
-     * @param taskId Identifier of the task to be removed.
-     * @return Returns true if the given task was found and removed.
-     *
-     * @hide
-     */
-    public boolean removeTask(int taskId) throws SecurityException {
-        try {
-            return getService().removeTask(taskId);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
      * Sets the windowing mode for a specific task. Only works on tasks of type
      * {@link WindowConfiguration#ACTIVITY_TYPE_STANDARD}
      * @param taskId The id of the task to set the windowing mode for.
diff --git a/android/app/ActivityOptions.java b/android/app/ActivityOptions.java
index a68c3a5..b62e4c2 100644
--- a/android/app/ActivityOptions.java
+++ b/android/app/ActivityOptions.java
@@ -17,13 +17,13 @@
 package android.app;
 
 import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
-import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.view.Display.INVALID_DISPLAY;
 
 import android.annotation.Nullable;
 import android.annotation.TestApi;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.Bitmap;
@@ -159,6 +159,12 @@
     private static final String KEY_ANIM_SPECS = "android:activity.animSpecs";
 
     /**
+     * Whether the activity should be launched into LockTask mode.
+     * @see #setLockTaskMode(boolean)
+     */
+    private static final String KEY_LOCK_TASK_MODE = "android:activity.lockTaskMode";
+
+    /**
      * The display id the activity should be launched into.
      * @see #setLaunchDisplayId(int)
      * @hide
@@ -279,6 +285,7 @@
     private int mResultCode;
     private int mExitCoordinatorIndex;
     private PendingIntent mUsageTimeReport;
+    private boolean mLockTaskMode = false;
     private int mLaunchDisplayId = INVALID_DISPLAY;
     @WindowConfiguration.WindowingMode
     private int mLaunchWindowingMode = WINDOWING_MODE_UNDEFINED;
@@ -870,6 +877,7 @@
                 mExitCoordinatorIndex = opts.getInt(KEY_EXIT_COORDINATOR_INDEX);
                 break;
         }
+        mLockTaskMode = opts.getBoolean(KEY_LOCK_TASK_MODE, false);
         mLaunchDisplayId = opts.getInt(KEY_LAUNCH_DISPLAY_ID, INVALID_DISPLAY);
         mLaunchWindowingMode = opts.getInt(KEY_LAUNCH_WINDOWING_MODE, WINDOWING_MODE_UNDEFINED);
         mLaunchActivityType = opts.getInt(KEY_LAUNCH_ACTIVITY_TYPE, ACTIVITY_TYPE_UNDEFINED);
@@ -1056,6 +1064,37 @@
     }
 
     /**
+     * Gets whether the activity is to be launched into LockTask mode.
+     * @return {@code true} if the activity is to be launched into LockTask mode.
+     * @see Activity#startLockTask()
+     * @see android.app.admin.DevicePolicyManager#setLockTaskPackages(ComponentName, String[])
+     */
+    public boolean getLockTaskMode() {
+        return mLockTaskMode;
+    }
+
+    /**
+     * Sets whether the activity is to be launched into LockTask mode.
+     *
+     * Use this option to start an activity in LockTask mode. Note that only apps permitted by
+     * {@link android.app.admin.DevicePolicyManager} can run in LockTask mode. Therefore, if
+     * {@link android.app.admin.DevicePolicyManager#isLockTaskPermitted(String)} returns
+     * {@code false} for the package of the target activity, a {@link SecurityException} will be
+     * thrown during {@link Context#startActivity(Intent, Bundle)}.
+     *
+     * Defaults to {@code false} if not set.
+     *
+     * @param lockTaskMode {@code true} if the activity is to be launched into LockTask mode.
+     * @return {@code this} {@link ActivityOptions} instance.
+     * @see Activity#startLockTask()
+     * @see android.app.admin.DevicePolicyManager#setLockTaskPackages(ComponentName, String[])
+     */
+    public ActivityOptions setLockTaskMode(boolean lockTaskMode) {
+        mLockTaskMode = lockTaskMode;
+        return this;
+    }
+
+    /**
      * Gets the id of the display where activity should be launched.
      * @return The id of the display where activity should be launched,
      *         {@link android.view.Display#INVALID_DISPLAY} if not set.
@@ -1248,6 +1287,7 @@
                 mExitCoordinatorIndex = otherOptions.mExitCoordinatorIndex;
                 break;
         }
+        mLockTaskMode = otherOptions.mLockTaskMode;
         mAnimSpecs = otherOptions.mAnimSpecs;
         mAnimationFinishedListener = otherOptions.mAnimationFinishedListener;
         mSpecsFuture = otherOptions.mSpecsFuture;
@@ -1322,6 +1362,7 @@
                 b.putInt(KEY_EXIT_COORDINATOR_INDEX, mExitCoordinatorIndex);
                 break;
         }
+        b.putBoolean(KEY_LOCK_TASK_MODE, mLockTaskMode);
         b.putInt(KEY_LAUNCH_DISPLAY_ID, mLaunchDisplayId);
         b.putInt(KEY_LAUNCH_WINDOWING_MODE, mLaunchWindowingMode);
         b.putInt(KEY_LAUNCH_ACTIVITY_TYPE, mLaunchActivityType);
diff --git a/android/app/KeyguardManager.java b/android/app/KeyguardManager.java
index 54f74b1..1fe2900 100644
--- a/android/app/KeyguardManager.java
+++ b/android/app/KeyguardManager.java
@@ -387,8 +387,6 @@
      * such as the Home key and the right soft keys, don't work.
      *
      * @return true if in keyguard restricted input mode.
-     *
-     * @see android.view.WindowManagerPolicy#inKeyguardRestrictedKeyInputMode
      */
     public boolean inKeyguardRestrictedInputMode() {
         try {
diff --git a/android/app/NotificationChannel.java b/android/app/NotificationChannel.java
index 47063f0..c06ad3f 100644
--- a/android/app/NotificationChannel.java
+++ b/android/app/NotificationChannel.java
@@ -32,6 +32,8 @@
 
 import com.android.internal.util.Preconditions;
 
+import com.android.internal.util.Preconditions;
+
 import org.json.JSONException;
 import org.json.JSONObject;
 import org.xmlpull.v1.XmlPullParser;
diff --git a/android/app/NotificationManager.java b/android/app/NotificationManager.java
index eb52cb7..a52dc1e 100644
--- a/android/app/NotificationManager.java
+++ b/android/app/NotificationManager.java
@@ -934,8 +934,14 @@
         public static final int PRIORITY_CATEGORY_CALLS = 1 << 3;
         /** Calls from repeat callers are prioritized. */
         public static final int PRIORITY_CATEGORY_REPEAT_CALLERS = 1 << 4;
+        /** Alarms are prioritized */
+        public static final int PRIORITY_CATEGORY_ALARMS = 1 << 5;
+        /** Media, system, game (catch-all for non-never suppressible sounds) are prioritized */
+        public static final int PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER = 1 << 6;
 
         private static final int[] ALL_PRIORITY_CATEGORIES = {
+            PRIORITY_CATEGORY_ALARMS,
+            PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER,
             PRIORITY_CATEGORY_REMINDERS,
             PRIORITY_CATEGORY_EVENTS,
             PRIORITY_CATEGORY_MESSAGES,
@@ -1135,6 +1141,9 @@
                 case PRIORITY_CATEGORY_MESSAGES: return "PRIORITY_CATEGORY_MESSAGES";
                 case PRIORITY_CATEGORY_CALLS: return "PRIORITY_CATEGORY_CALLS";
                 case PRIORITY_CATEGORY_REPEAT_CALLERS: return "PRIORITY_CATEGORY_REPEAT_CALLERS";
+                case PRIORITY_CATEGORY_ALARMS: return "PRIORITY_CATEGORY_ALARMS";
+                case PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER:
+                    return "PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER";
                 default: return "PRIORITY_CATEGORY_UNKNOWN_" + priorityCategory;
             }
         }
diff --git a/android/app/StatusBarManager.java b/android/app/StatusBarManager.java
index 8987bc0..23c4166 100644
--- a/android/app/StatusBarManager.java
+++ b/android/app/StatusBarManager.java
@@ -73,15 +73,16 @@
     public static final int DISABLE2_QUICK_SETTINGS = 1;
     public static final int DISABLE2_SYSTEM_ICONS = 1 << 1;
     public static final int DISABLE2_NOTIFICATION_SHADE = 1 << 2;
+    public static final int DISABLE2_GLOBAL_ACTIONS = 1 << 3;
 
     public static final int DISABLE2_NONE = 0x00000000;
 
     public static final int DISABLE2_MASK = DISABLE2_QUICK_SETTINGS | DISABLE2_SYSTEM_ICONS
-            | DISABLE2_NOTIFICATION_SHADE;
+            | DISABLE2_NOTIFICATION_SHADE | DISABLE2_GLOBAL_ACTIONS;
 
     @IntDef(flag = true,
             value = {DISABLE2_NONE, DISABLE2_MASK, DISABLE2_QUICK_SETTINGS, DISABLE2_SYSTEM_ICONS,
-                    DISABLE2_NOTIFICATION_SHADE})
+                    DISABLE2_NOTIFICATION_SHADE, DISABLE2_GLOBAL_ACTIONS})
     @Retention(RetentionPolicy.SOURCE)
     public @interface Disable2Flags {}
 
diff --git a/android/app/TaskStackListener.java b/android/app/TaskStackListener.java
index 402e209..895d12a 100644
--- a/android/app/TaskStackListener.java
+++ b/android/app/TaskStackListener.java
@@ -77,7 +77,7 @@
     }
 
     @Override
-    public void onTaskRemovalStarted(int taskId) {
+    public void onTaskRemovalStarted(int taskId) throws RemoteException {
     }
 
     @Override
@@ -91,11 +91,10 @@
     }
 
     @Override
-    public void onTaskProfileLocked(int taskId, int userId) {
+    public void onTaskProfileLocked(int taskId, int userId) throws RemoteException {
     }
 
     @Override
-    public void onTaskSnapshotChanged(int taskId, TaskSnapshot snapshot)
-            throws RemoteException {
+    public void onTaskSnapshotChanged(int taskId, TaskSnapshot snapshot) throws RemoteException {
     }
 }
diff --git a/android/app/WallpaperManager.java b/android/app/WallpaperManager.java
index 942cc99..081bd81 100644
--- a/android/app/WallpaperManager.java
+++ b/android/app/WallpaperManager.java
@@ -388,11 +388,12 @@
 
         public Bitmap peekWallpaperBitmap(Context context, boolean returnDefault,
                 @SetWallpaperFlags int which) {
-            return peekWallpaperBitmap(context, returnDefault, which, context.getUserId());
+            return peekWallpaperBitmap(context, returnDefault, which, context.getUserId(),
+                    false /* hardware */);
         }
 
         public Bitmap peekWallpaperBitmap(Context context, boolean returnDefault,
-                @SetWallpaperFlags int which, int userId) {
+                @SetWallpaperFlags int which, int userId, boolean hardware) {
             if (mService != null) {
                 try {
                     if (!mService.isWallpaperSupported(context.getOpPackageName())) {
@@ -409,7 +410,7 @@
                 mCachedWallpaper = null;
                 mCachedWallpaperUserId = 0;
                 try {
-                    mCachedWallpaper = getCurrentWallpaperLocked(context, userId);
+                    mCachedWallpaper = getCurrentWallpaperLocked(context, userId, hardware);
                     mCachedWallpaperUserId = userId;
                 } catch (OutOfMemoryError e) {
                     Log.w(TAG, "Out of memory loading the current wallpaper: " + e);
@@ -447,7 +448,7 @@
             }
         }
 
-        private Bitmap getCurrentWallpaperLocked(Context context, int userId) {
+        private Bitmap getCurrentWallpaperLocked(Context context, int userId, boolean hardware) {
             if (mService == null) {
                 Log.w(TAG, "WallpaperService not running");
                 return null;
@@ -460,6 +461,9 @@
                 if (fd != null) {
                     try {
                         BitmapFactory.Options options = new BitmapFactory.Options();
+                        if (hardware) {
+                            options.inPreferredConfig = Bitmap.Config.HARDWARE;
+                        }
                         return BitmapFactory.decodeFileDescriptor(
                                 fd.getFileDescriptor(), null, options);
                     } catch (OutOfMemoryError e) {
@@ -814,12 +818,23 @@
     }
 
     /**
-     * Like {@link #getDrawable()} but returns a Bitmap.
+     * Like {@link #getDrawable()} but returns a Bitmap with default {@link Bitmap.Config}.
      *
      * @hide
      */
     public Bitmap getBitmap() {
-        return getBitmapAsUser(mContext.getUserId());
+        return getBitmap(false);
+    }
+
+    /**
+     * Like {@link #getDrawable()} but returns a Bitmap.
+     *
+     * @param hardware Asks for a hardware backed bitmap.
+     * @see Bitmap.Config#HARDWARE
+     * @hide
+     */
+    public Bitmap getBitmap(boolean hardware) {
+        return getBitmapAsUser(mContext.getUserId(), hardware);
     }
 
     /**
@@ -827,8 +842,8 @@
      *
      * @hide
      */
-    public Bitmap getBitmapAsUser(int userId) {
-        return sGlobals.peekWallpaperBitmap(mContext, true, FLAG_SYSTEM, userId);
+    public Bitmap getBitmapAsUser(int userId, boolean hardware) {
+        return sGlobals.peekWallpaperBitmap(mContext, true, FLAG_SYSTEM, userId, hardware);
     }
 
     /**
diff --git a/android/app/WindowConfiguration.java b/android/app/WindowConfiguration.java
index 6b40538..251863c 100644
--- a/android/app/WindowConfiguration.java
+++ b/android/app/WindowConfiguration.java
@@ -511,7 +511,8 @@
         return windowingMode != WINDOWING_MODE_FREEFORM && windowingMode != WINDOWING_MODE_PINNED;
     }
 
-    private static String windowingModeToString(@WindowingMode int windowingMode) {
+    /** @hide */
+    public static String windowingModeToString(@WindowingMode int windowingMode) {
         switch (windowingMode) {
             case WINDOWING_MODE_UNDEFINED: return "undefined";
             case WINDOWING_MODE_FULLSCREEN: return "fullscreen";
diff --git a/android/app/admin/DevicePolicyManager.java b/android/app/admin/DevicePolicyManager.java
index 3c53063..ab8edee 100644
--- a/android/app/admin/DevicePolicyManager.java
+++ b/android/app/admin/DevicePolicyManager.java
@@ -6533,6 +6533,52 @@
     }
 
     /**
+     * Called by device owner to set the system wall clock time. This only takes effect if called
+     * when {@link android.provider.Settings.Global#AUTO_TIME} is 0, otherwise {@code false} will be
+     * returned.
+     *
+     * @param admin Which {@link DeviceAdminReceiver} this request is associated with
+     * @param millis time in milliseconds since the Epoch
+     * @return {@code true} if set time succeeded, {@code false} otherwise.
+     * @throws SecurityException if {@code admin} is not a device owner.
+     */
+    public boolean setTime(@NonNull ComponentName admin, long millis) {
+        throwIfParentInstance("setTime");
+        if (mService != null) {
+            try {
+                return mService.setTime(admin, millis);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Called by device owner to set the system's persistent default time zone. This only takes
+     * effect if called when {@link android.provider.Settings.Global#AUTO_TIME_ZONE} is 0, otherwise
+     * {@code false} will be returned.
+     *
+     * @see android.app.AlarmManager#setTimeZone(String)
+     * @param admin Which {@link DeviceAdminReceiver} this request is associated with
+     * @param timeZone one of the Olson ids from the list returned by
+     *     {@link java.util.TimeZone#getAvailableIDs}
+     * @return {@code true} if set timezone succeeded, {@code false} otherwise.
+     * @throws SecurityException if {@code admin} is not a device owner.
+     */
+    public boolean setTimeZone(@NonNull ComponentName admin, String timeZone) {
+        throwIfParentInstance("setTimeZone");
+        if (mService != null) {
+            try {
+                return mService.setTimeZone(admin, timeZone);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+        return false;
+    }
+
+    /**
      * Called by profile or device owners to update {@link android.provider.Settings.Secure}
      * settings. Validation that the value of the setting is in the correct form for the setting
      * type should be performed by the caller.
diff --git a/android/app/assist/AssistStructure.java b/android/app/assist/AssistStructure.java
index d9b7cd7..e491a4f 100644
--- a/android/app/assist/AssistStructure.java
+++ b/android/app/assist/AssistStructure.java
@@ -616,6 +616,9 @@
         CharSequence[] mAutofillOptions;
         boolean mSanitized;
         HtmlInfo mHtmlInfo;
+        int mMinEms = -1;
+        int mMaxEms = -1;
+        int mMaxLength = -1;
 
         // POJO used to override some autofill-related values when the node is parcelized.
         // Not written to parcel.
@@ -713,6 +716,9 @@
                 if (p instanceof HtmlInfo) {
                     mHtmlInfo = (HtmlInfo) p;
                 }
+                mMinEms = in.readInt();
+                mMaxEms = in.readInt();
+                mMaxLength = in.readInt();
             }
             if ((flags&FLAGS_HAS_LARGE_COORDS) != 0) {
                 mX = in.readInt();
@@ -876,6 +882,9 @@
                 } else {
                     out.writeParcelable(null, 0);
                 }
+                out.writeInt(mMinEms);
+                out.writeInt(mMaxEms);
+                out.writeInt(mMaxLength);
             }
             if ((flags&FLAGS_HAS_LARGE_COORDS) != 0) {
                 out.writeInt(mX);
@@ -1444,6 +1453,39 @@
         public ViewNode getChildAt(int index) {
             return mChildren[index];
         }
+
+        /**
+         * Returns the minimum width in ems of the text associated with this node, or {@code -1}
+         * if not supported by the node.
+         *
+         * <p>It's only relevant when the {@link AssistStructure} is used for autofill purposes,
+         * not for assist purposes.
+         */
+        public int getMinTextEms() {
+            return mMinEms;
+        }
+
+        /**
+         * Returns the maximum width in ems of the text associated with this node, or {@code -1}
+         * if not supported by the node.
+         *
+         * <p>It's only relevant when the {@link AssistStructure} is used for autofill purposes,
+         * not for assist purposes.
+         */
+        public int getMaxTextEms() {
+            return mMaxEms;
+        }
+
+        /**
+         * Returns the maximum length of the text associated with this node node, or {@code -1}
+         * if not supported by the node or not set.
+         *
+         * <p>It's only relevant when the {@link AssistStructure} is used for autofill purposes,
+         * not for assist purposes.
+         */
+        public int getMaxTextLength() {
+            return mMaxLength;
+        }
     }
 
     /**
@@ -1776,6 +1818,21 @@
         }
 
         @Override
+        public void setMinTextEms(int minEms) {
+            mNode.mMinEms = minEms;
+        }
+
+        @Override
+        public void setMaxTextEms(int maxEms) {
+            mNode.mMaxEms = maxEms;
+        }
+
+        @Override
+        public void setMaxTextLength(int maxLength) {
+            mNode.mMaxLength = maxLength;
+        }
+
+        @Override
         public void setDataIsSensitive(boolean sensitive) {
             mNode.mSanitized = !sensitive;
         }
diff --git a/android/app/job/JobScheduler.java b/android/app/job/JobScheduler.java
index 3868439..0deb2e1 100644
--- a/android/app/job/JobScheduler.java
+++ b/android/app/job/JobScheduler.java
@@ -24,7 +24,6 @@
 import android.annotation.SystemService;
 import android.content.ClipData;
 import android.content.Context;
-import android.content.Intent;
 import android.os.Bundle;
 import android.os.PersistableBundle;
 
@@ -40,16 +39,18 @@
  * and how to construct them. You will construct these JobInfo objects and pass them to the
  * JobScheduler with {@link #schedule(JobInfo)}. When the criteria declared are met, the
  * system will execute this job on your application's {@link android.app.job.JobService}.
- * You identify which JobService is meant to execute the logic for your job when you create the
- * JobInfo with
+ * You identify the service component that implements the logic for your job when you
+ * construct the JobInfo using
  * {@link android.app.job.JobInfo.Builder#JobInfo.Builder(int,android.content.ComponentName)}.
  * </p>
  * <p>
- * The framework will be intelligent about when you receive your callbacks, and attempt to batch
- * and defer them as much as possible. Typically if you don't specify a deadline on your job, it
- * can be run at any moment depending on the current state of the JobScheduler's internal queue,
- * however it might be deferred as long as until the next time the device is connected to a power
- * source.
+ * The framework will be intelligent about when it executes jobs, and attempt to batch
+ * and defer them as much as possible. Typically if you don't specify a deadline on a job, it
+ * can be run at any moment depending on the current state of the JobScheduler's internal queue.
+ * <p>
+ * While a job is running, the system holds a wakelock on behalf of your app.  For this reason,
+ * you do not need to take any action to guarantee that the device stays awake for the
+ * duration of the job.
  * </p>
  * <p>You do not
  * instantiate this class directly; instead, retrieve it through
@@ -141,30 +142,34 @@
             int userId, String tag);
 
     /**
-     * Cancel a job that is pending in the JobScheduler.
-     * @param jobId unique identifier for this job. Obtain this value from the jobs returned by
-     * {@link #getAllPendingJobs()}.
+     * Cancel the specified job.  If the job is currently executing, it is stopped
+     * immediately and the return value from its {@link JobService#onStopJob(JobParameters)}
+     * method is ignored.
+     *
+     * @param jobId unique identifier for the job to be canceled, as supplied to
+     *     {@link JobInfo.Builder#JobInfo.Builder(int, android.content.ComponentName)
+     *     JobInfo.Builder(int, android.content.ComponentName)}.
      */
     public abstract void cancel(int jobId);
 
     /**
-     * Cancel all jobs that have been registered with the JobScheduler by this package.
+     * Cancel <em>all</em> jobs that have been scheduled by the calling application.
      */
     public abstract void cancelAll();
 
     /**
-     * Retrieve all jobs for this package that are pending in the JobScheduler.
+     * Retrieve all jobs that have been scheduled by the calling application.
      *
-     * @return a list of all the jobs registered by this package that have not
-     *         yet been executed.
+     * @return a list of all of the app's scheduled jobs.  This includes jobs that are
+     *     currently started as well as those that are still waiting to run.
      */
     public abstract @NonNull List<JobInfo> getAllPendingJobs();
 
     /**
-     * Retrieve a specific job for this package that is pending in the
-     * JobScheduler.
+     * Look up the description of a scheduled job.
      *
-     * @return job registered by this package that has not yet been executed.
+     * @return The {@link JobInfo} description of the given scheduled job, or {@code null}
+     *     if the supplied job ID does not correspond to any job.
      */
     public abstract @Nullable JobInfo getPendingJob(int jobId);
 }
diff --git a/android/app/job/JobService.java b/android/app/job/JobService.java
index 9096b47..69afed2 100644
--- a/android/app/job/JobService.java
+++ b/android/app/job/JobService.java
@@ -18,16 +18,7 @@
 
 import android.app.Service;
 import android.content.Intent;
-import android.os.Handler;
 import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
-import android.os.RemoteException;
-import android.util.Log;
-
-import com.android.internal.annotations.GuardedBy;
-
-import java.lang.ref.WeakReference;
 
 /**
  * <p>Entry point for the callback from the {@link android.app.job.JobScheduler}.</p>
@@ -55,7 +46,7 @@
      * </pre>
      *
      * <p>If a job service is declared in the manifest but not protected with this
-     * permission, that service will be ignored by the OS.
+     * permission, that service will be ignored by the system.
      */
     public static final String PERMISSION_BIND =
             "android.permission.BIND_JOB_SERVICE";
@@ -81,14 +72,36 @@
     }
 
     /**
-     * Override this method with the callback logic for your job. Any such logic needs to be
-     * performed on a separate thread, as this function is executed on your application's main
-     * thread.
+     * Called to indicate that the job has begun executing.  Override this method with the
+     * logic for your job.  Like all other component lifecycle callbacks, this method executes
+     * on your application's main thread.
+     * <p>
+     * Return {@code true} from this method if your job needs to continue running.  If you
+     * do this, the job remains active until you call
+     * {@link #jobFinished(JobParameters, boolean)} to tell the system that it has completed
+     * its work, or until the job's required constraints are no longer satisfied.  For
+     * example, if the job was scheduled using
+     * {@link JobInfo.Builder#setRequiresCharging(boolean) setRequiresCharging(true)},
+     * it will be immediately halted by the system if the user unplugs the device from power,
+     * the job's {@link #onStopJob(JobParameters)} callback will be invoked, and the app
+     * will be expected to shut down all ongoing work connected with that job.
+     * <p>
+     * The system holds a wakelock on behalf of your app as long as your job is executing.
+     * This wakelock is acquired before this method is invoked, and is not released until either
+     * you call {@link #jobFinished(JobParameters, boolean)}, or after the system invokes
+     * {@link #onStopJob(JobParameters)} to notify your job that it is being shut down
+     * prematurely.
+     * <p>
+     * Returning {@code false} from this method means your job is already finished.  The
+     * system's wakelock for the job will be released, and {@link #onStopJob(JobParameters)}
+     * will not be invoked.
      *
-     * @param params Parameters specifying info about this job, including the extras bundle you
-     *               optionally provided at job-creation time.
-     * @return True if your service needs to process the work (on a separate thread). False if
-     * there's no more work to be done for this job.
+     * @param params Parameters specifying info about this job, including the optional
+     *     extras configured with {@link JobInfo.Builder#setExtras(android.os.PersistableBundle).
+     *     This object serves to identify this specific running job instance when calling
+     *     {@link #jobFinished(JobParameters, boolean)}.
+     * @return {@code true} if your service will continue running, using a separate thread
+     *     when appropriate.  {@code false} means that this job has completed its work.
      */
     public abstract boolean onStartJob(JobParameters params);
 
@@ -101,37 +114,44 @@
      * {@link android.app.job.JobInfo.Builder#setRequiredNetworkType(int)}, yet while your
      * job was executing the user toggled WiFi. Another example is if you had specified
      * {@link android.app.job.JobInfo.Builder#setRequiresDeviceIdle(boolean)}, and the phone left its
-     * idle maintenance window. You are solely responsible for the behaviour of your application
-     * upon receipt of this message; your app will likely start to misbehave if you ignore it. One
-     * immediate repercussion is that the system will cease holding a wakelock for you.</p>
+     * idle maintenance window. You are solely responsible for the behavior of your application
+     * upon receipt of this message; your app will likely start to misbehave if you ignore it.
+     * <p>
+     * Once this method returns, the system releases the wakelock that it is holding on
+     * behalf of the job.</p>
      *
-     * @param params Parameters specifying info about this job.
-     * @return True to indicate to the JobManager whether you'd like to reschedule this job based
-     * on the retry criteria provided at job creation-time. False to drop the job. Regardless of
-     * the value returned, your job must stop executing.
+     * @param params The parameters identifying this job, as supplied to
+     *               the job in the {@link #onStartJob(JobParameters)} callback.
+     * @return {@code true} to indicate to the JobManager whether you'd like to reschedule
+     * this job based on the retry criteria provided at job creation-time; or {@code false}
+     * to end the job entirely.  Regardless of the value returned, your job must stop executing.
      */
     public abstract boolean onStopJob(JobParameters params);
 
     /**
-     * Call this to inform the JobManager you've finished executing. This can be called from any
-     * thread, as it will ultimately be run on your application's main thread. When the system
-     * receives this message it will release the wakelock being held.
+     * Call this to inform the JobScheduler that the job has finished its work.  When the
+     * system receives this message, it releases the wakelock being held for the job.
      * <p>
-     *     You can specify post-execution behaviour to the scheduler here with
-     *     <code>needsReschedule </code>. This will apply a back-off timer to your job based on
-     *     the default, or what was set with
-     *     {@link android.app.job.JobInfo.Builder#setBackoffCriteria(long, int)}. The original
-     *     requirements are always honoured even for a backed-off job. Note that a job running in
-     *     idle mode will not be backed-off. Instead what will happen is the job will be re-added
-     *     to the queue and re-executed within a future idle maintenance window.
+     * You can request that the job be scheduled again by passing {@code true} as
+     * the <code>wantsReschedule</code> parameter. This will apply back-off policy
+     * for the job; this policy can be adjusted through the
+     * {@link android.app.job.JobInfo.Builder#setBackoffCriteria(long, int)} method
+     * when the job is originally scheduled.  The job's initial
+     * requirements are preserved when jobs are rescheduled, regardless of backed-off
+     * policy.
+     * <p class="note">
+     * A job running while the device is dozing will not be rescheduled with the normal back-off
+     * policy.  Instead, the job will be re-added to the queue and executed again during
+     * a future idle maintenance window.
      * </p>
      *
-     * @param params Parameters specifying system-provided info about this job, this was given to
-     *               your application in {@link #onStartJob(JobParameters)}.
-     * @param needsReschedule True if this job should be rescheduled according to the back-off
-     *                        criteria specified at schedule-time. False otherwise.
+     * @param params The parameters identifying this job, as supplied to
+     *               the job in the {@link #onStartJob(JobParameters)} callback.
+     * @param wantsReschedule {@code true} if this job should be rescheduled according
+     *     to the back-off criteria specified when it was first scheduled; {@code false}
+     *     otherwise.
      */
-    public final void jobFinished(JobParameters params, boolean needsReschedule) {
-        mEngine.jobFinished(params, needsReschedule);
+    public final void jobFinished(JobParameters params, boolean wantsReschedule) {
+        mEngine.jobFinished(params, wantsReschedule);
     }
-}
\ No newline at end of file
+}
diff --git a/android/slice/Slice.java b/android/app/slice/Slice.java
similarity index 65%
rename from android/slice/Slice.java
rename to android/app/slice/Slice.java
index 5768654..7f9f74b 100644
--- a/android/slice/Slice.java
+++ b/android/app/slice/Slice.java
@@ -14,38 +14,35 @@
  * limitations under the License.
  */
 
-package android.slice;
-
-import static android.slice.SliceItem.TYPE_ACTION;
-import static android.slice.SliceItem.TYPE_COLOR;
-import static android.slice.SliceItem.TYPE_IMAGE;
-import static android.slice.SliceItem.TYPE_REMOTE_INPUT;
-import static android.slice.SliceItem.TYPE_REMOTE_VIEW;
-import static android.slice.SliceItem.TYPE_SLICE;
-import static android.slice.SliceItem.TYPE_TEXT;
-import static android.slice.SliceItem.TYPE_TIMESTAMP;
+package android.app.slice;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.StringDef;
 import android.app.PendingIntent;
 import android.app.RemoteInput;
+import android.content.ContentResolver;
+import android.content.IContentProvider;
 import android.graphics.drawable.Icon;
 import android.net.Uri;
+import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.os.RemoteException;
 import android.widget.RemoteViews;
 
 import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.Preconditions;
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.List;
 
 /**
  * A slice is a piece of app content and actions that can be surfaced outside of the app.
  *
  * <p>They are constructed using {@link Builder} in a tree structure
  * that provides the OS some information about how the content should be displayed.
- * @hide
  */
 public final class Slice implements Parcelable {
 
@@ -53,7 +50,7 @@
      * @hide
      */
     @StringDef({HINT_TITLE, HINT_LIST, HINT_LIST_ITEM, HINT_LARGE, HINT_ACTIONS, HINT_SELECTED,
-            HINT_SOURCE, HINT_MESSAGE, HINT_HORIZONTAL, HINT_NO_TINT})
+            HINT_SOURCE, HINT_MESSAGE, HINT_HORIZONTAL, HINT_NO_TINT, HINT_PARTIAL})
     public @interface SliceHint{ }
 
     /**
@@ -102,6 +99,12 @@
      * Hint to indicate that this content should not be tinted.
      */
     public static final String HINT_NO_TINT     = "no_tint";
+    /**
+     * Hint to indicate that this slice is incomplete and an update will be sent once
+     * loading is complete. Slices which contain HINT_PARTIAL will not be cached by the
+     * OS and should not be cached by apps.
+     */
+    public static final String HINT_PARTIAL     = "partial";
 
     // These two are coming over from prototyping, but we probably don't want in
     // public API, at least not right now.
@@ -109,19 +112,12 @@
      * @hide
      */
     public static final String HINT_ALT         = "alt";
-    /**
-     * @hide
-     */
-    public static final String HINT_PARTIAL     = "partial";
 
     private final SliceItem[] mItems;
     private final @SliceHint String[] mHints;
     private Uri mUri;
 
-    /**
-     * @hide
-     */
-    public Slice(ArrayList<SliceItem> items, @SliceHint String[] hints, Uri uri) {
+    Slice(ArrayList<SliceItem> items, @SliceHint String[] hints, Uri uri) {
         mHints = hints;
         mItems = items.toArray(new SliceItem[items.size()]);
         mUri = uri;
@@ -147,15 +143,15 @@
     /**
      * @return All child {@link SliceItem}s that this Slice contains.
      */
-    public SliceItem[] getItems() {
-        return mItems;
+    public List<SliceItem> getItems() {
+        return Arrays.asList(mItems);
     }
 
     /**
      * @return All hints associated with this Slice.
      */
-    public @SliceHint String[] getHints() {
-        return mHints;
+    public @SliceHint List<String> getHints() {
+        return Arrays.asList(mHints);
     }
 
     /**
@@ -163,14 +159,14 @@
      */
     public SliceItem getPrimaryIcon() {
         for (SliceItem item : getItems()) {
-            if (item.getType() == TYPE_IMAGE) {
+            if (item.getType() == SliceItem.TYPE_IMAGE) {
                 return item;
             }
-            if (!(item.getType() == TYPE_SLICE && item.hasHint(Slice.HINT_LIST))
+            if (!(item.getType() == SliceItem.TYPE_SLICE && item.hasHint(Slice.HINT_LIST))
                     && !item.hasHint(Slice.HINT_ACTIONS)
                     && !item.hasHint(Slice.HINT_LIST_ITEM)
-                    && (item.getType() != TYPE_ACTION)) {
-                SliceItem icon = SliceQuery.find(item, TYPE_IMAGE);
+                    && (item.getType() != SliceItem.TYPE_ACTION)) {
+                SliceItem icon = SliceQuery.find(item, SliceItem.TYPE_IMAGE);
                 if (icon != null) return icon;
             }
         }
@@ -235,10 +231,18 @@
         }
 
         /**
+         * Add hints to the Slice being constructed
+         */
+        public Builder addHints(@SliceHint List<String> hints) {
+            return addHints(hints.toArray(new String[hints.size()]));
+        }
+
+        /**
          * Add a sub-slice to the slice being constructed
          */
         public Builder addSubSlice(@NonNull Slice slice) {
-            mItems.add(new SliceItem(slice, TYPE_SLICE, slice.getHints()));
+            mItems.add(new SliceItem(slice, SliceItem.TYPE_SLICE, slice.getHints().toArray(
+                    new String[slice.getHints().size()])));
             return this;
         }
 
@@ -246,7 +250,7 @@
          * Add an action to the slice being constructed
          */
         public Slice.Builder addAction(@NonNull PendingIntent action, @NonNull Slice s) {
-            mItems.add(new SliceItem(action, s, TYPE_ACTION, new String[0]));
+            mItems.add(new SliceItem(action, s, SliceItem.TYPE_ACTION, new String[0]));
             return this;
         }
 
@@ -254,31 +258,53 @@
          * Add text to the slice being constructed
          */
         public Builder addText(CharSequence text, @SliceHint String... hints) {
-            mItems.add(new SliceItem(text, TYPE_TEXT, hints));
+            mItems.add(new SliceItem(text, SliceItem.TYPE_TEXT, hints));
             return this;
         }
 
         /**
+         * Add text to the slice being constructed
+         */
+        public Builder addText(CharSequence text, @SliceHint List<String> hints) {
+            return addText(text, hints.toArray(new String[hints.size()]));
+        }
+
+        /**
          * Add an image to the slice being constructed
          */
         public Builder addIcon(Icon icon, @SliceHint String... hints) {
-            mItems.add(new SliceItem(icon, TYPE_IMAGE, hints));
+            mItems.add(new SliceItem(icon, SliceItem.TYPE_IMAGE, hints));
             return this;
         }
 
         /**
+         * Add an image to the slice being constructed
+         */
+        public Builder addIcon(Icon icon, @SliceHint List<String> hints) {
+            return addIcon(icon, hints.toArray(new String[hints.size()]));
+        }
+
+        /**
          * @hide This isn't final
          */
         public Builder addRemoteView(RemoteViews remoteView, @SliceHint String... hints) {
-            mItems.add(new SliceItem(remoteView, TYPE_REMOTE_VIEW, hints));
+            mItems.add(new SliceItem(remoteView, SliceItem.TYPE_REMOTE_VIEW, hints));
             return this;
         }
 
         /**
          * Add remote input to the slice being constructed
          */
+        public Slice.Builder addRemoteInput(RemoteInput remoteInput,
+                @SliceHint List<String> hints) {
+            return addRemoteInput(remoteInput, hints.toArray(new String[hints.size()]));
+        }
+
+        /**
+         * Add remote input to the slice being constructed
+         */
         public Slice.Builder addRemoteInput(RemoteInput remoteInput, @SliceHint String... hints) {
-            mItems.add(new SliceItem(remoteInput, TYPE_REMOTE_INPUT, hints));
+            mItems.add(new SliceItem(remoteInput, SliceItem.TYPE_REMOTE_INPUT, hints));
             return this;
         }
 
@@ -286,19 +312,33 @@
          * Add a color to the slice being constructed
          */
         public Builder addColor(int color, @SliceHint String... hints) {
-            mItems.add(new SliceItem(color, TYPE_COLOR, hints));
+            mItems.add(new SliceItem(color, SliceItem.TYPE_COLOR, hints));
             return this;
         }
 
         /**
+         * Add a color to the slice being constructed
+         */
+        public Builder addColor(int color, @SliceHint List<String> hints) {
+            return addColor(color, hints.toArray(new String[hints.size()]));
+        }
+
+        /**
          * Add a timestamp to the slice being constructed
          */
         public Slice.Builder addTimestamp(long time, @SliceHint String... hints) {
-            mItems.add(new SliceItem(time, TYPE_TIMESTAMP, hints));
+            mItems.add(new SliceItem(time, SliceItem.TYPE_TIMESTAMP, hints));
             return this;
         }
 
         /**
+         * Add a timestamp to the slice being constructed
+         */
+        public Slice.Builder addTimestamp(long time, @SliceHint List<String> hints) {
+            return addTimestamp(time, hints.toArray(new String[hints.size()]));
+        }
+
+        /**
          * Construct the slice.
          */
         public Slice build() {
@@ -322,18 +362,18 @@
      * @hide
      * @return A string representation of this slice.
      */
-    public String getString() {
-        return getString("");
+    public String toString() {
+        return toString("");
     }
 
-    private String getString(String indent) {
+    private String toString(String indent) {
         StringBuilder sb = new StringBuilder();
         for (int i = 0; i < mItems.length; i++) {
             sb.append(indent);
-            if (mItems[i].getType() == TYPE_SLICE) {
+            if (mItems[i].getType() == SliceItem.TYPE_SLICE) {
                 sb.append("slice:\n");
-                sb.append(mItems[i].getSlice().getString(indent + "   "));
-            } else if (mItems[i].getType() == TYPE_TEXT) {
+                sb.append(mItems[i].getSlice().toString(indent + "   "));
+            } else if (mItems[i].getType() == SliceItem.TYPE_TEXT) {
                 sb.append("text: ");
                 sb.append(mItems[i].getText());
                 sb.append("\n");
@@ -344,4 +384,34 @@
         }
         return sb.toString();
     }
+
+    /**
+     * Turns a slice Uri into slice content.
+     *
+     * @param resolver ContentResolver to be used.
+     * @param uri The URI to a slice provider
+     * @return The Slice provided by the app or null if none is given.
+     * @see Slice
+     */
+    public static @Nullable Slice bindSlice(ContentResolver resolver, @NonNull Uri uri) {
+        Preconditions.checkNotNull(uri, "uri");
+        IContentProvider provider = resolver.acquireProvider(uri);
+        if (provider == null) {
+            throw new IllegalArgumentException("Unknown URI " + uri);
+        }
+        try {
+            Bundle extras = new Bundle();
+            extras.putParcelable(SliceProvider.EXTRA_BIND_URI, uri);
+            final Bundle res = provider.call(resolver.getPackageName(), SliceProvider.METHOD_SLICE,
+                    null, extras);
+            Bundle.setDefusable(res, true);
+            return res.getParcelable(SliceProvider.EXTRA_SLICE);
+        } catch (RemoteException e) {
+            // Arbitrary and not worth documenting, as Activity
+            // Manager will kill this process shortly anyway.
+            return null;
+        } finally {
+            resolver.releaseProvider(provider);
+        }
+    }
 }
diff --git a/android/slice/SliceItem.java b/android/app/slice/SliceItem.java
similarity index 93%
rename from android/slice/SliceItem.java
rename to android/app/slice/SliceItem.java
index 2827ab9..6e69b05 100644
--- a/android/slice/SliceItem.java
+++ b/android/app/slice/SliceItem.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package android.slice;
+package android.app.slice;
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
@@ -23,13 +23,15 @@
 import android.graphics.drawable.Icon;
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.slice.Slice.SliceHint;
 import android.text.TextUtils;
 import android.util.Pair;
 import android.widget.RemoteViews;
 
 import com.android.internal.util.ArrayUtils;
 
+import java.util.Arrays;
+import java.util.List;
+
 
 /**
  * A SliceItem is a single unit in the tree structure of a {@link Slice}.
@@ -47,7 +49,6 @@
  * The hints that a {@link SliceItem} are a set of strings which annotate
  * the content. The hints that are guaranteed to be understood by the system
  * are defined on {@link Slice}.
- * @hide
  */
 public final class SliceItem implements Parcelable {
 
@@ -97,14 +98,15 @@
     /**
      * @hide
      */
-    protected @SliceHint String[] mHints;
+    protected @Slice.SliceHint
+    String[] mHints;
     private final int mType;
     private final Object mObj;
 
     /**
      * @hide
      */
-    public SliceItem(Object obj, @SliceType int type, @SliceHint String[] hints) {
+    public SliceItem(Object obj, @SliceType int type, @Slice.SliceHint String[] hints) {
         mHints = hints;
         mType = type;
         mObj = obj;
@@ -113,7 +115,7 @@
     /**
      * @hide
      */
-    public SliceItem(PendingIntent intent, Slice slice, int type, @SliceHint String[] hints) {
+    public SliceItem(PendingIntent intent, Slice slice, int type, @Slice.SliceHint String[] hints) {
         this(new Pair<>(intent, slice), type, hints);
     }
 
@@ -121,14 +123,14 @@
      * Gets all hints associated with this SliceItem.
      * @return Array of hints.
      */
-    public @NonNull @SliceHint String[] getHints() {
-        return mHints;
+    public @NonNull @Slice.SliceHint List<String> getHints() {
+        return Arrays.asList(mHints);
     }
 
     /**
      * @hide
      */
-    public void addHint(@SliceHint String hint) {
+    public void addHint(@Slice.SliceHint String hint) {
         mHints = ArrayUtils.appendElement(String.class, mHints, hint);
     }
 
@@ -206,7 +208,7 @@
      * @param hint The hint to check for
      * @return true if this item contains the given hint
      */
-    public boolean hasHint(@SliceHint String hint) {
+    public boolean hasHint(@Slice.SliceHint String hint) {
         return ArrayUtils.contains(mHints, hint);
     }
 
@@ -234,7 +236,7 @@
     /**
      * @hide
      */
-    public boolean hasHints(@SliceHint String[] hints) {
+    public boolean hasHints(@Slice.SliceHint String[] hints) {
         if (hints == null) return true;
         for (String hint : hints) {
             if (!TextUtils.isEmpty(hint) && !ArrayUtils.contains(mHints, hint)) {
@@ -247,7 +249,7 @@
     /**
      * @hide
      */
-    public boolean hasAnyHints(@SliceHint String[] hints) {
+    public boolean hasAnyHints(@Slice.SliceHint String[] hints) {
         if (hints == null) return false;
         for (String hint : hints) {
             if (ArrayUtils.contains(mHints, hint)) {
diff --git a/android/slice/SliceProvider.java b/android/app/slice/SliceProvider.java
similarity index 76%
rename from android/slice/SliceProvider.java
rename to android/app/slice/SliceProvider.java
index 4e21371..df87b45 100644
--- a/android/slice/SliceProvider.java
+++ b/android/app/slice/SliceProvider.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.slice;
+package android.app.slice;
 
 import android.Manifest.permission;
 import android.content.ContentProvider;
@@ -26,6 +26,8 @@
 import android.os.CancellationSignal;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.StrictMode;
+import android.os.StrictMode.ThreadPolicy;
 import android.util.Log;
 
 import java.util.concurrent.CountDownLatch;
@@ -51,10 +53,15 @@
  * </pre>
  *
  * @see Slice
- * @hide
  */
 public abstract class SliceProvider extends ContentProvider {
 
+    /**
+     * This is the Android platform's MIME type for a slice: URI
+     * containing a slice implemented through {@link SliceProvider}.
+     */
+    public static final String SLICE_TYPE = "vnd.android.slice";
+
     private static final String TAG = "SliceProvider";
     /**
      * @hide
@@ -73,8 +80,18 @@
 
     /**
      * Implemented to create a slice. Will be called on the main thread.
+     * <p>
+     * onBindSlice should return as quickly as possible so that the UI tied
+     * to this slice can be responsive. No network or other IO will be allowed
+     * during onBindSlice. Any loading that needs to be done should happen
+     * off the main thread with a call to {@link ContentResolver#notifyChange(Uri, ContentObserver)}
+     * when the app is ready to provide the complete data in onBindSlice.
+     * <p>
+     *
      * @see {@link Slice}.
+     * @see {@link Slice#HINT_PARTIAL}
      */
+    // TODO: Provide alternate notifyChange that takes in the slice (i.e. notifyChange(Uri, Slice)).
     public abstract Slice onBindSlice(Uri sliceUri);
 
     @Override
@@ -120,11 +137,11 @@
     @Override
     public final String getType(Uri uri) {
         if (DEBUG) Log.d(TAG, "getType " + uri);
-        return null;
+        return SLICE_TYPE;
     }
 
     @Override
-    public final Bundle call(String method, String arg, Bundle extras) {
+    public Bundle call(String method, String arg, Bundle extras) {
         if (method.equals(METHOD_SLICE)) {
             getContext().enforceCallingPermission(permission.BIND_SLICE,
                     "Slice binding requires the permission BIND_SLICE");
@@ -143,8 +160,17 @@
         CountDownLatch latch = new CountDownLatch(1);
         Handler mainHandler = new Handler(Looper.getMainLooper());
         mainHandler.post(() -> {
-            output[0] = onBindSlice(sliceUri);
-            latch.countDown();
+            ThreadPolicy oldPolicy = StrictMode.getThreadPolicy();
+            try {
+                StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
+                        .detectAll()
+                        .penaltyDeath()
+                        .build());
+                output[0] = onBindSlice(sliceUri);
+            } finally {
+                StrictMode.setThreadPolicy(oldPolicy);
+                latch.countDown();
+            }
         });
         try {
             latch.await();
diff --git a/android/slice/SliceQuery.java b/android/app/slice/SliceQuery.java
similarity index 90%
rename from android/slice/SliceQuery.java
rename to android/app/slice/SliceQuery.java
index d99b26a..d1fe2c9 100644
--- a/android/slice/SliceQuery.java
+++ b/android/app/slice/SliceQuery.java
@@ -14,12 +14,8 @@
  * limitations under the License.
  */
 
-package android.slice;
+package android.app.slice;
 
-import static android.slice.SliceItem.TYPE_ACTION;
-import static android.slice.SliceItem.TYPE_SLICE;
-
-import java.util.Arrays;
 import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
@@ -114,7 +110,9 @@
      * @hide
      */
     public static SliceItem find(Slice s, int type, String[] hints, String[] nonHints) {
-        return find(new SliceItem(s, TYPE_SLICE, s.getHints()), type, hints, nonHints);
+        List<String> h = s.getHints();
+        return find(new SliceItem(s, SliceItem.TYPE_SLICE, h.toArray(new String[h.size()])), type,
+                hints, nonHints);
     }
 
     /**
@@ -140,8 +138,9 @@
             @Override
             public SliceItem next() {
                 SliceItem item = items.poll();
-                if (item.getType() == TYPE_SLICE || item.getType() == TYPE_ACTION) {
-                    items.addAll(Arrays.asList(item.getSlice().getItems()));
+                if (item.getType() == SliceItem.TYPE_SLICE
+                        || item.getType() == SliceItem.TYPE_ACTION) {
+                    items.addAll(item.getSlice().getItems());
                 }
                 return item;
             }
diff --git a/android/slice/views/ActionRow.java b/android/app/slice/views/ActionRow.java
similarity index 97%
rename from android/slice/views/ActionRow.java
rename to android/app/slice/views/ActionRow.java
index 93e9c03..c7d99f7 100644
--- a/android/slice/views/ActionRow.java
+++ b/android/app/slice/views/ActionRow.java
@@ -14,19 +14,19 @@
  * limitations under the License.
  */
 
-package android.slice.views;
+package android.app.slice.views;
 
 import android.app.PendingIntent;
 import android.app.PendingIntent.CanceledException;
 import android.app.RemoteInput;
+import android.app.slice.Slice;
+import android.app.slice.SliceItem;
+import android.app.slice.SliceQuery;
 import android.content.Context;
 import android.content.res.ColorStateList;
 import android.graphics.Color;
 import android.graphics.drawable.Icon;
 import android.os.AsyncTask;
-import android.slice.Slice;
-import android.slice.SliceItem;
-import android.slice.SliceQuery;
 import android.util.TypedValue;
 import android.view.View;
 import android.view.ViewParent;
diff --git a/android/slice/views/GridView.java b/android/app/slice/views/GridView.java
similarity index 94%
rename from android/slice/views/GridView.java
rename to android/app/slice/views/GridView.java
index 18a90f7..6f30c50 100644
--- a/android/slice/views/GridView.java
+++ b/android/app/slice/views/GridView.java
@@ -14,16 +14,16 @@
  * limitations under the License.
  */
 
-package android.slice.views;
+package android.app.slice.views;
 
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
 
+import android.app.slice.Slice;
+import android.app.slice.SliceItem;
+import android.app.slice.views.LargeSliceAdapter.SliceListView;
 import android.content.Context;
 import android.graphics.Color;
-import android.slice.Slice;
-import android.slice.SliceItem;
-import android.slice.views.LargeSliceAdapter.SliceListView;
 import android.util.AttributeSet;
 import android.util.TypedValue;
 import android.view.Gravity;
@@ -38,7 +38,7 @@
 import com.android.internal.R;
 
 import java.util.ArrayList;
-import java.util.Arrays;
+import java.util.List;
 
 /**
  * @hide
@@ -76,10 +76,10 @@
         removeAllViews();
         int total = 1;
         if (slice.getType() == SliceItem.TYPE_SLICE) {
-            SliceItem[] items = slice.getSlice().getItems();
-            total = items.length;
+            List<SliceItem> items = slice.getSlice().getItems();
+            total = items.size();
             for (int i = 0; i < total; i++) {
-                SliceItem item = items[i];
+                SliceItem item = items.get(i);
                 if (isFull()) {
                     continue;
                 }
@@ -142,7 +142,7 @@
             // TODO: Unify sporadic inflates that happen throughout the code.
             ArrayList<SliceItem> items = new ArrayList<>();
             if (item.getType() == SliceItem.TYPE_SLICE) {
-                items.addAll(Arrays.asList(item.getSlice().getItems()));
+                items.addAll(item.getSlice().getItems());
             }
             items.forEach(i -> {
                 Context context = getContext();
diff --git a/android/slice/views/LargeSliceAdapter.java b/android/app/slice/views/LargeSliceAdapter.java
similarity index 97%
rename from android/slice/views/LargeSliceAdapter.java
rename to android/app/slice/views/LargeSliceAdapter.java
index e77a1b2..6794ff9 100644
--- a/android/slice/views/LargeSliceAdapter.java
+++ b/android/app/slice/views/LargeSliceAdapter.java
@@ -14,13 +14,13 @@
  * limitations under the License.
  */
 
-package android.slice.views;
+package android.app.slice.views;
 
+import android.app.slice.Slice;
+import android.app.slice.SliceItem;
+import android.app.slice.SliceQuery;
+import android.app.slice.views.LargeSliceAdapter.SliceViewHolder;
 import android.content.Context;
-import android.slice.Slice;
-import android.slice.SliceItem;
-import android.slice.SliceQuery;
-import android.slice.views.LargeSliceAdapter.SliceViewHolder;
 import android.util.ArrayMap;
 import android.view.LayoutInflater;
 import android.view.View;
diff --git a/android/slice/views/LargeTemplateView.java b/android/app/slice/views/LargeTemplateView.java
similarity index 92%
rename from android/slice/views/LargeTemplateView.java
rename to android/app/slice/views/LargeTemplateView.java
index d53e8fc..9e22516 100644
--- a/android/slice/views/LargeTemplateView.java
+++ b/android/app/slice/views/LargeTemplateView.java
@@ -14,22 +14,21 @@
  * limitations under the License.
  */
 
-package android.slice.views;
+package android.app.slice.views;
 
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
 
+import android.app.slice.Slice;
+import android.app.slice.SliceItem;
+import android.app.slice.SliceQuery;
+import android.app.slice.views.SliceView.SliceModeView;
 import android.content.Context;
-import android.slice.Slice;
-import android.slice.SliceItem;
-import android.slice.SliceQuery;
-import android.slice.views.SliceView.SliceModeView;
 import android.util.TypedValue;
 
 import com.android.internal.widget.LinearLayoutManager;
 import com.android.internal.widget.RecyclerView;
 
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.List;
 
 /**
@@ -86,7 +85,7 @@
         if (slice.hasHint(Slice.HINT_LIST)) {
             addList(slice, items);
         } else {
-            Arrays.asList(slice.getItems()).forEach(item -> {
+            slice.getItems().forEach(item -> {
                 if (item.hasHint(Slice.HINT_ACTIONS)) {
                     return;
                 } else if (item.getType() == SliceItem.TYPE_COLOR) {
@@ -109,7 +108,7 @@
     }
 
     private void addList(Slice slice, List<SliceItem> items) {
-        List<SliceItem> sliceItems = Arrays.asList(slice.getItems());
+        List<SliceItem> sliceItems = slice.getItems();
         sliceItems.forEach(i -> i.addHint(Slice.HINT_LIST_ITEM));
         items.addAll(sliceItems);
     }
diff --git a/android/slice/views/MessageView.java b/android/app/slice/views/MessageView.java
similarity index 92%
rename from android/slice/views/MessageView.java
rename to android/app/slice/views/MessageView.java
index 7b03e0b..77252bf 100644
--- a/android/slice/views/MessageView.java
+++ b/android/app/slice/views/MessageView.java
@@ -14,16 +14,16 @@
  * limitations under the License.
  */
 
-package android.slice.views;
+package android.app.slice.views;
 
+import android.app.slice.Slice;
+import android.app.slice.SliceItem;
+import android.app.slice.SliceQuery;
+import android.app.slice.views.LargeSliceAdapter.SliceListView;
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.drawable.Drawable;
-import android.slice.Slice;
-import android.slice.SliceItem;
-import android.slice.SliceQuery;
-import android.slice.views.LargeSliceAdapter.SliceListView;
 import android.text.SpannableStringBuilder;
 import android.util.AttributeSet;
 import android.util.TypedValue;
diff --git a/android/slice/views/RemoteInputView.java b/android/app/slice/views/RemoteInputView.java
similarity index 99%
rename from android/slice/views/RemoteInputView.java
rename to android/app/slice/views/RemoteInputView.java
index a29bb5c..e53cb1e 100644
--- a/android/slice/views/RemoteInputView.java
+++ b/android/app/slice/views/RemoteInputView.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package android.slice.views;
+package android.app.slice.views;
 
 import android.animation.Animator;
 import android.app.Notification;
diff --git a/android/slice/views/ShortcutView.java b/android/app/slice/views/ShortcutView.java
similarity index 94%
rename from android/slice/views/ShortcutView.java
rename to android/app/slice/views/ShortcutView.java
index 8fe2f1a..b6790c7 100644
--- a/android/slice/views/ShortcutView.java
+++ b/android/app/slice/views/ShortcutView.java
@@ -14,20 +14,20 @@
  * limitations under the License.
  */
 
-package android.slice.views;
+package android.app.slice.views;
 
 import android.app.PendingIntent;
 import android.app.PendingIntent.CanceledException;
+import android.app.slice.Slice;
+import android.app.slice.SliceItem;
+import android.app.slice.SliceQuery;
+import android.app.slice.views.SliceView.SliceModeView;
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.Color;
 import android.graphics.drawable.ShapeDrawable;
 import android.graphics.drawable.shapes.OvalShape;
 import android.net.Uri;
-import android.slice.Slice;
-import android.slice.SliceItem;
-import android.slice.SliceQuery;
-import android.slice.views.SliceView.SliceModeView;
 import android.view.ViewGroup;
 
 import com.android.internal.R;
diff --git a/android/slice/views/SliceView.java b/android/app/slice/views/SliceView.java
similarity index 93%
rename from android/slice/views/SliceView.java
rename to android/app/slice/views/SliceView.java
index f379248..32484fc 100644
--- a/android/slice/views/SliceView.java
+++ b/android/app/slice/views/SliceView.java
@@ -14,23 +14,25 @@
  * limitations under the License.
  */
 
-package android.slice.views;
+package android.app.slice.views;
 
 import android.annotation.StringDef;
+import android.app.slice.Slice;
+import android.app.slice.SliceItem;
+import android.app.slice.SliceQuery;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.drawable.ColorDrawable;
 import android.net.Uri;
-import android.slice.Slice;
-import android.slice.SliceItem;
-import android.slice.SliceQuery;
 import android.util.Log;
 import android.view.MotionEvent;
 import android.view.View;
 import android.widget.FrameLayout;
 import android.widget.LinearLayout;
 
+import java.util.List;
+
 /**
  * A view that can display a {@link Slice} in different {@link SliceMode}'s.
  *
@@ -120,7 +122,7 @@
      */
     public void bindSlice(Uri sliceUri) {
         validate(sliceUri);
-        Slice s = mContext.getContentResolver().bindSlice(sliceUri);
+        Slice s = Slice.bindSlice(mContext.getContentResolver(), sliceUri);
         bindSlice(s);
     }
 
@@ -201,7 +203,7 @@
         }
         // TODO: Smarter mapping here from one state to the next.
         SliceItem color = SliceQuery.find(mCurrentSlice, SliceItem.TYPE_COLOR);
-        SliceItem[] items = mCurrentSlice.getItems();
+        List<SliceItem> items = mCurrentSlice.getItems();
         SliceItem actionRow = SliceQuery.find(mCurrentSlice, SliceItem.TYPE_SLICE,
                 Slice.HINT_ACTIONS,
                 Slice.HINT_ALT);
@@ -212,7 +214,7 @@
             addView(mCurrentView);
             addView(mActions);
         }
-        if (items.length > 1 || (items.length != 0 && items[0] != actionRow)) {
+        if (items.size() > 1 || (items.size() != 0 && items.get(0) != actionRow)) {
             mCurrentView.setVisibility(View.VISIBLE);
             mCurrentView.setSlice(mCurrentSlice);
         } else {
@@ -239,7 +241,7 @@
     }
 
     private static void validate(Uri sliceUri) {
-        if (!ContentResolver.SCHEME_SLICE.equals(sliceUri.getScheme())) {
+        if (!ContentResolver.SCHEME_CONTENT.equals(sliceUri.getScheme())) {
             throw new RuntimeException("Invalid uri " + sliceUri);
         }
         if (sliceUri.getPathSegments().size() == 0) {
diff --git a/android/slice/views/SliceViewUtil.java b/android/app/slice/views/SliceViewUtil.java
similarity index 99%
rename from android/slice/views/SliceViewUtil.java
rename to android/app/slice/views/SliceViewUtil.java
index 1b5a6d1..19e8e7c 100644
--- a/android/slice/views/SliceViewUtil.java
+++ b/android/app/slice/views/SliceViewUtil.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package android.slice.views;
+package android.app.slice.views;
 
 import android.annotation.ColorInt;
 import android.content.Context;
diff --git a/android/slice/views/SmallTemplateView.java b/android/app/slice/views/SmallTemplateView.java
similarity index 91%
rename from android/slice/views/SmallTemplateView.java
rename to android/app/slice/views/SmallTemplateView.java
index b0b181e..42b2d21 100644
--- a/android/slice/views/SmallTemplateView.java
+++ b/android/app/slice/views/SmallTemplateView.java
@@ -14,16 +14,16 @@
  * limitations under the License.
  */
 
-package android.slice.views;
+package android.app.slice.views;
 
 import android.app.PendingIntent.CanceledException;
+import android.app.slice.Slice;
+import android.app.slice.SliceItem;
+import android.app.slice.SliceQuery;
+import android.app.slice.views.LargeSliceAdapter.SliceListView;
+import android.app.slice.views.SliceView.SliceModeView;
 import android.content.Context;
 import android.os.AsyncTask;
-import android.slice.Slice;
-import android.slice.SliceItem;
-import android.slice.SliceQuery;
-import android.slice.views.LargeSliceAdapter.SliceListView;
-import android.slice.views.SliceView.SliceModeView;
 import android.view.View;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
@@ -34,7 +34,6 @@
 import java.text.Format;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Date;
 import java.util.List;
 
@@ -117,7 +116,7 @@
         int itemCount = 0;
         boolean hasSummary = false;
         ArrayList<SliceItem> sliceItems = new ArrayList<SliceItem>(
-                Arrays.asList(slice.getSlice().getItems()));
+                slice.getSlice().getItems());
         for (int i = 0; i < sliceItems.size(); i++) {
             SliceItem item = sliceItems.get(i);
             if (!hasSummary && item.getType() == SliceItem.TYPE_TEXT
@@ -140,9 +139,9 @@
                     mEndContainer.addView(tv);
                     itemCount++;
                 } else if (item.getType() == SliceItem.TYPE_SLICE) {
-                    SliceItem[] subItems = item.getSlice().getItems();
-                    for (int j = 0; j < subItems.length; j++) {
-                        sliceItems.add(subItems[j]);
+                    List<SliceItem> subItems = item.getSlice().getItems();
+                    for (int j = 0; j < subItems.size(); j++) {
+                        sliceItems.add(subItems.get(j));
                     }
                 }
             }
@@ -151,7 +150,8 @@
 
     @Override
     public void setSlice(Slice slice) {
-        setSliceItem(new SliceItem(slice, SliceItem.TYPE_SLICE, slice.getHints()));
+        setSliceItem(new SliceItem(slice, SliceItem.TYPE_SLICE,
+                slice.getHints().toArray(new String[slice.getHints().size()])));
     }
 
     /**
diff --git a/android/app/usage/UsageStatsManager.java b/android/app/usage/UsageStatsManager.java
index 051dccb..fd579fc 100644
--- a/android/app/usage/UsageStatsManager.java
+++ b/android/app/usage/UsageStatsManager.java
@@ -48,10 +48,10 @@
  * </pre>
  * A request for data in the middle of a time interval will include that interval.
  * <p/>
- * <b>NOTE:</b> This API requires the permission android.permission.PACKAGE_USAGE_STATS, which
- * is a system-level permission and will not be granted to third-party apps. However, declaring
- * the permission implies intention to use the API and the user of the device can grant permission
- * through the Settings application.
+ * <b>NOTE:</b> This API requires the permission android.permission.PACKAGE_USAGE_STATS.
+ * However, declaring the permission implies intention to use the API and the user of the device
+ * still needs to grant permission through the Settings application.
+ * See {@link android.provider.Settings#ACTION_USAGE_ACCESS_SETTINGS}
  */
 @SystemService(Context.USAGE_STATS_SERVICE)
 public final class UsageStatsManager {
@@ -122,7 +122,7 @@
      * @param intervalType The time interval by which the stats are aggregated.
      * @param beginTime The inclusive beginning of the range of stats to include in the results.
      * @param endTime The exclusive end of the range of stats to include in the results.
-     * @return A list of {@link UsageStats} or null if none are available.
+     * @return A list of {@link UsageStats}
      *
      * @see #INTERVAL_DAILY
      * @see #INTERVAL_WEEKLY
@@ -139,7 +139,7 @@
                 return slice.getList();
             }
         } catch (RemoteException e) {
-            // fallthrough and return null.
+            // fallthrough and return the empty list.
         }
         return Collections.emptyList();
     }
@@ -152,7 +152,7 @@
      * @param intervalType The time interval by which the stats are aggregated.
      * @param beginTime The inclusive beginning of the range of stats to include in the results.
      * @param endTime The exclusive end of the range of stats to include in the results.
-     * @return A list of {@link ConfigurationStats} or null if none are available.
+     * @return A list of {@link ConfigurationStats}
      */
     public List<ConfigurationStats> queryConfigurations(int intervalType, long beginTime,
             long endTime) {
@@ -185,7 +185,7 @@
                 return iter;
             }
         } catch (RemoteException e) {
-            // fallthrough and return null
+            // fallthrough and return empty result.
         }
         return sEmptyResults;
     }
@@ -197,8 +197,7 @@
      *
      * @param beginTime The inclusive beginning of the range of stats to include in the results.
      * @param endTime The exclusive end of the range of stats to include in the results.
-     * @return A {@link java.util.Map} keyed by package name, or null if no stats are
-     *         available.
+     * @return A {@link java.util.Map} keyed by package name
      */
     public Map<String, UsageStats> queryAndAggregateUsageStats(long beginTime, long endTime) {
         List<UsageStats> stats = queryUsageStats(INTERVAL_BEST, beginTime, endTime);
diff --git a/android/appwidget/AppWidgetManager.java b/android/appwidget/AppWidgetManager.java
index 969b19e..37bb6b0 100644
--- a/android/appwidget/AppWidgetManager.java
+++ b/android/appwidget/AppWidgetManager.java
@@ -20,17 +20,19 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SdkConstant;
-import android.annotation.SystemService;
 import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SystemService;
+import android.app.IServiceConnection;
 import android.app.PendingIntent;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentSender;
+import android.content.ServiceConnection;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.ShortcutInfo;
 import android.os.Bundle;
-import android.os.IBinder;
+import android.os.Handler;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.UserHandle;
@@ -1051,43 +1053,23 @@
      * The appWidgetId specified must already be bound to the calling AppWidgetHost via
      * {@link android.appwidget.AppWidgetManager#bindAppWidgetId AppWidgetManager.bindAppWidgetId()}.
      *
-     * @param packageName   The package from which the binding is requested.
      * @param appWidgetId   The AppWidget instance for which to bind the RemoteViewsService.
      * @param intent        The intent of the service which will be providing the data to the
      *                      RemoteViewsAdapter.
      * @param connection    The callback interface to be notified when a connection is made or lost.
+     * @param flags         Flags used for binding to the service
+     *
+     * @see Context#getServiceDispatcher(ServiceConnection, Handler, int)
      * @hide
      */
-    public void bindRemoteViewsService(String packageName, int appWidgetId, Intent intent,
-            IBinder connection) {
+    public boolean bindRemoteViewsService(Context context, int appWidgetId, Intent intent,
+            IServiceConnection connection, @Context.BindServiceFlags int flags) {
         if (mService == null) {
-            return;
+            return false;
         }
         try {
-            mService.bindRemoteViewsService(packageName, appWidgetId, intent, connection);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-     * Unbinds the RemoteViewsService for a given appWidgetId and intent.
-     *
-     * The appWidgetId specified muse already be bound to the calling AppWidgetHost via
-     * {@link android.appwidget.AppWidgetManager#bindAppWidgetId AppWidgetManager.bindAppWidgetId()}.
-     *
-     * @param packageName   The package from which the binding is requested.
-     * @param appWidgetId   The AppWidget instance for which to bind the RemoteViewsService.
-     * @param intent        The intent of the service which will be providing the data to the
-     *                      RemoteViewsAdapter.
-     * @hide
-     */
-    public void unbindRemoteViewsService(String packageName, int appWidgetId, Intent intent) {
-        if (mService == null) {
-            return;
-        }
-        try {
-            mService.unbindRemoteViewsService(packageName, appWidgetId, intent);
+            return mService.bindRemoteViewsService(context.getOpPackageName(), appWidgetId, intent,
+                    context.getIApplicationThread(), context.getActivityToken(), connection, flags);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/android/arch/lifecycle/ActivityFullLifecycleTest.java b/android/arch/lifecycle/ActivityFullLifecycleTest.java
index ee4e661..78dd015 100644
--- a/android/arch/lifecycle/ActivityFullLifecycleTest.java
+++ b/android/arch/lifecycle/ActivityFullLifecycleTest.java
@@ -16,48 +16,43 @@
 
 package android.arch.lifecycle;
 
-import static android.arch.lifecycle.Lifecycle.Event.ON_CREATE;
-import static android.arch.lifecycle.Lifecycle.Event.ON_DESTROY;
-import static android.arch.lifecycle.Lifecycle.Event.ON_PAUSE;
-import static android.arch.lifecycle.Lifecycle.Event.ON_RESUME;
-import static android.arch.lifecycle.Lifecycle.Event.ON_START;
-import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
-import static android.arch.lifecycle.testapp.TestEvent.ACTIVITY_CALLBACK;
-import static android.arch.lifecycle.testapp.TestEvent.LIFECYCLE_EVENT;
+import static android.arch.lifecycle.TestUtils.OrderedTuples.CREATE;
+import static android.arch.lifecycle.TestUtils.OrderedTuples.DESTROY;
+import static android.arch.lifecycle.TestUtils.OrderedTuples.PAUSE;
+import static android.arch.lifecycle.TestUtils.OrderedTuples.RESUME;
+import static android.arch.lifecycle.TestUtils.OrderedTuples.START;
+import static android.arch.lifecycle.TestUtils.OrderedTuples.STOP;
+import static android.arch.lifecycle.TestUtils.flatMap;
 
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.is;
 
 import android.app.Activity;
 import android.arch.lifecycle.Lifecycle.Event;
-import android.arch.lifecycle.testapp.CollectingActivity;
+import android.arch.lifecycle.testapp.CollectingLifecycleOwner;
+import android.arch.lifecycle.testapp.CollectingSupportActivity;
 import android.arch.lifecycle.testapp.FrameworkLifecycleRegistryActivity;
-import android.arch.lifecycle.testapp.FullLifecycleTestActivity;
-import android.arch.lifecycle.testapp.SupportLifecycleRegistryActivity;
 import android.arch.lifecycle.testapp.TestEvent;
 import android.support.test.filters.SmallTest;
 import android.support.test.rule.ActivityTestRule;
-import android.util.Pair;
+import android.support.v4.util.Pair;
 
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 
-import java.util.ArrayList;
 import java.util.List;
 
 @SmallTest
 @RunWith(Parameterized.class)
 public class ActivityFullLifecycleTest {
     @Rule
-    public ActivityTestRule activityTestRule =
-            new ActivityTestRule<>(FullLifecycleTestActivity.class);
+    public final ActivityTestRule<? extends CollectingLifecycleOwner> activityTestRule;
 
     @Parameterized.Parameters
     public static Class[] params() {
-        return new Class[]{FullLifecycleTestActivity.class,
-                SupportLifecycleRegistryActivity.class,
+        return new Class[]{CollectingSupportActivity.class,
                 FrameworkLifecycleRegistryActivity.class};
     }
 
@@ -68,28 +63,13 @@
 
 
     @Test
-    public void testFullLifecycle() throws InterruptedException {
-        Activity activity = activityTestRule.getActivity();
-        List<Pair<TestEvent, Event>> results = ((CollectingActivity) activity)
-                .waitForCollectedEvents();
+    public void testFullLifecycle() throws Throwable {
+        CollectingLifecycleOwner owner = activityTestRule.getActivity();
+        TestUtils.waitTillResumed(owner, activityTestRule);
+        activityTestRule.finishActivity();
 
-        Event[] expectedEvents =
-                new Event[]{ON_CREATE, ON_START, ON_RESUME, ON_PAUSE, ON_STOP, ON_DESTROY};
-
-        List<Pair<TestEvent, Event>> expected = new ArrayList<>();
-        boolean beforeResume = true;
-        for (Event i : expectedEvents) {
-            if (beforeResume) {
-                expected.add(new Pair<>(ACTIVITY_CALLBACK, i));
-                expected.add(new Pair<>(LIFECYCLE_EVENT, i));
-            } else {
-                expected.add(new Pair<>(LIFECYCLE_EVENT, i));
-                expected.add(new Pair<>(ACTIVITY_CALLBACK, i));
-            }
-            if (i == ON_RESUME) {
-                beforeResume = false;
-            }
-        }
-        assertThat(results, is(expected));
+        TestUtils.waitTillDestroyed(owner, activityTestRule);
+        List<Pair<TestEvent, Event>> results = owner.copyCollectedEvents();
+        assertThat(results, is(flatMap(CREATE, START, RESUME, PAUSE, STOP, DESTROY)));
     }
 }
diff --git a/android/arch/lifecycle/AndroidViewModel.java b/android/arch/lifecycle/AndroidViewModel.java
index 2c7e173..106b2ef 100644
--- a/android/arch/lifecycle/AndroidViewModel.java
+++ b/android/arch/lifecycle/AndroidViewModel.java
@@ -16,7 +16,9 @@
 
 package android.arch.lifecycle;
 
+import android.annotation.SuppressLint;
 import android.app.Application;
+import android.support.annotation.NonNull;
 
 /**
  * Application context aware {@link ViewModel}.
@@ -25,16 +27,19 @@
  * <p>
  */
 public class AndroidViewModel extends ViewModel {
+    @SuppressLint("StaticFieldLeak")
     private Application mApplication;
 
-    public AndroidViewModel(Application application) {
+    public AndroidViewModel(@NonNull Application application) {
         mApplication = application;
     }
 
     /**
      * Return the application.
      */
+    @NonNull
     public <T extends Application> T getApplication() {
+        //noinspection unchecked
         return (T) mApplication;
     }
 }
diff --git a/android/arch/lifecycle/ClassesInfoCache.java b/android/arch/lifecycle/ClassesInfoCache.java
index f077dae..d88e276 100644
--- a/android/arch/lifecycle/ClassesInfoCache.java
+++ b/android/arch/lifecycle/ClassesInfoCache.java
@@ -46,7 +46,7 @@
             return mHasLifecycleMethods.get(klass);
         }
 
-        Method[] methods = klass.getDeclaredMethods();
+        Method[] methods = getDeclaredMethods(klass);
         for (Method method : methods) {
             OnLifecycleEvent annotation = method.getAnnotation(OnLifecycleEvent.class);
             if (annotation != null) {
@@ -64,6 +64,18 @@
         return false;
     }
 
+    private Method[] getDeclaredMethods(Class klass) {
+        try {
+            return klass.getDeclaredMethods();
+        } catch (NoClassDefFoundError e) {
+            throw new IllegalArgumentException("The observer class has some methods that use "
+                    + "newer APIs which are not available in the current OS version. Lifecycles "
+                    + "cannot access even other methods so you should make sure that your "
+                    + "observer classes only access framework classes that are available "
+                    + "in your min API level OR use lifecycle:compiler annotation processor.", e);
+        }
+    }
+
     CallbackInfo getInfo(Class klass) {
         CallbackInfo existing = mCallbackMap.get(klass);
         if (existing != null) {
@@ -106,7 +118,7 @@
             }
         }
 
-        Method[] methods = declaredMethods != null ? declaredMethods : klass.getDeclaredMethods();
+        Method[] methods = declaredMethods != null ? declaredMethods : getDeclaredMethods(klass);
         boolean hasLifecycleMethods = false;
         for (Method method : methods) {
             OnLifecycleEvent annotation = method.getAnnotation(OnLifecycleEvent.class);
diff --git a/android/arch/lifecycle/Lifecycle.java b/android/arch/lifecycle/Lifecycle.java
index 02db5ff..c0a2090 100644
--- a/android/arch/lifecycle/Lifecycle.java
+++ b/android/arch/lifecycle/Lifecycle.java
@@ -17,6 +17,7 @@
 package android.arch.lifecycle;
 
 import android.support.annotation.MainThread;
+import android.support.annotation.NonNull;
 
 /**
  * Defines an object that has an Android Lifecycle. {@link android.support.v4.app.Fragment Fragment}
@@ -83,7 +84,7 @@
      * @param observer The observer to notify.
      */
     @MainThread
-    public abstract void addObserver(LifecycleObserver observer);
+    public abstract void addObserver(@NonNull LifecycleObserver observer);
 
     /**
      * Removes the given observer from the observers list.
@@ -99,7 +100,7 @@
      * @param observer The observer to be removed.
      */
     @MainThread
-    public abstract void removeObserver(LifecycleObserver observer);
+    public abstract void removeObserver(@NonNull LifecycleObserver observer);
 
     /**
      * Returns the current state of the Lifecycle.
@@ -193,7 +194,7 @@
          * @param state State to compare with
          * @return true if this State is greater or equal to the given {@code state}
          */
-        public boolean isAtLeast(State state) {
+        public boolean isAtLeast(@NonNull State state) {
             return compareTo(state) >= 0;
         }
     }
diff --git a/android/arch/lifecycle/LifecycleOwner.java b/android/arch/lifecycle/LifecycleOwner.java
index 934cf3a..068bac1 100644
--- a/android/arch/lifecycle/LifecycleOwner.java
+++ b/android/arch/lifecycle/LifecycleOwner.java
@@ -16,6 +16,8 @@
 
 package android.arch.lifecycle;
 
+import android.support.annotation.NonNull;
+
 /**
  * A class that has an Android lifecycle. These events can be used by custom components to
  * handle lifecycle changes without implementing any code inside the Activity or the Fragment.
@@ -29,5 +31,6 @@
      *
      * @return The lifecycle of the provider.
      */
+    @NonNull
     Lifecycle getLifecycle();
 }
diff --git a/android/arch/lifecycle/LifecycleRegistry.java b/android/arch/lifecycle/LifecycleRegistry.java
index b83e6b8..bf8aff7 100644
--- a/android/arch/lifecycle/LifecycleRegistry.java
+++ b/android/arch/lifecycle/LifecycleRegistry.java
@@ -29,9 +29,12 @@
 import static android.arch.lifecycle.Lifecycle.State.STARTED;
 
 import android.arch.core.internal.FastSafeIterableMap;
+import android.support.annotation.MainThread;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
+import android.util.Log;
 
+import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.Map.Entry;
@@ -44,6 +47,8 @@
  */
 public class LifecycleRegistry extends Lifecycle {
 
+    private static final String LOG_TAG = "LifecycleRegistry";
+
     /**
      * Custom list that keeps observers and can handle removals / additions during traversal.
      *
@@ -59,8 +64,12 @@
     private State mState;
     /**
      * The provider that owns this Lifecycle.
+     * Only WeakReference on LifecycleOwner is kept, so if somebody leaks Lifecycle, they won't leak
+     * the whole Fragment / Activity. However, to leak Lifecycle object isn't great idea neither,
+     * because it keeps strong references on all other listeners, so you'll leak all of them as
+     * well.
      */
-    private final LifecycleOwner mLifecycleOwner;
+    private final WeakReference<LifecycleOwner> mLifecycleOwner;
 
     private int mAddingObserverCounter = 0;
 
@@ -86,19 +95,19 @@
      * @param provider The owner LifecycleOwner
      */
     public LifecycleRegistry(@NonNull LifecycleOwner provider) {
-        mLifecycleOwner = provider;
+        mLifecycleOwner = new WeakReference<>(provider);
         mState = INITIALIZED;
     }
 
     /**
-     * Only marks the current state as the given value. It doesn't dispatch any event to its
-     * listeners.
+     * Moves the Lifecycle to the given state and dispatches necessary events to the observers.
      *
      * @param state new state
      */
     @SuppressWarnings("WeakerAccess")
-    public void markState(State state) {
-        mState = state;
+    @MainThread
+    public void markState(@NonNull State state) {
+        moveToState(state);
     }
 
     /**
@@ -109,8 +118,16 @@
      *
      * @param event The event that was received
      */
-    public void handleLifecycleEvent(Lifecycle.Event event) {
-        mState = getStateAfter(event);
+    public void handleLifecycleEvent(@NonNull Lifecycle.Event event) {
+        State next = getStateAfter(event);
+        moveToState(next);
+    }
+
+    private void moveToState(State next) {
+        if (mState == next) {
+            return;
+        }
+        mState = next;
         if (mHandlingEvent || mAddingObserverCounter != 0) {
             mNewEventOccurred = true;
             // we will figure out what to do on upper level.
@@ -140,7 +157,7 @@
     }
 
     @Override
-    public void addObserver(LifecycleObserver observer) {
+    public void addObserver(@NonNull LifecycleObserver observer) {
         State initialState = mState == DESTROYED ? DESTROYED : INITIALIZED;
         ObserverWithState statefulObserver = new ObserverWithState(observer, initialState);
         ObserverWithState previous = mObserverMap.putIfAbsent(observer, statefulObserver);
@@ -148,15 +165,19 @@
         if (previous != null) {
             return;
         }
+        LifecycleOwner lifecycleOwner = mLifecycleOwner.get();
+        if (lifecycleOwner == null) {
+            // it is null we should be destroyed. Fallback quickly
+            return;
+        }
 
         boolean isReentrance = mAddingObserverCounter != 0 || mHandlingEvent;
-
         State targetState = calculateTargetState(observer);
         mAddingObserverCounter++;
         while ((statefulObserver.mState.compareTo(targetState) < 0
                 && mObserverMap.contains(observer))) {
             pushParentState(statefulObserver.mState);
-            statefulObserver.dispatchEvent(mLifecycleOwner, upEvent(statefulObserver.mState));
+            statefulObserver.dispatchEvent(lifecycleOwner, upEvent(statefulObserver.mState));
             popParentState();
             // mState / subling may have been changed recalculate
             targetState = calculateTargetState(observer);
@@ -178,7 +199,7 @@
     }
 
     @Override
-    public void removeObserver(LifecycleObserver observer) {
+    public void removeObserver(@NonNull LifecycleObserver observer) {
         // we consciously decided not to send destruction events here in opposition to addObserver.
         // Our reasons for that:
         // 1. These events haven't yet happened at all. In contrast to events in addObservers, that
@@ -258,7 +279,7 @@
         throw new IllegalArgumentException("Unexpected state value " + state);
     }
 
-    private void forwardPass() {
+    private void forwardPass(LifecycleOwner lifecycleOwner) {
         Iterator<Entry<LifecycleObserver, ObserverWithState>> ascendingIterator =
                 mObserverMap.iteratorWithAdditions();
         while (ascendingIterator.hasNext() && !mNewEventOccurred) {
@@ -267,13 +288,13 @@
             while ((observer.mState.compareTo(mState) < 0 && !mNewEventOccurred
                     && mObserverMap.contains(entry.getKey()))) {
                 pushParentState(observer.mState);
-                observer.dispatchEvent(mLifecycleOwner, upEvent(observer.mState));
+                observer.dispatchEvent(lifecycleOwner, upEvent(observer.mState));
                 popParentState();
             }
         }
     }
 
-    private void backwardPass() {
+    private void backwardPass(LifecycleOwner lifecycleOwner) {
         Iterator<Entry<LifecycleObserver, ObserverWithState>> descendingIterator =
                 mObserverMap.descendingIterator();
         while (descendingIterator.hasNext() && !mNewEventOccurred) {
@@ -283,7 +304,7 @@
                     && mObserverMap.contains(entry.getKey()))) {
                 Event event = downEvent(observer.mState);
                 pushParentState(getStateAfter(event));
-                observer.dispatchEvent(mLifecycleOwner, event);
+                observer.dispatchEvent(lifecycleOwner, event);
                 popParentState();
             }
         }
@@ -292,16 +313,22 @@
     // happens only on the top of stack (never in reentrance),
     // so it doesn't have to take in account parents
     private void sync() {
+        LifecycleOwner lifecycleOwner = mLifecycleOwner.get();
+        if (lifecycleOwner == null) {
+            Log.w(LOG_TAG, "LifecycleOwner is garbage collected, you shouldn't try dispatch "
+                    + "new events from it.");
+            return;
+        }
         while (!isSynced()) {
             mNewEventOccurred = false;
             // no need to check eldest for nullability, because isSynced does it for us.
             if (mState.compareTo(mObserverMap.eldest().getValue().mState) < 0) {
-                backwardPass();
+                backwardPass(lifecycleOwner);
             }
             Entry<LifecycleObserver, ObserverWithState> newest = mObserverMap.newest();
             if (!mNewEventOccurred && newest != null
                     && mState.compareTo(newest.getValue().mState) > 0) {
-                forwardPass();
+                forwardPass(lifecycleOwner);
             }
         }
         mNewEventOccurred = false;
diff --git a/android/arch/lifecycle/LifecycleRegistryOwner.java b/android/arch/lifecycle/LifecycleRegistryOwner.java
index 38eeb6d..0c67fef 100644
--- a/android/arch/lifecycle/LifecycleRegistryOwner.java
+++ b/android/arch/lifecycle/LifecycleRegistryOwner.java
@@ -16,6 +16,8 @@
 
 package android.arch.lifecycle;
 
+import android.support.annotation.NonNull;
+
 /**
  * @deprecated Use {@code android.support.v7.app.AppCompatActivity}
  * which extends {@link LifecycleOwner}, so there are no use cases for this class.
@@ -23,6 +25,7 @@
 @SuppressWarnings({"WeakerAccess", "unused"})
 @Deprecated
 public interface LifecycleRegistryOwner extends LifecycleOwner {
+    @NonNull
     @Override
     LifecycleRegistry getLifecycle();
 }
diff --git a/android/arch/lifecycle/LifecycleRegistryTest.java b/android/arch/lifecycle/LifecycleRegistryTest.java
index 6506454..2a7bbad 100644
--- a/android/arch/lifecycle/LifecycleRegistryTest.java
+++ b/android/arch/lifecycle/LifecycleRegistryTest.java
@@ -566,6 +566,25 @@
         verify(observer).onCreate();
     }
 
+    private static void forceGc() {
+        Runtime.getRuntime().gc();
+        Runtime.getRuntime().runFinalization();
+        Runtime.getRuntime().gc();
+        Runtime.getRuntime().runFinalization();
+    }
+
+    @Test
+    public void goneLifecycleOwner() {
+        fullyInitializeRegistry();
+        mLifecycleOwner = null;
+        forceGc();
+        TestObserver observer = mock(TestObserver.class);
+        mRegistry.addObserver(observer);
+        verify(observer, never()).onCreate();
+        verify(observer, never()).onStart();
+        verify(observer, never()).onResume();
+    }
+
     private void dispatchEvent(Lifecycle.Event event) {
         when(mLifecycle.getCurrentState()).thenReturn(LifecycleRegistry.getStateAfter(event));
         mRegistry.handleLifecycleEvent(event);
diff --git a/android/arch/lifecycle/LiveDataOnSaveInstanceStateTest.java b/android/arch/lifecycle/LiveDataOnSaveInstanceStateTest.java
new file mode 100644
index 0000000..836cfff
--- /dev/null
+++ b/android/arch/lifecycle/LiveDataOnSaveInstanceStateTest.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2017 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.arch.lifecycle;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import android.app.Instrumentation;
+import android.arch.lifecycle.testapp.CollectingSupportActivity;
+import android.arch.lifecycle.testapp.CollectingSupportFragment;
+import android.arch.lifecycle.testapp.NavigationDialogActivity;
+import android.content.Intent;
+import android.os.Build;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SdkSuppress;
+import android.support.test.filters.SmallTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.v4.app.FragmentActivity;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class LiveDataOnSaveInstanceStateTest {
+    @Rule
+    public ActivityTestRule<CollectingSupportActivity> mActivityTestRule =
+            new ActivityTestRule<>(CollectingSupportActivity.class);
+
+    @Test
+    @SdkSuppress(maxSdkVersion = Build.VERSION_CODES.M)
+    public void liveData_partiallyObscuredActivity_maxSdkM() throws Throwable {
+        CollectingSupportActivity activity = mActivityTestRule.getActivity();
+
+        liveData_partiallyObscuredLifecycleOwner_maxSdkM(activity);
+    }
+
+    @Test
+    @SdkSuppress(maxSdkVersion = Build.VERSION_CODES.M)
+    public void liveData_partiallyObscuredActivityWithFragment_maxSdkM() throws Throwable {
+        CollectingSupportActivity activity = mActivityTestRule.getActivity();
+        CollectingSupportFragment fragment = new CollectingSupportFragment();
+        mActivityTestRule.runOnUiThread(() -> activity.replaceFragment(fragment));
+
+        liveData_partiallyObscuredLifecycleOwner_maxSdkM(fragment);
+    }
+
+    @Test
+    @SdkSuppress(maxSdkVersion = Build.VERSION_CODES.M)
+    public void liveData_partiallyObscuredActivityFragmentInFragment_maxSdkM() throws Throwable {
+        CollectingSupportActivity activity = mActivityTestRule.getActivity();
+        CollectingSupportFragment fragment = new CollectingSupportFragment();
+        CollectingSupportFragment fragment2 = new CollectingSupportFragment();
+        mActivityTestRule.runOnUiThread(() -> {
+            activity.replaceFragment(fragment);
+            fragment.replaceFragment(fragment2);
+        });
+
+        liveData_partiallyObscuredLifecycleOwner_maxSdkM(fragment2);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.N)
+    public void liveData_partiallyObscuredActivity_minSdkN() throws Throwable {
+        CollectingSupportActivity activity = mActivityTestRule.getActivity();
+
+        liveData_partiallyObscuredLifecycleOwner_minSdkN(activity);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.N)
+    public void liveData_partiallyObscuredActivityWithFragment_minSdkN() throws Throwable {
+        CollectingSupportActivity activity = mActivityTestRule.getActivity();
+        CollectingSupportFragment fragment = new CollectingSupportFragment();
+        mActivityTestRule.runOnUiThread(() -> activity.replaceFragment(fragment));
+
+        liveData_partiallyObscuredLifecycleOwner_minSdkN(fragment);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.N)
+    public void liveData_partiallyObscuredActivityFragmentInFragment_minSdkN() throws Throwable {
+        CollectingSupportActivity activity = mActivityTestRule.getActivity();
+        CollectingSupportFragment fragment = new CollectingSupportFragment();
+        CollectingSupportFragment fragment2 = new CollectingSupportFragment();
+        mActivityTestRule.runOnUiThread(() -> {
+            activity.replaceFragment(fragment);
+            fragment.replaceFragment(fragment2);
+        });
+
+        liveData_partiallyObscuredLifecycleOwner_minSdkN(fragment2);
+    }
+
+    private void liveData_partiallyObscuredLifecycleOwner_maxSdkM(LifecycleOwner lifecycleOwner)
+            throws Throwable {
+        final AtomicInteger atomicInteger = new AtomicInteger(0);
+        MutableLiveData<Integer> mutableLiveData = new MutableLiveData<>();
+        mActivityTestRule.runOnUiThread(() -> mutableLiveData.setValue(0));
+
+        TestUtils.waitTillResumed(lifecycleOwner, mActivityTestRule);
+
+        mutableLiveData.observe(lifecycleOwner, atomicInteger::set);
+
+        final FragmentActivity dialogActivity = launchDialog();
+
+        TestUtils.waitTillCreated(lifecycleOwner, mActivityTestRule);
+
+        // Change the LiveData value and assert that the observer is not called given that the
+        // lifecycle is in the CREATED state.
+        mActivityTestRule.runOnUiThread(() -> mutableLiveData.setValue(1));
+        assertThat(atomicInteger.get(), is(0));
+
+        // Finish the dialog Activity, wait for the main activity to be resumed, and assert that
+        // the observer's onChanged method is called.
+        mActivityTestRule.runOnUiThread(dialogActivity::finish);
+        TestUtils.waitTillResumed(lifecycleOwner, mActivityTestRule);
+        assertThat(atomicInteger.get(), is(1));
+    }
+
+    private void liveData_partiallyObscuredLifecycleOwner_minSdkN(LifecycleOwner lifecycleOwner)
+            throws Throwable {
+        final AtomicInteger atomicInteger = new AtomicInteger(0);
+        MutableLiveData<Integer> mutableLiveData = new MutableLiveData<>();
+        mActivityTestRule.runOnUiThread(() -> mutableLiveData.setValue(0));
+
+        TestUtils.waitTillResumed(lifecycleOwner, mActivityTestRule);
+
+        mutableLiveData.observe(lifecycleOwner, atomicInteger::set);
+
+        // Launch the NavigationDialogActivity, partially obscuring the activity, and wait for the
+        // lifecycleOwner to hit onPause (or enter the STARTED state).  On API 24 and above, this
+        // onPause should be the last lifecycle method called (and the STARTED state should be the
+        // final resting state).
+        launchDialog();
+        TestUtils.waitTillStarted(lifecycleOwner, mActivityTestRule);
+
+        // Change the LiveData's value and verify that the observer's onChanged method is called
+        // since we are in the STARTED state.
+        mActivityTestRule.runOnUiThread(() -> mutableLiveData.setValue(1));
+        assertThat(atomicInteger.get(), is(1));
+    }
+
+    private FragmentActivity launchDialog() throws Throwable {
+        Instrumentation.ActivityMonitor monitor = new Instrumentation.ActivityMonitor(
+                NavigationDialogActivity.class.getCanonicalName(), null, false);
+        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        instrumentation.addMonitor(monitor);
+
+        FragmentActivity activity = mActivityTestRule.getActivity();
+        // helps with less flaky API 16 tests
+        Intent intent = new Intent(activity, NavigationDialogActivity.class);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
+        intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
+        activity.startActivity(intent);
+        FragmentActivity fragmentActivity = (FragmentActivity) monitor.waitForActivity();
+        TestUtils.waitTillResumed(fragmentActivity, mActivityTestRule);
+        return fragmentActivity;
+    }
+}
diff --git a/android/arch/lifecycle/LiveDataTest.java b/android/arch/lifecycle/LiveDataTest.java
index 9f0b425..647d5d7 100644
--- a/android/arch/lifecycle/LiveDataTest.java
+++ b/android/arch/lifecycle/LiveDataTest.java
@@ -53,18 +53,29 @@
 public class LiveDataTest {
     private PublicLiveData<String> mLiveData;
     private LifecycleOwner mOwner;
+    private LifecycleOwner mOwner2;
     private LifecycleRegistry mRegistry;
+    private LifecycleRegistry mRegistry2;
     private MethodExec mActiveObserversChanged;
     private boolean mInObserver;
 
     @Before
     public void init() {
         mLiveData = new PublicLiveData<>();
-        mOwner = mock(LifecycleOwner.class);
-        mRegistry = new LifecycleRegistry(mOwner);
-        when(mOwner.getLifecycle()).thenReturn(mRegistry);
+
         mActiveObserversChanged = mock(MethodExec.class);
         mLiveData.activeObserversChanged = mActiveObserversChanged;
+
+        mOwner = mock(LifecycleOwner.class);
+
+        mRegistry = new LifecycleRegistry(mOwner);
+        when(mOwner.getLifecycle()).thenReturn(mRegistry);
+
+        mOwner2 = mock(LifecycleOwner.class);
+
+        mRegistry2 = new LifecycleRegistry(mOwner2);
+        when(mOwner2.getLifecycle()).thenReturn(mRegistry2);
+
         mInObserver = false;
     }
 
@@ -159,14 +170,11 @@
     @Test
     public void testAddSameObserverIn2LifecycleOwners() {
         Observer<String> observer = (Observer<String>) mock(Observer.class);
-        LifecycleOwner owner2 = mock(LifecycleOwner.class);
-        LifecycleRegistry registry2 = new LifecycleRegistry(owner2);
-        when(owner2.getLifecycle()).thenReturn(registry2);
 
         mLiveData.observe(mOwner, observer);
         Throwable throwable = null;
         try {
-            mLiveData.observe(owner2, observer);
+            mLiveData.observe(mOwner2, observer);
         } catch (Throwable t) {
             throwable = t;
         }
@@ -456,6 +464,210 @@
         inOrder.verifyNoMoreInteractions();
     }
 
+    @Test
+    public void setValue_neverActive_observerOnChangedNotCalled() {
+        Observer<String> observer = (Observer<String>) mock(Observer.class);
+        mLiveData.observe(mOwner, observer);
+
+        mLiveData.setValue("1");
+
+        verify(observer, never()).onChanged(anyString());
+    }
+
+    @Test
+    public void setValue_twoObserversTwoStartedOwners_onChangedCalledOnBoth() {
+        Observer<String> observer1 = mock(Observer.class);
+        Observer<String> observer2 = mock(Observer.class);
+
+        mLiveData.observe(mOwner, observer1);
+        mLiveData.observe(mOwner2, observer2);
+
+        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+        mRegistry2.handleLifecycleEvent(Lifecycle.Event.ON_START);
+
+        mLiveData.setValue("1");
+
+        verify(observer1).onChanged("1");
+        verify(observer2).onChanged("1");
+    }
+
+    @Test
+    public void setValue_twoObserversOneStartedOwner_onChangedCalledOnOneCorrectObserver() {
+        Observer<String> observer1 = mock(Observer.class);
+        Observer<String> observer2 = mock(Observer.class);
+
+        mLiveData.observe(mOwner, observer1);
+        mLiveData.observe(mOwner2, observer2);
+
+        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+
+        mLiveData.setValue("1");
+
+        verify(observer1).onChanged("1");
+        verify(observer2, never()).onChanged(anyString());
+    }
+
+    @Test
+    public void setValue_twoObserversBothStartedAfterSetValue_onChangedCalledOnBoth() {
+        Observer<String> observer1 = mock(Observer.class);
+        Observer<String> observer2 = mock(Observer.class);
+
+        mLiveData.observe(mOwner, observer1);
+        mLiveData.observe(mOwner2, observer2);
+
+        mLiveData.setValue("1");
+
+        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+        mRegistry2.handleLifecycleEvent(Lifecycle.Event.ON_START);
+
+        verify(observer1).onChanged("1");
+        verify(observer1).onChanged("1");
+    }
+
+    @Test
+    public void setValue_twoObserversOneStartedAfterSetValue_onChangedCalledOnCorrectObserver() {
+        Observer<String> observer1 = mock(Observer.class);
+        Observer<String> observer2 = mock(Observer.class);
+
+        mLiveData.observe(mOwner, observer1);
+        mLiveData.observe(mOwner2, observer2);
+
+        mLiveData.setValue("1");
+
+        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+
+        verify(observer1).onChanged("1");
+        verify(observer2, never()).onChanged(anyString());
+    }
+
+    @Test
+    public void setValue_twoObserversOneStarted_liveDataBecomesActive() {
+        Observer<String> observer1 = mock(Observer.class);
+        Observer<String> observer2 = mock(Observer.class);
+
+        mLiveData.observe(mOwner, observer1);
+        mLiveData.observe(mOwner2, observer2);
+
+        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+
+        verify(mActiveObserversChanged).onCall(true);
+    }
+
+    @Test
+    public void setValue_twoObserversOneStopped_liveDataStaysActive() {
+        Observer<String> observer1 = mock(Observer.class);
+        Observer<String> observer2 = mock(Observer.class);
+
+        mLiveData.observe(mOwner, observer1);
+        mLiveData.observe(mOwner2, observer2);
+
+        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+        mRegistry2.handleLifecycleEvent(Lifecycle.Event.ON_START);
+
+        verify(mActiveObserversChanged).onCall(true);
+
+        reset(mActiveObserversChanged);
+        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP);
+
+        verify(mActiveObserversChanged, never()).onCall(anyBoolean());
+    }
+
+    /**
+     * Verifies that if a lifecycle's state changes without an event, and changes to something that
+     * LiveData would become inactive in response to, LiveData will detect the change upon new data
+     * being set and become inactive.  Also verifies that once the lifecycle enters into a state
+     * that LiveData should become active to, that it does indeed become active.
+     */
+    @Test
+    public void liveDataActiveStateIsManagedCorrectlyWithoutEvent_oneObserver() {
+        Observer<String> observer = (Observer<String>) mock(Observer.class);
+        mLiveData.observe(mOwner, observer);
+
+        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+
+        // Marking state as CREATED should call onInactive.
+        reset(mActiveObserversChanged);
+        mRegistry.markState(Lifecycle.State.CREATED);
+        verify(mActiveObserversChanged).onCall(false);
+        reset(mActiveObserversChanged);
+
+        // Setting a new value should trigger LiveData to realize the Lifecycle it is observing
+        // is in a state where the LiveData should be inactive, so the LiveData will call onInactive
+        // and the Observer shouldn't be affected.
+        mLiveData.setValue("1");
+
+        // state is already CREATED so should not call again
+        verify(mActiveObserversChanged, never()).onCall(anyBoolean());
+        verify(observer, never()).onChanged(anyString());
+
+        // Sanity check.  Because we've only marked the state as CREATED, sending ON_START
+        // should re-dispatch events.
+        reset(mActiveObserversChanged);
+        reset(observer);
+        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+        verify(mActiveObserversChanged).onCall(true);
+        verify(observer).onChanged("1");
+    }
+
+    /**
+     *  This test verifies that LiveData will detect changes in LifecycleState that would make it
+     *  inactive upon the setting of new data, but only if all of the Lifecycles it's observing
+     *  are all in those states.  It also makes sure that once it is inactive, that it will become
+     *  active again once one of the lifecycles it's observing moves to an appropriate state.
+     */
+    @Test
+    public void liveDataActiveStateIsManagedCorrectlyWithoutEvent_twoObservers() {
+        Observer<String> observer1 = mock(Observer.class);
+        Observer<String> observer2 = mock(Observer.class);
+
+        mLiveData.observe(mOwner, observer1);
+        mLiveData.observe(mOwner2, observer2);
+
+        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+        mRegistry2.handleLifecycleEvent(Lifecycle.Event.ON_START);
+
+        // Marking the state to created won't change LiveData to be inactive.
+        reset(mActiveObserversChanged);
+        mRegistry.markState(Lifecycle.State.CREATED);
+        verify(mActiveObserversChanged, never()).onCall(anyBoolean());
+
+        // After setting a value, the LiveData will stay active because there is still a STARTED
+        // lifecycle being observed.  The one Observer associated with the STARTED lifecycle will
+        // also have been called, but the other Observer will not have been called.
+        reset(observer1);
+        reset(observer2);
+        mLiveData.setValue("1");
+        verify(mActiveObserversChanged, never()).onCall(anyBoolean());
+        verify(observer1, never()).onChanged(anyString());
+        verify(observer2).onChanged("1");
+
+        // Now we set the other Lifecycle to be inactive, live data should become inactive.
+        reset(observer1);
+        reset(observer2);
+        mRegistry2.markState(Lifecycle.State.CREATED);
+        verify(mActiveObserversChanged).onCall(false);
+        verify(observer1, never()).onChanged(anyString());
+        verify(observer2, never()).onChanged(anyString());
+
+        // Now we post another value, because both lifecycles are in the Created state, live data
+        // will not dispatch any values
+        reset(mActiveObserversChanged);
+        mLiveData.setValue("2");
+        verify(mActiveObserversChanged, never()).onCall(anyBoolean());
+        verify(observer1, never()).onChanged(anyString());
+        verify(observer2, never()).onChanged(anyString());
+
+        // Now that the first Lifecycle has been moved back to the Resumed state, the LiveData will
+        // be made active and it's associated Observer will be called with the new value, but the
+        // Observer associated with the Lifecycle that is still in the Created state won't be
+        // called.
+        reset(mActiveObserversChanged);
+        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME);
+        verify(mActiveObserversChanged).onCall(true);
+        verify(observer1).onChanged("2");
+        verify(observer2, never()).onChanged(anyString());
+    }
+
     @SuppressWarnings("WeakerAccess")
     static class PublicLiveData<T> extends LiveData<T> {
         // cannot spy due to internal calls
diff --git a/android/arch/lifecycle/MediatorLiveData.java b/android/arch/lifecycle/MediatorLiveData.java
index 672b3a3..5864739 100644
--- a/android/arch/lifecycle/MediatorLiveData.java
+++ b/android/arch/lifecycle/MediatorLiveData.java
@@ -19,16 +19,49 @@
 import android.arch.core.internal.SafeIterableMap;
 import android.support.annotation.CallSuper;
 import android.support.annotation.MainThread;
+import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 
 import java.util.Map;
 
 /**
- * {@link LiveData} subclass which may observer other {@code LiveData} objects and react on
+ * {@link LiveData} subclass which may observe other {@code LiveData} objects and react on
  * {@code OnChanged} events from them.
  * <p>
  * This class correctly propagates its active/inactive states down to source {@code LiveData}
  * objects.
+ * <p>
+ * Consider the following scenario: we have 2 instances of {@code LiveData}, let's name them
+ * {@code liveData1} and {@code liveData2}, and we want to merge their emissions in one object:
+ * {@code liveDataMerger}. Then, {@code liveData1} and {@code liveData2} will become sources for
+ * the {@code MediatorLiveData liveDataMerger} and every time {@code onChanged} callback
+ * is called for either of them, we set a new value in {@code liveDataMerger}.
+ *
+ * <pre>
+ * LiveData<Integer> liveData1 = ...;
+ * LiveData<Integer> liveData2 = ...;
+ *
+ * MediatorLiveData<Integer> liveDataMerger = new MediatorLiveData<>();
+ * liveDataMerger.addSource(liveData1, value -> liveDataMerger.setValue(value));
+ * liveDataMerger.addSource(liveData2, value -> liveDataMerger.setValue(value));
+ * </pre>
+ * <p>
+ * Let's consider that we only want 10 values emitted by {@code liveData1}, to be
+ * merged in the {@code liveDataMerger}. Then, after 10 values, we can stop listening to {@code
+ * liveData1} and remove it as a source.
+ * <pre>
+ * liveDataMerger.addSource(liveData1, new Observer<Integer>() {
+ *      private int count = 1;
+ *
+ *      {@literal @}Override public void onChanged(@Nullable Integer s) {
+ *          count++;
+ *          liveDataMerger.setValue(s);
+ *          if (count > 10) {
+ *              liveDataMerger.removeSource(liveData1);
+ *          }
+ *      }
+ * });
+ * </pre>
  *
  * @param <T> The type of data hold by this instance
  */
@@ -49,7 +82,7 @@
      * @param <S>       The type of data hold by {@code source} LiveData
      */
     @MainThread
-    public <S> void addSource(LiveData<S> source, Observer<S> onChanged) {
+    public <S> void addSource(@NonNull LiveData<S> source, @NonNull Observer<S> onChanged) {
         Source<S> e = new Source<>(source, onChanged);
         Source<?> existing = mSources.putIfAbsent(source, e);
         if (existing != null && existing.mObserver != onChanged) {
@@ -71,7 +104,7 @@
      * @param <S>      the type of data hold by {@code source} LiveData
      */
     @MainThread
-    public <S> void removeSource(LiveData<S> toRemote) {
+    public <S> void removeSource(@NonNull LiveData<S> toRemote) {
         Source<?> source = mSources.remove(toRemote);
         if (source != null) {
             source.unplug();
diff --git a/android/arch/lifecycle/MissingClassTest.java b/android/arch/lifecycle/MissingClassTest.java
new file mode 100644
index 0000000..81a0756
--- /dev/null
+++ b/android/arch/lifecycle/MissingClassTest.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2017 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.arch.lifecycle;
+
+import android.app.PictureInPictureParams;
+import android.os.Build;
+import android.support.test.filters.SdkSuppress;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SdkSuppress(maxSdkVersion = Build.VERSION_CODES.N_MR1)
+@SmallTest
+public class MissingClassTest {
+    public static class ObserverWithMissingClasses {
+        @SuppressWarnings("unused")
+        public void newApiMethod(PictureInPictureParams params) {}
+
+        @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
+        public void onResume() {}
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testMissingApi() {
+        new ReflectiveGenericLifecycleObserver(new ObserverWithMissingClasses());
+    }
+}
diff --git a/android/arch/lifecycle/PartiallyCoveredActivityTest.java b/android/arch/lifecycle/PartiallyCoveredActivityTest.java
new file mode 100644
index 0000000..07a9dc5
--- /dev/null
+++ b/android/arch/lifecycle/PartiallyCoveredActivityTest.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2017 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.arch.lifecycle;
+
+import static android.arch.lifecycle.Lifecycle.Event.ON_RESUME;
+import static android.arch.lifecycle.Lifecycle.Event.ON_START;
+import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
+import static android.arch.lifecycle.TestUtils.OrderedTuples.CREATE;
+import static android.arch.lifecycle.TestUtils.OrderedTuples.DESTROY;
+import static android.arch.lifecycle.TestUtils.OrderedTuples.PAUSE;
+import static android.arch.lifecycle.TestUtils.OrderedTuples.RESUME;
+import static android.arch.lifecycle.TestUtils.OrderedTuples.START;
+import static android.arch.lifecycle.TestUtils.OrderedTuples.STOP;
+import static android.arch.lifecycle.TestUtils.flatMap;
+import static android.arch.lifecycle.testapp.TestEvent.LIFECYCLE_EVENT;
+import static android.arch.lifecycle.testapp.TestEvent.OWNER_CALLBACK;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import static java.util.Arrays.asList;
+import static java.util.Collections.singletonList;
+
+import android.app.Instrumentation;
+import android.arch.lifecycle.testapp.CollectingLifecycleOwner;
+import android.arch.lifecycle.testapp.CollectingSupportActivity;
+import android.arch.lifecycle.testapp.CollectingSupportFragment;
+import android.arch.lifecycle.testapp.NavigationDialogActivity;
+import android.arch.lifecycle.testapp.TestEvent;
+import android.content.Intent;
+import android.os.Build;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.util.Pair;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+
+/**
+ * Runs tests about the state when an activity is partially covered by another activity. Pre
+ * API 24, framework behavior changes so the test rely on whether state is saved or not and makes
+ * assertions accordingly.
+ */
+@SuppressWarnings("unchecked")
+@RunWith(Parameterized.class)
+@LargeTest
+public class PartiallyCoveredActivityTest {
+    private static final List[] IF_SAVED = new List[]{
+            // when overlaid
+            flatMap(CREATE, START, RESUME, PAUSE,
+                    singletonList(new Pair<>(LIFECYCLE_EVENT, ON_STOP))),
+            // post dialog dismiss
+            asList(new Pair<>(OWNER_CALLBACK, ON_RESUME),
+                    new Pair<>(LIFECYCLE_EVENT, ON_START),
+                    new Pair<>(LIFECYCLE_EVENT, ON_RESUME)),
+            // post finish
+            flatMap(PAUSE, STOP, DESTROY)};
+
+    private static final List[] IF_NOT_SAVED = new List[]{
+            // when overlaid
+            flatMap(CREATE, START, RESUME, PAUSE),
+            // post dialog dismiss
+            flatMap(RESUME),
+            // post finish
+            flatMap(PAUSE, STOP, DESTROY)};
+
+    private static final boolean sShouldSave = Build.VERSION.SDK_INT < Build.VERSION_CODES.N;
+    private static final List<Pair<TestEvent, Lifecycle.Event>>[] EXPECTED =
+            sShouldSave ? IF_SAVED : IF_NOT_SAVED;
+
+    @Rule
+    public ActivityTestRule<CollectingSupportActivity> activityRule =
+            new ActivityTestRule<CollectingSupportActivity>(
+                    CollectingSupportActivity.class) {
+                @Override
+                protected Intent getActivityIntent() {
+                    // helps with less flaky API 16 tests
+                    Intent intent = new Intent(InstrumentationRegistry.getTargetContext(),
+                            CollectingSupportActivity.class);
+                    intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
+                    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                    intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
+                    return intent;
+                }
+            };
+    private final boolean mDismissDialog;
+
+    @Parameterized.Parameters(name = "dismissDialog_{0}")
+    public static List<Boolean> dismissDialog() {
+        return asList(true, false);
+    }
+
+    public PartiallyCoveredActivityTest(boolean dismissDialog) {
+        mDismissDialog = dismissDialog;
+    }
+
+    @Test
+    public void coveredWithDialog_activity() throws Throwable {
+        final CollectingSupportActivity activity = activityRule.getActivity();
+        runTest(activity);
+    }
+
+    @Test
+    public void coveredWithDialog_fragment() throws Throwable {
+        CollectingSupportFragment fragment = new CollectingSupportFragment();
+        activityRule.runOnUiThread(() -> activityRule.getActivity().replaceFragment(fragment));
+        runTest(fragment);
+    }
+
+    @Test
+    public void coveredWithDialog_childFragment() throws Throwable {
+        CollectingSupportFragment parentFragment = new CollectingSupportFragment();
+        CollectingSupportFragment childFragment = new CollectingSupportFragment();
+        activityRule.runOnUiThread(() -> {
+            activityRule.getActivity().replaceFragment(parentFragment);
+            parentFragment.replaceFragment(childFragment);
+        });
+        runTest(childFragment);
+    }
+
+    private void runTest(CollectingLifecycleOwner owner) throws Throwable {
+        TestUtils.waitTillResumed(owner, activityRule);
+        FragmentActivity dialog = launchDialog();
+        assertStateSaving();
+        waitForIdle();
+        assertThat(owner.copyCollectedEvents(), is(EXPECTED[0]));
+        List<Pair<TestEvent, Lifecycle.Event>> expected;
+        if (mDismissDialog) {
+            dialog.finish();
+            TestUtils.waitTillResumed(activityRule.getActivity(), activityRule);
+            assertThat(owner.copyCollectedEvents(), is(flatMap(EXPECTED[0], EXPECTED[1])));
+            expected = flatMap(EXPECTED[0], EXPECTED[1], EXPECTED[2]);
+        } else {
+            expected = flatMap(CREATE, START, RESUME, PAUSE, STOP, DESTROY);
+        }
+        CollectingSupportActivity activity = activityRule.getActivity();
+        activityRule.finishActivity();
+        TestUtils.waitTillDestroyed(activity, activityRule);
+        assertThat(owner.copyCollectedEvents(), is(expected));
+    }
+
+    // test sanity
+    private void assertStateSaving() throws ExecutionException, InterruptedException {
+        final CollectingSupportActivity activity = activityRule.getActivity();
+        if (sShouldSave) {
+            // state should be saved. wait for it to be saved
+            assertThat("test sanity",
+                    activity.waitForStateSave(20), is(true));
+            assertThat("test sanity", activity.getSupportFragmentManager()
+                    .isStateSaved(), is(true));
+        } else {
+            // should should not be saved
+            assertThat("test sanity", activity.getSupportFragmentManager()
+                    .isStateSaved(), is(false));
+        }
+    }
+
+    private void waitForIdle() {
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+    }
+
+    private FragmentActivity launchDialog() throws Throwable {
+        Instrumentation.ActivityMonitor monitor = new Instrumentation.ActivityMonitor(
+                NavigationDialogActivity.class.getCanonicalName(), null, false);
+        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        instrumentation.addMonitor(monitor);
+
+        FragmentActivity activity = activityRule.getActivity();
+
+        Intent intent = new Intent(activity, NavigationDialogActivity.class);
+        // disabling animations helps with less flaky API 16 tests
+        intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
+        intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
+        activity.startActivity(intent);
+        FragmentActivity fragmentActivity = (FragmentActivity) monitor.waitForActivity();
+        TestUtils.waitTillResumed(fragmentActivity, activityRule);
+        return fragmentActivity;
+    }
+}
diff --git a/android/arch/lifecycle/ProcessLifecycleOwner.java b/android/arch/lifecycle/ProcessLifecycleOwner.java
index e2a1256..74ea97f 100644
--- a/android/arch/lifecycle/ProcessLifecycleOwner.java
+++ b/android/arch/lifecycle/ProcessLifecycleOwner.java
@@ -22,6 +22,7 @@
 import android.content.Context;
 import android.os.Bundle;
 import android.os.Handler;
+import android.support.annotation.NonNull;
 import android.support.annotation.VisibleForTesting;
 
 /**
@@ -156,7 +157,7 @@
         app.registerActivityLifecycleCallbacks(new EmptyActivityLifecycleCallbacks() {
             @Override
             public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
-                ReportFragment  .get(activity).setProcessListener(mInitializationListener);
+                ReportFragment.get(activity).setProcessListener(mInitializationListener);
             }
 
             @Override
@@ -171,6 +172,7 @@
         });
     }
 
+    @NonNull
     @Override
     public Lifecycle getLifecycle() {
         return mRegistry;
diff --git a/android/arch/lifecycle/LifecycleRuntimeTrojanProvider.java b/android/arch/lifecycle/ProcessLifecycleOwnerInitializer.java
similarity index 95%
rename from android/arch/lifecycle/LifecycleRuntimeTrojanProvider.java
rename to android/arch/lifecycle/ProcessLifecycleOwnerInitializer.java
index ac278c0..6cf80d2 100644
--- a/android/arch/lifecycle/LifecycleRuntimeTrojanProvider.java
+++ b/android/arch/lifecycle/ProcessLifecycleOwnerInitializer.java
@@ -29,7 +29,7 @@
  * @hide
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public class LifecycleRuntimeTrojanProvider extends ContentProvider {
+public class ProcessLifecycleOwnerInitializer extends ContentProvider {
     @Override
     public boolean onCreate() {
         LifecycleDispatcher.init(getContext());
diff --git a/android/arch/lifecycle/ProcessOwnerTest.java b/android/arch/lifecycle/ProcessOwnerTest.java
index 37bdcdb..77baf94 100644
--- a/android/arch/lifecycle/ProcessOwnerTest.java
+++ b/android/arch/lifecycle/ProcessOwnerTest.java
@@ -31,6 +31,7 @@
 import android.arch.lifecycle.testapp.NavigationDialogActivity;
 import android.arch.lifecycle.testapp.NavigationTestActivityFirst;
 import android.arch.lifecycle.testapp.NavigationTestActivitySecond;
+import android.arch.lifecycle.testapp.NonSupportActivity;
 import android.content.Context;
 import android.content.Intent;
 import android.support.test.InstrumentationRegistry;
@@ -95,6 +96,22 @@
     }
 
     @Test
+    public void testNavigationToNonSupport() throws Throwable {
+        FragmentActivity firstActivity = setupObserverOnResume();
+        Instrumentation.ActivityMonitor monitor = new Instrumentation.ActivityMonitor(
+                NonSupportActivity.class.getCanonicalName(), null, false);
+        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        instrumentation.addMonitor(monitor);
+
+        Intent intent = new Intent(firstActivity, NonSupportActivity.class);
+        firstActivity.finish();
+        firstActivity.startActivity(intent);
+        NonSupportActivity secondActivity = (NonSupportActivity) monitor.waitForActivity();
+        assertThat("Failed to navigate", secondActivity, notNullValue());
+        checkProcessObserverSilent(secondActivity);
+    }
+
+    @Test
     public void testRecreation() throws Throwable {
         FragmentActivity activity = setupObserverOnResume();
         FragmentActivity recreated = TestUtils.recreateActivity(activity, activityTestRule);
@@ -164,4 +181,11 @@
         activityTestRule.runOnUiThread(() ->
                 ProcessLifecycleOwner.get().getLifecycle().removeObserver(mObserver));
     }
+
+    private void checkProcessObserverSilent(NonSupportActivity activity) throws Throwable {
+        assertThat(activity.awaitResumedState(), is(true));
+        assertThat(mObserver.mChangedState, is(false));
+        activityTestRule.runOnUiThread(() ->
+                ProcessLifecycleOwner.get().getLifecycle().removeObserver(mObserver));
+    }
 }
diff --git a/android/arch/lifecycle/ReportFragment.java b/android/arch/lifecycle/ReportFragment.java
index 3e4ece8..16a89ce 100644
--- a/android/arch/lifecycle/ReportFragment.java
+++ b/android/arch/lifecycle/ReportFragment.java
@@ -28,7 +28,6 @@
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
 public class ReportFragment extends Fragment {
-
     private static final String REPORT_FRAGMENT_TAG = "android.arch.lifecycle"
             + ".LifecycleDispatcher.report_fragment_tag";
 
diff --git a/android/arch/lifecycle/TestUtils.java b/android/arch/lifecycle/TestUtils.java
index f0214bf..f7f9bbe 100644
--- a/android/arch/lifecycle/TestUtils.java
+++ b/android/arch/lifecycle/TestUtils.java
@@ -16,16 +16,35 @@
 
 package android.arch.lifecycle;
 
+import static android.arch.lifecycle.Lifecycle.Event.ON_CREATE;
+import static android.arch.lifecycle.Lifecycle.Event.ON_DESTROY;
+import static android.arch.lifecycle.Lifecycle.Event.ON_PAUSE;
+import static android.arch.lifecycle.Lifecycle.Event.ON_RESUME;
+import static android.arch.lifecycle.Lifecycle.Event.ON_START;
+import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
+import static android.arch.lifecycle.Lifecycle.State.CREATED;
+import static android.arch.lifecycle.Lifecycle.State.DESTROYED;
 import static android.arch.lifecycle.Lifecycle.State.RESUMED;
+import static android.arch.lifecycle.Lifecycle.State.STARTED;
+import static android.arch.lifecycle.testapp.TestEvent.LIFECYCLE_EVENT;
+import static android.arch.lifecycle.testapp.TestEvent.OWNER_CALLBACK;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
 
 import android.app.Activity;
 import android.app.Instrumentation;
 import android.app.Instrumentation.ActivityMonitor;
+import android.arch.lifecycle.testapp.TestEvent;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.rule.ActivityTestRule;
-import android.support.v4.app.FragmentActivity;
+import android.support.v4.util.Pair;
 
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 
 class TestUtils {
 
@@ -61,23 +80,88 @@
         return result;
     }
 
-    static void waitTillResumed(final FragmentActivity a, ActivityTestRule<?> activityRule)
+    static void waitTillCreated(final LifecycleOwner owner, ActivityTestRule<?> activityRule)
+            throws Throwable {
+        waitTillState(owner, activityRule, CREATED);
+    }
+
+    static void waitTillStarted(final LifecycleOwner owner, ActivityTestRule<?> activityRule)
+            throws Throwable {
+        waitTillState(owner, activityRule, STARTED);
+    }
+
+    static void waitTillResumed(final LifecycleOwner owner, ActivityTestRule<?> activityRule)
+            throws Throwable {
+        waitTillState(owner, activityRule, RESUMED);
+    }
+
+    static void waitTillDestroyed(final LifecycleOwner owner, ActivityTestRule<?> activityRule)
+            throws Throwable {
+        waitTillState(owner, activityRule, DESTROYED);
+    }
+
+    static void waitTillState(final LifecycleOwner owner, ActivityTestRule<?> activityRule,
+            Lifecycle.State state)
             throws Throwable {
         final CountDownLatch latch = new CountDownLatch(1);
         activityRule.runOnUiThread(() -> {
-            Lifecycle.State currentState = a.getLifecycle().getCurrentState();
-            if (currentState == RESUMED) {
+            Lifecycle.State currentState = owner.getLifecycle().getCurrentState();
+            if (currentState == state) {
                 latch.countDown();
+            } else {
+                owner.getLifecycle().addObserver(new LifecycleObserver() {
+                    @OnLifecycleEvent(Lifecycle.Event.ON_ANY)
+                    public void onStateChanged(LifecycleOwner provider) {
+                        if (provider.getLifecycle().getCurrentState() == state) {
+                            latch.countDown();
+                            provider.getLifecycle().removeObserver(this);
+                        }
+                    }
+                });
             }
-            a.getLifecycle().addObserver(new LifecycleObserver() {
-                @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
-                public void onStateChanged(LifecycleOwner provider) {
-                    latch.countDown();
-                    provider.getLifecycle().removeObserver(this);
-                }
-            });
         });
-        latch.await();
+        boolean latchResult = latch.await(1, TimeUnit.MINUTES);
+        assertThat("expected " + state + " never happened. Current state:"
+                        + owner.getLifecycle().getCurrentState(), latchResult, is(true));
+
+        // wait for another loop to ensure all observers are called
+        activityRule.runOnUiThread(() -> {
+            // do nothing
+        });
     }
 
+    @SafeVarargs
+    static <T> List<T> flatMap(List<T>... items) {
+        ArrayList<T> result = new ArrayList<>();
+        for (List<T> item : items) {
+            result.addAll(item);
+        }
+        return result;
+    }
+
+    /**
+     * Event tuples of {@link TestEvent} and {@link Lifecycle.Event}
+     * in the order they should arrive.
+     */
+    @SuppressWarnings("unchecked")
+    static class OrderedTuples {
+        static final List<Pair<TestEvent, Lifecycle.Event>> CREATE =
+                Arrays.asList(new Pair(OWNER_CALLBACK, ON_CREATE),
+                        new Pair(LIFECYCLE_EVENT, ON_CREATE));
+        static final List<Pair<TestEvent, Lifecycle.Event>> START =
+                Arrays.asList(new Pair(OWNER_CALLBACK, ON_START),
+                        new Pair(LIFECYCLE_EVENT, ON_START));
+        static final List<Pair<TestEvent, Lifecycle.Event>> RESUME =
+                Arrays.asList(new Pair(OWNER_CALLBACK, ON_RESUME),
+                        new Pair(LIFECYCLE_EVENT, ON_RESUME));
+        static final List<Pair<TestEvent, Lifecycle.Event>> PAUSE =
+                Arrays.asList(new Pair(LIFECYCLE_EVENT, ON_PAUSE),
+                        new Pair(OWNER_CALLBACK, ON_PAUSE));
+        static final List<Pair<TestEvent, Lifecycle.Event>> STOP =
+                Arrays.asList(new Pair(LIFECYCLE_EVENT, ON_STOP),
+                        new Pair(OWNER_CALLBACK, ON_STOP));
+        static final List<Pair<TestEvent, Lifecycle.Event>> DESTROY =
+                Arrays.asList(new Pair(LIFECYCLE_EVENT, ON_DESTROY),
+                        new Pair(OWNER_CALLBACK, ON_DESTROY));
+    }
 }
diff --git a/android/arch/lifecycle/Transformations.java b/android/arch/lifecycle/Transformations.java
index 9ce9cbb..c735f8b 100644
--- a/android/arch/lifecycle/Transformations.java
+++ b/android/arch/lifecycle/Transformations.java
@@ -18,6 +18,7 @@
 
 import android.arch.core.util.Function;
 import android.support.annotation.MainThread;
+import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 
 /**
@@ -60,7 +61,8 @@
      * @return a LiveData which emits resulting values
      */
     @MainThread
-    public static <X, Y> LiveData<Y> map(LiveData<X> source, final Function<X, Y> func) {
+    public static <X, Y> LiveData<Y> map(@NonNull LiveData<X> source,
+            @NonNull final Function<X, Y> func) {
         final MediatorLiveData<Y> result = new MediatorLiveData<>();
         result.addSource(source, new Observer<X>() {
             @Override
@@ -120,8 +122,8 @@
      * @param <Y>     a type of resulting LiveData
      */
     @MainThread
-    public static <X, Y> LiveData<Y> switchMap(LiveData<X> trigger,
-            final Function<X, LiveData<Y>> func) {
+    public static <X, Y> LiveData<Y> switchMap(@NonNull LiveData<X> trigger,
+            @NonNull final Function<X, LiveData<Y>> func) {
         final MediatorLiveData<Y> result = new MediatorLiveData<>();
         result.addSource(trigger, new Observer<X>() {
             LiveData<Y> mSource;
diff --git a/android/arch/lifecycle/ViewModelProvider.java b/android/arch/lifecycle/ViewModelProvider.java
index 7ef591f..29cbab8 100644
--- a/android/arch/lifecycle/ViewModelProvider.java
+++ b/android/arch/lifecycle/ViewModelProvider.java
@@ -43,7 +43,8 @@
          * @param <T>        The type parameter for the ViewModel.
          * @return a newly created ViewModel
          */
-        <T extends ViewModel> T create(Class<T> modelClass);
+        @NonNull
+        <T extends ViewModel> T create(@NonNull Class<T> modelClass);
     }
 
     private final Factory mFactory;
@@ -70,7 +71,7 @@
      * @param factory factory a {@code Factory} which will be used to instantiate
      *                new {@code ViewModels}
      */
-    public ViewModelProvider(ViewModelStore store, Factory factory) {
+    public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
         mFactory = factory;
         this.mViewModelStore = store;
     }
@@ -88,7 +89,8 @@
      * @param <T>        The type parameter for the ViewModel.
      * @return A ViewModel that is an instance of the given type {@code T}.
      */
-    public <T extends ViewModel> T get(Class<T> modelClass) {
+    @NonNull
+    public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
         String canonicalName = modelClass.getCanonicalName();
         if (canonicalName == null) {
             throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels");
@@ -136,8 +138,9 @@
      */
     public static class NewInstanceFactory implements Factory {
 
+        @NonNull
         @Override
-        public <T extends ViewModel> T create(Class<T> modelClass) {
+        public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
             //noinspection TryWithIdenticalCatches
             try {
                 return modelClass.newInstance();
diff --git a/android/arch/lifecycle/ViewModelProviders.java b/android/arch/lifecycle/ViewModelProviders.java
index 746162a..b4b20aa 100644
--- a/android/arch/lifecycle/ViewModelProviders.java
+++ b/android/arch/lifecycle/ViewModelProviders.java
@@ -139,8 +139,9 @@
             mApplication = application;
         }
 
+        @NonNull
         @Override
-        public <T extends ViewModel> T create(Class<T> modelClass) {
+        public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
             if (AndroidViewModel.class.isAssignableFrom(modelClass)) {
                 //noinspection TryWithIdenticalCatches
                 try {
diff --git a/android/arch/lifecycle/ViewModelStoreOwner.java b/android/arch/lifecycle/ViewModelStoreOwner.java
index 5058305..e26fa32 100644
--- a/android/arch/lifecycle/ViewModelStoreOwner.java
+++ b/android/arch/lifecycle/ViewModelStoreOwner.java
@@ -16,6 +16,8 @@
 
 package android.arch.lifecycle;
 
+import android.support.annotation.NonNull;
+
 /**
  * A scope that owns {@link ViewModelStore}.
  * <p>
@@ -30,5 +32,6 @@
      *
      * @return a {@code ViewModelStore}
      */
+    @NonNull
     ViewModelStore getViewModelStore();
 }
diff --git a/android/arch/lifecycle/ViewModelStores.java b/android/arch/lifecycle/ViewModelStores.java
index 8c17dd9..d7d769d 100644
--- a/android/arch/lifecycle/ViewModelStores.java
+++ b/android/arch/lifecycle/ViewModelStores.java
@@ -19,6 +19,7 @@
 import static android.arch.lifecycle.HolderFragment.holderFragmentFor;
 
 import android.support.annotation.MainThread;
+import android.support.annotation.NonNull;
 import android.support.v4.app.Fragment;
 import android.support.v4.app.FragmentActivity;
 
@@ -38,7 +39,7 @@
      * @return a {@code ViewModelStore}
      */
     @MainThread
-    public static ViewModelStore of(FragmentActivity activity) {
+    public static ViewModelStore of(@NonNull FragmentActivity activity) {
         return holderFragmentFor(activity).getViewModelStore();
     }
 
@@ -49,7 +50,7 @@
      * @return a {@code ViewModelStore}
      */
     @MainThread
-    public static ViewModelStore of(Fragment fragment) {
+    public static ViewModelStore of(@NonNull Fragment fragment) {
         return holderFragmentFor(fragment).getViewModelStore();
     }
 }
diff --git a/android/arch/lifecycle/testapp/CollectingActivity.java b/android/arch/lifecycle/testapp/CollectingLifecycleOwner.java
similarity index 76%
rename from android/arch/lifecycle/testapp/CollectingActivity.java
rename to android/arch/lifecycle/testapp/CollectingLifecycleOwner.java
index 6e243b6..4213cab 100644
--- a/android/arch/lifecycle/testapp/CollectingActivity.java
+++ b/android/arch/lifecycle/testapp/CollectingLifecycleOwner.java
@@ -17,21 +17,20 @@
 package android.arch.lifecycle.testapp;
 
 import android.arch.lifecycle.Lifecycle;
-import android.util.Pair;
+import android.arch.lifecycle.LifecycleOwner;
+import android.support.v4.util.Pair;
 
 import java.util.List;
 
 /**
  * For activities that collect their events.
  */
-public interface CollectingActivity {
-    long TIMEOUT = 5;
-
+public interface CollectingLifecycleOwner extends LifecycleOwner {
     /**
-     * Return collected events
+     * Return a copy of currently collected events
      *
      * @return The list of collected events.
      * @throws InterruptedException
      */
-    List<Pair<TestEvent, Lifecycle.Event>> waitForCollectedEvents() throws InterruptedException;
+    List<Pair<TestEvent, Lifecycle.Event>> copyCollectedEvents();
 }
diff --git a/android/arch/lifecycle/testapp/CollectingSupportActivity.java b/android/arch/lifecycle/testapp/CollectingSupportActivity.java
new file mode 100644
index 0000000..f38d422
--- /dev/null
+++ b/android/arch/lifecycle/testapp/CollectingSupportActivity.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2017 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.arch.lifecycle.testapp;
+
+import static android.arch.lifecycle.testapp.TestEvent.OWNER_CALLBACK;
+
+import android.arch.lifecycle.Lifecycle.Event;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.util.Pair;
+import android.widget.FrameLayout;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * LifecycleRegistryOwner that extends FragmentActivity.
+ */
+public class CollectingSupportActivity extends FragmentActivity implements
+        CollectingLifecycleOwner {
+
+    private final List<Pair<TestEvent, Event>> mCollectedEvents = new ArrayList<>();
+    private TestObserver mTestObserver = new TestObserver(mCollectedEvents);
+    private CountDownLatch mSavedStateLatch = new CountDownLatch(1);
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        FrameLayout layout = new FrameLayout(this);
+        layout.setId(R.id.fragment_container);
+        setContentView(layout);
+        mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Event.ON_CREATE));
+        getLifecycle().addObserver(mTestObserver);
+    }
+
+    /**
+     * replaces the main content fragment w/ the given fragment.
+     */
+    public void replaceFragment(Fragment fragment) {
+        getSupportFragmentManager()
+                .beginTransaction()
+                .add(R.id.fragment_container, fragment)
+                .commitNow();
+    }
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+        mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Event.ON_START));
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Event.ON_RESUME));
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Event.ON_DESTROY));
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Event.ON_STOP));
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Event.ON_PAUSE));
+        // helps with less flaky API 16 tests.
+        overridePendingTransition(0, 0);
+    }
+
+    @Override
+    public List<Pair<TestEvent, Event>> copyCollectedEvents() {
+        return new ArrayList<>(mCollectedEvents);
+    }
+
+    @Override
+    protected void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        mSavedStateLatch.countDown();
+    }
+
+    /**
+     * Waits for onSaveInstanceState to be called.
+     */
+    public boolean waitForStateSave(@SuppressWarnings("SameParameterValue") int seconds)
+            throws InterruptedException {
+        return mSavedStateLatch.await(seconds, TimeUnit.SECONDS);
+    }
+}
diff --git a/android/arch/lifecycle/testapp/CollectingSupportFragment.java b/android/arch/lifecycle/testapp/CollectingSupportFragment.java
new file mode 100644
index 0000000..9bbbe16
--- /dev/null
+++ b/android/arch/lifecycle/testapp/CollectingSupportFragment.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2017 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.arch.lifecycle.testapp;
+
+import static android.arch.lifecycle.testapp.TestEvent.OWNER_CALLBACK;
+
+import android.annotation.SuppressLint;
+import android.arch.lifecycle.Lifecycle;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.v4.app.Fragment;
+import android.support.v4.util.Pair;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A support fragment that collects all of its events.
+ */
+@SuppressLint("ValidFragment")
+public class CollectingSupportFragment extends Fragment implements CollectingLifecycleOwner {
+    private final List<Pair<TestEvent, Lifecycle.Event>> mCollectedEvents =
+            new ArrayList<>();
+    private TestObserver mTestObserver = new TestObserver(mCollectedEvents);
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_CREATE));
+        getLifecycle().addObserver(mTestObserver);
+    }
+
+    @Nullable
+    @Override
+    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
+            @Nullable Bundle savedInstanceState) {
+        //noinspection ConstantConditions
+        FrameLayout layout = new FrameLayout(container.getContext());
+        layout.setId(R.id.child_fragment_container);
+        return layout;
+    }
+
+    /**
+     * Runs a replace fragment transaction with 'fragment' on this Fragment.
+     */
+    public void replaceFragment(Fragment fragment) {
+        getChildFragmentManager()
+                .beginTransaction()
+                .add(R.id.child_fragment_container, fragment)
+                .commitNow();
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_START));
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_RESUME));
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_DESTROY));
+    }
+
+    @Override
+    public void onStop() {
+        super.onStop();
+        mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_STOP));
+    }
+
+    @Override
+    public void onPause() {
+        super.onPause();
+        mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_PAUSE));
+    }
+
+    @Override
+    public List<Pair<TestEvent, Lifecycle.Event>> copyCollectedEvents() {
+        return new ArrayList<>(mCollectedEvents);
+    }
+}
diff --git a/android/arch/lifecycle/testapp/FrameworkLifecycleRegistryActivity.java b/android/arch/lifecycle/testapp/FrameworkLifecycleRegistryActivity.java
index d8f4fb3..cdf577c 100644
--- a/android/arch/lifecycle/testapp/FrameworkLifecycleRegistryActivity.java
+++ b/android/arch/lifecycle/testapp/FrameworkLifecycleRegistryActivity.java
@@ -16,27 +16,29 @@
 
 package android.arch.lifecycle.testapp;
 
-import static android.arch.lifecycle.testapp.TestEvent.ACTIVITY_CALLBACK;
+import static android.arch.lifecycle.testapp.TestEvent.OWNER_CALLBACK;
 
 import android.app.Activity;
 import android.arch.lifecycle.Lifecycle;
 import android.arch.lifecycle.LifecycleRegistry;
 import android.arch.lifecycle.LifecycleRegistryOwner;
 import android.os.Bundle;
-import android.util.Pair;
+import android.support.annotation.NonNull;
+import android.support.v4.util.Pair;
 
 import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
 
 /**
  * LifecycleRegistryOwner that extends framework activity.
  */
+@SuppressWarnings("deprecation")
 public class FrameworkLifecycleRegistryActivity extends Activity implements
-        LifecycleRegistryOwner, CollectingActivity {
+        LifecycleRegistryOwner, CollectingLifecycleOwner {
     private LifecycleRegistry mLifecycleRegistry = new LifecycleRegistry(this);
 
+    @NonNull
     @Override
     public LifecycleRegistry getLifecycle() {
         return mLifecycleRegistry;
@@ -49,49 +51,43 @@
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_CREATE));
+        mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_CREATE));
         getLifecycle().addObserver(mTestObserver);
     }
 
     @Override
     protected void onStart() {
         super.onStart();
-        mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_START));
+        mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_START));
     }
 
     @Override
     protected void onResume() {
         super.onResume();
-        mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_RESUME));
-        finish();
+        mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_RESUME));
     }
 
     @Override
     protected void onDestroy() {
         super.onDestroy();
-        mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_DESTROY));
+        mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_DESTROY));
         mLatch.countDown();
     }
 
     @Override
     protected void onStop() {
         super.onStop();
-        mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_STOP));
+        mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_STOP));
     }
 
     @Override
     protected void onPause() {
         super.onPause();
-        mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_PAUSE));
+        mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_PAUSE));
     }
 
-    /**
-     * awaits for all events and returns them.
-     */
     @Override
-    public List<Pair<TestEvent, Lifecycle.Event>> waitForCollectedEvents()
-            throws InterruptedException {
-        mLatch.await(TIMEOUT, TimeUnit.SECONDS);
-        return mCollectedEvents;
+    public List<Pair<TestEvent, Lifecycle.Event>> copyCollectedEvents() {
+        return new ArrayList<>(mCollectedEvents);
     }
 }
diff --git a/android/arch/lifecycle/testapp/FullLifecycleTestActivity.java b/android/arch/lifecycle/testapp/FullLifecycleTestActivity.java
deleted file mode 100644
index 5f33c28..0000000
--- a/android/arch/lifecycle/testapp/FullLifecycleTestActivity.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle.testapp;
-
-import static android.arch.lifecycle.testapp.TestEvent.ACTIVITY_CALLBACK;
-
-import android.arch.lifecycle.Lifecycle;
-import android.os.Bundle;
-import android.support.v4.app.FragmentActivity;
-import android.util.Pair;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Activity for testing full lifecycle
- */
-public class FullLifecycleTestActivity extends FragmentActivity implements CollectingActivity {
-
-    private List<Pair<TestEvent, Lifecycle.Event>> mCollectedEvents = new ArrayList<>();
-    private TestObserver mTestObserver = new TestObserver(mCollectedEvents);
-    private CountDownLatch mLatch = new CountDownLatch(1);
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_CREATE));
-        getLifecycle().addObserver(mTestObserver);
-    }
-
-    @Override
-    protected void onStart() {
-        super.onStart();
-        mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_START));
-    }
-
-    @Override
-    protected void onResume() {
-        super.onResume();
-        mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_RESUME));
-        finish();
-    }
-
-    @Override
-    protected void onDestroy() {
-        super.onDestroy();
-        mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_DESTROY));
-        mLatch.countDown();
-    }
-
-    @Override
-    protected void onStop() {
-        super.onStop();
-        mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_STOP));
-    }
-
-    @Override
-    protected void onPause() {
-        super.onPause();
-        mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_PAUSE));
-    }
-
-    /**
-     * awaits for all events and returns them.
-     */
-    @Override
-    public List<Pair<TestEvent, Lifecycle.Event>> waitForCollectedEvents()
-            throws InterruptedException {
-        mLatch.await(TIMEOUT, TimeUnit.SECONDS);
-        return mCollectedEvents;
-    }
-}
diff --git a/android/arch/lifecycle/testapp/MainActivity.java b/android/arch/lifecycle/testapp/MainActivity.java
deleted file mode 100644
index b9d5914..0000000
--- a/android/arch/lifecycle/testapp/MainActivity.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle.testapp;
-
-import android.os.Bundle;
-import android.support.v4.app.FragmentActivity;
-
-/**
- * Simple test activity
- */
-public class MainActivity extends FragmentActivity {
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(R.layout.activity);
-    }
-}
diff --git a/android/arch/lifecycle/testapp/NavigationDialogActivity.java b/android/arch/lifecycle/testapp/NavigationDialogActivity.java
index 0ae9403..7d53528 100644
--- a/android/arch/lifecycle/testapp/NavigationDialogActivity.java
+++ b/android/arch/lifecycle/testapp/NavigationDialogActivity.java
@@ -22,4 +22,10 @@
  *  an activity with Dialog theme.
  */
 public class NavigationDialogActivity extends FragmentActivity {
+    @Override
+    protected void onPause() {
+        super.onPause();
+        // helps with less flaky API 16 tests
+        overridePendingTransition(0, 0);
+    }
 }
diff --git a/android/arch/lifecycle/testapp/NonSupportActivity.java b/android/arch/lifecycle/testapp/NonSupportActivity.java
new file mode 100644
index 0000000..835d846
--- /dev/null
+++ b/android/arch/lifecycle/testapp/NonSupportActivity.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2017 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.arch.lifecycle.testapp;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * Activity which doesn't extend FragmentActivity, to test ProcessLifecycleOwner because it
+ * should work anyway.
+ */
+public class NonSupportActivity extends Activity {
+
+    private static final int TIMEOUT = 1; //secs
+    private final Lock mLock = new ReentrantLock();
+    private Condition mIsResumedCondition = mLock.newCondition();
+    private boolean mIsResumed = false;
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        mLock.lock();
+        try {
+            mIsResumed = true;
+            mIsResumedCondition.signalAll();
+        } finally {
+            mLock.unlock();
+        }
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        mLock.lock();
+        try {
+            mIsResumed = false;
+        } finally {
+            mLock.unlock();
+        }
+    }
+
+    /**
+     *  awaits resumed state
+     * @return
+     * @throws InterruptedException
+     */
+    public boolean awaitResumedState() throws InterruptedException {
+        mLock.lock();
+        try {
+            while (!mIsResumed) {
+                if (!mIsResumedCondition.await(TIMEOUT, TimeUnit.SECONDS)) {
+                    return false;
+                }
+            }
+            return true;
+        } finally {
+            mLock.unlock();
+        }
+    }
+}
diff --git a/android/arch/lifecycle/testapp/SupportLifecycleRegistryActivity.java b/android/arch/lifecycle/testapp/SupportLifecycleRegistryActivity.java
deleted file mode 100644
index c46c6d3..0000000
--- a/android/arch/lifecycle/testapp/SupportLifecycleRegistryActivity.java
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright (C) 2017 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.arch.lifecycle.testapp;
-
-import static android.arch.lifecycle.testapp.TestEvent.ACTIVITY_CALLBACK;
-
-import android.arch.lifecycle.Lifecycle.Event;
-import android.arch.lifecycle.LifecycleRegistry;
-import android.arch.lifecycle.LifecycleRegistryOwner;
-import android.os.Bundle;
-import android.support.v4.app.FragmentActivity;
-import android.util.Pair;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * LifecycleRegistryOwner that extends FragmentActivity.
- */
-public class SupportLifecycleRegistryActivity extends FragmentActivity implements
-        LifecycleRegistryOwner, CollectingActivity {
-    private LifecycleRegistry mLifecycleRegistry = new LifecycleRegistry(this);
-    @Override
-    public LifecycleRegistry getLifecycle() {
-        return mLifecycleRegistry;
-    }
-
-    private List<Pair<TestEvent, Event>> mCollectedEvents = new ArrayList<>();
-    private TestObserver mTestObserver = new TestObserver(mCollectedEvents);
-    private CountDownLatch mLatch = new CountDownLatch(1);
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Event.ON_CREATE));
-        getLifecycle().addObserver(mTestObserver);
-    }
-
-    @Override
-    protected void onStart() {
-        super.onStart();
-        mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Event.ON_START));
-    }
-
-    @Override
-    protected void onResume() {
-        super.onResume();
-        mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Event.ON_RESUME));
-        finish();
-    }
-
-    @Override
-    protected void onDestroy() {
-        super.onDestroy();
-        mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Event.ON_DESTROY));
-        mLatch.countDown();
-    }
-
-    @Override
-    protected void onStop() {
-        super.onStop();
-        mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Event.ON_STOP));
-    }
-
-    @Override
-    protected void onPause() {
-        super.onPause();
-        mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Event.ON_PAUSE));
-    }
-
-    /**
-     * awaits for all events and returns them.
-     */
-    @Override
-    public List<Pair<TestEvent, Event>> waitForCollectedEvents() throws InterruptedException {
-        mLatch.await(TIMEOUT, TimeUnit.SECONDS);
-        return mCollectedEvents;
-    }
-}
diff --git a/android/arch/lifecycle/testapp/TestEvent.java b/android/arch/lifecycle/testapp/TestEvent.java
index 0929f84..788045a 100644
--- a/android/arch/lifecycle/testapp/TestEvent.java
+++ b/android/arch/lifecycle/testapp/TestEvent.java
@@ -17,6 +17,6 @@
 package android.arch.lifecycle.testapp;
 
 public enum TestEvent {
-    ACTIVITY_CALLBACK,
-    LIFECYCLE_EVENT
+    OWNER_CALLBACK,
+    LIFECYCLE_EVENT,
 }
diff --git a/android/arch/lifecycle/testapp/TestObserver.java b/android/arch/lifecycle/testapp/TestObserver.java
index c611239..00b8e16 100644
--- a/android/arch/lifecycle/testapp/TestObserver.java
+++ b/android/arch/lifecycle/testapp/TestObserver.java
@@ -28,7 +28,7 @@
 import android.arch.lifecycle.LifecycleObserver;
 import android.arch.lifecycle.LifecycleOwner;
 import android.arch.lifecycle.OnLifecycleEvent;
-import android.util.Pair;
+import android.support.v4.util.Pair;
 
 import java.util.List;
 
diff --git a/android/arch/paging/BoundedDataSource.java b/android/arch/paging/BoundedDataSource.java
index 664ab16..0656490 100644
--- a/android/arch/paging/BoundedDataSource.java
+++ b/android/arch/paging/BoundedDataSource.java
@@ -21,7 +21,6 @@
 import android.support.annotation.WorkerThread;
 
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.List;
 
 /**
@@ -75,7 +74,6 @@
             if (result.size() != loadSize) {
                 throw new IllegalStateException("invalid number of items returned.");
             }
-            Collections.reverse(result);
         }
         return result;
     }
diff --git a/android/arch/paging/ContiguousDataSource.java b/android/arch/paging/ContiguousDataSource.java
index afcc208..be9da20 100644
--- a/android/arch/paging/ContiguousDataSource.java
+++ b/android/arch/paging/ContiguousDataSource.java
@@ -26,21 +26,65 @@
 /** @hide */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
 public abstract class ContiguousDataSource<Key, Value> extends DataSource<Key, Value> {
-    /**
-     * Number of items that this DataSource can provide in total, or COUNT_UNDEFINED.
-     *
-     * @return number of items that this DataSource can provide in total, or COUNT_UNDEFINED
-     * if difficult or undesired to compute.
-     */
-    public int countItems() {
-        return COUNT_UNDEFINED;
-    }
-
     @Override
     boolean isContiguous() {
         return true;
     }
 
+    void loadInitial(Key key, int pageSize, boolean enablePlaceholders,
+            PageResult.Receiver<Key, Value> receiver) {
+        NullPaddedList<Value> initial = loadInitial(key, pageSize, enablePlaceholders);
+        if (initial != null) {
+            receiver.onPageResult(new PageResult<>(
+                    PageResult.INIT,
+                    new Page<Key, Value>(initial.mList),
+                    initial.getLeadingNullCount(),
+                    initial.getTrailingNullCount(),
+                    initial.getPositionOffset()));
+        } else {
+            receiver.onPageResult(new PageResult<Key, Value>(
+                    PageResult.INIT, null, 0, 0, 0));
+        }
+    }
+
+    void loadAfter(int currentEndIndex, @NonNull Value currentEndItem, int pageSize,
+            PageResult.Receiver<Key, Value> receiver) {
+        List<Value> list = loadAfter(currentEndIndex, currentEndItem, pageSize);
+
+        Page<Key, Value> page = list != null
+                ? new Page<Key, Value>(list) : null;
+
+        receiver.postOnPageResult(new PageResult<>(
+                PageResult.APPEND, page, 0, 0, 0));
+    }
+
+    void loadBefore(int currentBeginIndex, @NonNull Value currentBeginItem, int pageSize,
+            PageResult.Receiver<Key, Value> receiver) {
+        List<Value> list = loadBefore(currentBeginIndex, currentBeginItem, pageSize);
+
+        Page<Key, Value> page = list != null
+                ? new Page<Key, Value>(list) : null;
+
+        receiver.postOnPageResult(new PageResult<>(
+                PageResult.PREPEND, page, 0, 0, 0));
+    }
+
+    /**
+     * Get the key from either the position, or item, or null if position/item invalid.
+     * <p>
+     * Position may not match passed item's position - if trying to query the key from a position
+     * that isn't yet loaded, a fallback item (last loaded item accessed) will be passed.
+     */
+    abstract Key getKey(int position, Value item);
+
+    @Nullable
+    abstract List<Value> loadAfterImpl(int currentEndIndex,
+            @NonNull Value currentEndItem, int pageSize);
+
+    @Nullable
+    abstract List<Value> loadBeforeImpl(int currentBeginIndex,
+            @NonNull Value currentBeginItem, int pageSize);
+
     /** @hide */
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     @WorkerThread
@@ -48,21 +92,7 @@
     public abstract NullPaddedList<Value> loadInitial(
             Key key, int initialLoadSize, boolean enablePlaceholders);
 
-    /**
-     * Load data after the given position / item.
-     * <p>
-     * It's valid to return a different list size than the page size, if it's easier for this data
-     * source. It is generally safer to increase number loaded than reduce.
-     *
-     * @param currentEndIndex Load items after this index, starting with currentEndIndex + 1.
-     * @param currentEndItem  Load items after this item, can be used for precise querying based on
-     *                        item contents.
-     * @param pageSize        Suggested number of items to load.
-     * @return List of items, starting at position currentEndIndex + 1. Null if the data source is
-     * no longer valid, and should not be queried again.
-     *
-     * @hide
-     */
+    /** @hide */
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     @WorkerThread
     @Nullable
@@ -78,24 +108,7 @@
         return list;
     }
 
-    @Nullable
-    abstract List<Value> loadAfterImpl(int currentEndIndex,
-            @NonNull Value currentEndItem, int pageSize);
-
-    /**
-     * Load data before the given position / item.
-     * <p>
-     * It's valid to return a different list size than the page size, if it's easier for this data
-     * source. It is generally safer to increase number loaded than reduce.
-     *
-     * @param currentBeginIndex Load items before this index, starting with currentBeginIndex - 1.
-     * @param currentBeginItem  Load items after this item, can be used for precise querying based
-     *                          on item contents.
-     * @param pageSize          Suggested number of items to load.
-     * @return List of items, in descending order, starting at position currentBeginIndex - 1.
-     *
-     * @hide
-     */
+    /** @hide */
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     @WorkerThread
     @Nullable
@@ -111,15 +124,4 @@
         return list;
 
     }
-
-    @Nullable
-    abstract List<Value> loadBeforeImpl(int currentBeginIndex,
-            @NonNull Value currentBeginItem, int pageSize);
-
-    /**
-     * Get the key from either the position, or item. Position may not match passed item's position,
-     * if trying to query the key from a position that isn't yet loaded, so a fallback item must be
-     * used.
-     */
-    abstract Key getKey(int position, Value item);
 }
diff --git a/android/arch/paging/ContiguousDiffHelperTest.java b/android/arch/paging/ContiguousDiffHelperTest.java
deleted file mode 100644
index 4f221b3..0000000
--- a/android/arch/paging/ContiguousDiffHelperTest.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * Copyright (C) 2017 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.arch.paging;
-
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.verifyZeroInteractions;
-
-import android.support.annotation.NonNull;
-import android.support.test.filters.SmallTest;
-import android.support.v7.recyclerview.extensions.DiffCallback;
-import android.support.v7.util.DiffUtil;
-import android.support.v7.util.ListUpdateCallback;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-import org.mockito.Mockito;
-
-@SmallTest
-@RunWith(JUnit4.class)
-public class ContiguousDiffHelperTest {
-    private interface CallbackValidator {
-        void validate(ListUpdateCallback callback);
-    }
-
-    private static final DiffCallback<String> DIFF_CALLBACK = new DiffCallback<String>() {
-        @Override
-        public boolean areItemsTheSame(@NonNull String oldItem, @NonNull String newItem) {
-            // first char means same item
-            return oldItem.charAt(0) == newItem.charAt(0);
-        }
-
-        @Override
-        public boolean areContentsTheSame(@NonNull String oldItem, @NonNull String newItem) {
-            return oldItem.equals(newItem);
-        }
-    };
-
-    private void validateTwoListDiff(StringPagedList oldList, StringPagedList newList,
-            CallbackValidator callbackValidator) {
-        DiffUtil.DiffResult diffResult = ContiguousDiffHelper.computeDiff(oldList, newList,
-                DIFF_CALLBACK, false);
-
-        ListUpdateCallback listUpdateCallback = Mockito.mock(ListUpdateCallback.class);
-        ContiguousDiffHelper.dispatchDiff(listUpdateCallback, oldList, newList, diffResult);
-
-        callbackValidator.validate(listUpdateCallback);
-    }
-
-    @Test
-    public void sameListNoUpdates() {
-        validateTwoListDiff(
-                new StringPagedList(5, 5, "a", "b", "c"),
-                new StringPagedList(5, 5, "a", "b", "c"),
-                new CallbackValidator() {
-                    @Override
-                    public void validate(ListUpdateCallback callback) {
-                        verifyZeroInteractions(callback);
-                    }
-                }
-        );
-    }
-
-    @Test
-    public void appendFill() {
-        validateTwoListDiff(
-                new StringPagedList(5, 5, "a", "b"),
-                new StringPagedList(5, 4, "a", "b", "c"),
-                new CallbackValidator() {
-                    @Override
-                    public void validate(ListUpdateCallback callback) {
-                        verify(callback).onRemoved(11, 1);
-                        verify(callback).onInserted(7, 1);
-                        // NOTE: ideally would be onChanged(7, 1, null)
-                        verifyNoMoreInteractions(callback);
-                    }
-                }
-        );
-    }
-
-    @Test
-    public void prependFill() {
-        validateTwoListDiff(
-                new StringPagedList(5, 5, "b", "c"),
-                new StringPagedList(4, 5, "a", "b", "c"),
-                new CallbackValidator() {
-                    @Override
-                    public void validate(ListUpdateCallback callback) {
-                        verify(callback).onRemoved(0, 1);
-                        verify(callback).onInserted(4, 1);
-                        //NOTE: ideally would be onChanged(4, 1, null);
-                        verifyNoMoreInteractions(callback);
-                    }
-                }
-        );
-    }
-
-    @Test
-    public void change() {
-        validateTwoListDiff(
-                new StringPagedList(5, 5, "a1", "b1", "c1"),
-                new StringPagedList(5, 5, "a2", "b1", "c2"),
-                new CallbackValidator() {
-                    @Override
-                    public void validate(ListUpdateCallback callback) {
-                        verify(callback).onChanged(5, 1, null);
-                        verify(callback).onChanged(7, 1, null);
-                        verifyNoMoreInteractions(callback);
-                    }
-                }
-        );
-    }
-}
diff --git a/android/arch/paging/ContiguousPagedList.java b/android/arch/paging/ContiguousPagedList.java
index d8907c3..2a5cd42 100644
--- a/android/arch/paging/ContiguousPagedList.java
+++ b/android/arch/paging/ContiguousPagedList.java
@@ -16,101 +16,136 @@
 
 package android.arch.paging;
 
+import android.support.annotation.AnyThread;
 import android.support.annotation.MainThread;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
-import android.support.annotation.RestrictTo;
-import android.support.annotation.WorkerThread;
 
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
 import java.util.concurrent.Executor;
-import java.util.concurrent.atomic.AtomicBoolean;
 
-/** @hide */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-class ContiguousPagedList<T> extends NullPaddedList<T> {
-
-    private final ContiguousDataSource<?, T> mDataSource;
-    private final Executor mMainThreadExecutor;
-    private final Executor mBackgroundThreadExecutor;
-    private final Config mConfig;
-
+class ContiguousPagedList<K, V> extends PagedList<V> implements PagedStorage.Callback {
+    private final ContiguousDataSource<K, V> mDataSource;
     private boolean mPrependWorkerRunning = false;
     private boolean mAppendWorkerRunning = false;
 
     private int mPrependItemsRequested = 0;
     private int mAppendItemsRequested = 0;
 
-    private int mLastLoad = 0;
-    private T mLastItem = null;
+    @SuppressWarnings("unchecked")
+    private final PagedStorage<K, V> mKeyedStorage = (PagedStorage<K, V>) mStorage;
 
-    private AtomicBoolean mDetached = new AtomicBoolean(false);
+    private final PageResult.Receiver<K, V> mReceiver = new PageResult.Receiver<K, V>() {
+        @AnyThread
+        @Override
+        public void postOnPageResult(@NonNull final PageResult<K, V> pageResult) {
+            // NOTE: if we're already on main thread, this can delay page receive by a frame
+            mMainThreadExecutor.execute(new Runnable() {
+                @Override
+                public void run() {
+                    onPageResult(pageResult);
+                }
+            });
+        }
 
-    private ArrayList<WeakReference<Callback>> mCallbacks = new ArrayList<>();
+        @MainThread
+        @Override
+        public void onPageResult(@NonNull PageResult<K, V> pageResult) {
+            if (pageResult.page == null) {
+                detach();
+                return;
+            }
 
-    @WorkerThread
-    <K> ContiguousPagedList(@NonNull ContiguousDataSource<K, T> dataSource,
+            if (isDetached()) {
+                // No op, have detached
+                return;
+            }
+
+            Page<K, V> page = pageResult.page;
+            if (pageResult.type == PageResult.INIT) {
+                mKeyedStorage.init(pageResult.leadingNulls, page, pageResult.trailingNulls,
+                        pageResult.positionOffset, ContiguousPagedList.this);
+                notifyInserted(0, mKeyedStorage.size());
+            } else if (pageResult.type == PageResult.APPEND) {
+                mKeyedStorage.appendPage(page, ContiguousPagedList.this);
+            } else if (pageResult.type == PageResult.PREPEND) {
+                mKeyedStorage.prependPage(page, ContiguousPagedList.this);
+            }
+        }
+    };
+
+    ContiguousPagedList(
+            @NonNull ContiguousDataSource<K, V> dataSource,
             @NonNull Executor mainThreadExecutor,
             @NonNull Executor backgroundThreadExecutor,
-            Config config,
-            @Nullable K key) {
-        super();
-
+            @NonNull Config config,
+            final @Nullable K key) {
+        super(new PagedStorage<K, V>(), mainThreadExecutor, backgroundThreadExecutor, config);
         mDataSource = dataSource;
-        mMainThreadExecutor = mainThreadExecutor;
-        mBackgroundThreadExecutor = backgroundThreadExecutor;
-        mConfig = config;
-        NullPaddedList<T> initialState = dataSource.loadInitial(
-                key, config.mInitialLoadSizeHint, config.mEnablePlaceholders);
 
-        if (initialState != null) {
-            mPositionOffset = initialState.getPositionOffset();
+        // blocking init just triggers the initial load on the construction thread -
+        // Could still be posted with callback, if desired.
+        mDataSource.loadInitial(key,
+                mConfig.initialLoadSizeHint,
+                mConfig.enablePlaceholders,
+                mReceiver);
+    }
 
-            mLeadingNullCount = initialState.getLeadingNullCount();
-            mList = new ArrayList<>(initialState.mList);
-            mTrailingNullCount = initialState.getTrailingNullCount();
+    @MainThread
+    @Override
+    void dispatchUpdatesSinceSnapshot(
+            @NonNull PagedList<V> pagedListSnapshot, @NonNull Callback callback) {
 
-            if (initialState.getLeadingNullCount() == 0
-                    && initialState.getTrailingNullCount() == 0
-                    && config.mPrefetchDistance < 1) {
-                throw new IllegalArgumentException("Null padding is required to support the 0"
-                        + " prefetch case - require either null items or prefetching to fetch"
-                        + " beyond initial load.");
-            }
+        final PagedStorage<?, V> snapshot = pagedListSnapshot.mStorage;
 
-            if (initialState.size() != 0) {
-                mLastLoad = mLeadingNullCount + mList.size() / 2;
-                mLastItem = mList.get(mList.size() / 2);
-            }
-        } else {
-            mList = new ArrayList<>();
-            detach();
+        final int newlyAppended = mStorage.getNumberAppended() - snapshot.getNumberAppended();
+        final int newlyPrepended = mStorage.getNumberPrepended() - snapshot.getNumberPrepended();
+
+        final int previousTrailing = snapshot.getTrailingNullCount();
+        final int previousLeading = snapshot.getLeadingNullCount();
+
+        // Validate that the snapshot looks like a previous version of this list - if it's not,
+        // we can't be sure we'll dispatch callbacks safely
+        if (newlyAppended < 0
+                || newlyPrepended < 0
+                || mStorage.getTrailingNullCount() != Math.max(previousTrailing - newlyAppended, 0)
+                || mStorage.getLeadingNullCount() != Math.max(previousLeading - newlyPrepended, 0)
+                || (mStorage.getStorageCount()
+                        != snapshot.getStorageCount() + newlyAppended + newlyPrepended)) {
+            throw new IllegalArgumentException("Invalid snapshot provided - doesn't appear"
+                    + " to be a snapshot of this PagedList");
         }
-        if (mList.size() == 0) {
-            // Empty initial state, so don't try and fetch data.
-            mPrependWorkerRunning = true;
-            mAppendWorkerRunning = true;
+
+        if (newlyAppended != 0) {
+            final int changedCount = Math.min(previousTrailing, newlyAppended);
+            final int addedCount = newlyAppended - changedCount;
+
+            final int endPosition = snapshot.getLeadingNullCount() + snapshot.getStorageCount();
+            if (changedCount != 0) {
+                callback.onChanged(endPosition, changedCount);
+            }
+            if (addedCount != 0) {
+                callback.onInserted(endPosition + changedCount, addedCount);
+            }
+        }
+        if (newlyPrepended != 0) {
+            final int changedCount = Math.min(previousLeading, newlyPrepended);
+            final int addedCount = newlyPrepended - changedCount;
+
+            if (changedCount != 0) {
+                callback.onChanged(previousLeading, changedCount);
+            }
+            if (addedCount != 0) {
+                callback.onInserted(0, addedCount);
+            }
         }
     }
 
+    @MainThread
     @Override
-    public T get(int index) {
-        T item = super.get(index);
-        if (item != null) {
-            mLastItem = item;
-        }
-        return item;
-    }
-
-    @Override
-    public void loadAround(int index) {
-        mLastLoad = index + mPositionOffset;
-
-        int prependItems = mConfig.mPrefetchDistance - (index - mLeadingNullCount);
-        int appendItems = index + mConfig.mPrefetchDistance - (mLeadingNullCount + mList.size());
+    protected void loadAroundInternal(int index) {
+        int prependItems = mConfig.prefetchDistance - (index - mStorage.getLeadingNullCount());
+        int appendItems = index + mConfig.prefetchDistance
+                - (mStorage.getLeadingNullCount() + mStorage.getStorageCount());
 
         mPrependItemsRequested = Math.max(prependItems, mPrependItemsRequested);
         if (mPrependItemsRequested > 0) {
@@ -123,21 +158,6 @@
         }
     }
 
-    @Override
-    public int getLoadedCount() {
-        return mList.size();
-    }
-
-    @Override
-    public int getLeadingNullCount() {
-        return mLeadingNullCount;
-    }
-
-    @Override
-    public int getTrailingNullCount() {
-        return mTrailingNullCount;
-    }
-
     @MainThread
     private void schedulePrepend() {
         if (mPrependWorkerRunning) {
@@ -145,29 +165,17 @@
         }
         mPrependWorkerRunning = true;
 
-        final int position = mLeadingNullCount + mPositionOffset;
-        final T item = mList.get(0);
+        final int position = mStorage.getLeadingNullCount() + mStorage.getPositionOffset();
+
+        // safe to access first item here - mStorage can't be empty if we're prepending
+        final V item = mStorage.getFirstContiguousItem();
         mBackgroundThreadExecutor.execute(new Runnable() {
             @Override
             public void run() {
-                if (mDetached.get()) {
+                if (isDetached()) {
                     return;
                 }
-
-                final List<T> data = mDataSource.loadBefore(position, item, mConfig.mPageSize);
-                if (data != null) {
-                    mMainThreadExecutor.execute(new Runnable() {
-                        @Override
-                        public void run() {
-                            if (mDetached.get()) {
-                                return;
-                            }
-                            prependImpl(data);
-                        }
-                    });
-                } else {
-                    detach();
-                }
+                mDataSource.loadBefore(position, item, mConfig.pageSize, mReceiver);
             }
         });
     }
@@ -179,56 +187,44 @@
         }
         mAppendWorkerRunning = true;
 
-        final int position = mLeadingNullCount + mList.size() - 1 + mPositionOffset;
-        final T item = mList.get(mList.size() - 1);
+        final int position = mStorage.getLeadingNullCount()
+                + mStorage.getStorageCount() - 1 + mStorage.getPositionOffset();
+
+        // safe to access first item here - mStorage can't be empty if we're appending
+        final V item = mStorage.getLastContiguousItem();
         mBackgroundThreadExecutor.execute(new Runnable() {
             @Override
             public void run() {
-                if (mDetached.get()) {
+                if (isDetached()) {
                     return;
                 }
-
-                final List<T> data = mDataSource.loadAfter(position, item, mConfig.mPageSize);
-                if (data != null) {
-                    mMainThreadExecutor.execute(new Runnable() {
-                        @Override
-                        public void run() {
-                            if (mDetached.get()) {
-                                return;
-                            }
-                            appendImpl(data);
-                        }
-                    });
-                } else {
-                    detach();
-                }
+                mDataSource.loadAfter(position, item, mConfig.pageSize, mReceiver);
             }
         });
     }
 
+    @Override
+    boolean isContiguous() {
+        return true;
+    }
+
+    @Nullable
+    @Override
+    public Object getLastKey() {
+        return mDataSource.getKey(mLastLoad, mLastItem);
+    }
+
     @MainThread
-    private void prependImpl(List<T> before) {
-        final int count = before.size();
-        if (count == 0) {
-            // Nothing returned from source, stop loading in this direction
-            return;
-        }
+    @Override
+    public void onInitialized(int count) {
+        notifyInserted(0, count);
+    }
 
-        Collections.reverse(before);
-        mList.addAll(0, before);
-
-        final int changedCount = Math.min(mLeadingNullCount, count);
-        final int addedCount = count - changedCount;
-
-        if (changedCount != 0) {
-            mLeadingNullCount -= changedCount;
-        }
-        mPositionOffset -= addedCount;
-        mNumberPrepended += count;
-
-
-        // only try to post more work after fully prepended (with offsets / null counts updated)
-        mPrependItemsRequested -= count;
+    @MainThread
+    @Override
+    public void onPagePrepended(int leadingNulls, int changedCount, int addedCount) {
+        // consider whether to post more work, now that a page is fully prepended
+        mPrependItemsRequested = mPrependItemsRequested - changedCount - addedCount;
         mPrependWorkerRunning = false;
         if (mPrependItemsRequested > 0) {
             // not done prepending, keep going
@@ -236,39 +232,16 @@
         }
 
         // finally dispatch callbacks, after prepend may have already been scheduled
-        for (WeakReference<Callback> weakRef : mCallbacks) {
-            Callback callback = weakRef.get();
-            if (callback != null) {
-                if (changedCount != 0) {
-                    callback.onChanged(mLeadingNullCount, changedCount);
-                }
-                if (addedCount != 0) {
-                    callback.onInserted(0, addedCount);
-                }
-            }
-        }
+        notifyChanged(leadingNulls, changedCount);
+        notifyInserted(0, addedCount);
     }
 
     @MainThread
-    private void appendImpl(List<T> after) {
-        final int count = after.size();
-        if (count == 0) {
-            // Nothing returned from source, stop loading in this direction
-            return;
-        }
+    @Override
+    public void onPageAppended(int endPosition, int changedCount, int addedCount) {
+        // consider whether to post more work, now that a page is fully appended
 
-        mList.addAll(after);
-
-        final int changedCount = Math.min(mTrailingNullCount, count);
-        final int addedCount = count - changedCount;
-
-        if (changedCount != 0) {
-            mTrailingNullCount -= changedCount;
-        }
-        mNumberAppended += count;
-
-        // only try to post more work after fully appended (with null counts updated)
-        mAppendItemsRequested -= count;
+        mAppendItemsRequested = mAppendItemsRequested - changedCount - addedCount;
         mAppendWorkerRunning = false;
         if (mAppendItemsRequested > 0) {
             // not done appending, keep going
@@ -276,100 +249,19 @@
         }
 
         // finally dispatch callbacks, after append may have already been scheduled
-        for (WeakReference<Callback> weakRef : mCallbacks) {
-            Callback callback = weakRef.get();
-            if (callback != null) {
-                final int endPosition = mLeadingNullCount + mList.size() - count;
-                if (changedCount != 0) {
-                    callback.onChanged(endPosition, changedCount);
-                }
-                if (addedCount != 0) {
-                    callback.onInserted(endPosition + changedCount, addedCount);
-                }
-            }
-        }
+        notifyChanged(endPosition, changedCount);
+        notifyInserted(endPosition + changedCount, addedCount);
     }
 
+    @MainThread
     @Override
-    public boolean isImmutable() {
-        // TODO: return true if had nulls, and now getLoadedCount() == size(). Is that safe?
-        // Currently we don't prevent DataSources from returning more items than their null counts
-        return isDetached();
+    public void onPagePlaceholderInserted(int pageIndex) {
+        throw new IllegalStateException("Tiled callback on ContiguousPagedList");
     }
 
+    @MainThread
     @Override
-    public void addWeakCallback(@Nullable PagedList<T> previousSnapshot,
-            @NonNull Callback callback) {
-        NullPaddedList<T> snapshot = (NullPaddedList<T>) previousSnapshot;
-        if (snapshot != this && snapshot != null) {
-            final int newlyAppended = mNumberAppended - snapshot.getNumberAppended();
-            final int newlyPrepended = mNumberPrepended - snapshot.getNumberPrepended();
-
-            final int previousTrailing = snapshot.getTrailingNullCount();
-            final int previousLeading = snapshot.getLeadingNullCount();
-
-            // Validate that the snapshot looks like a previous version of this list - if it's not,
-            // we can't be sure we'll dispatch callbacks safely
-            if (newlyAppended < 0
-                    || newlyPrepended < 0
-                    || mTrailingNullCount != Math.max(previousTrailing - newlyAppended, 0)
-                    || mLeadingNullCount != Math.max(previousLeading - newlyPrepended, 0)
-                    || snapshot.getLoadedCount() + newlyAppended + newlyPrepended != mList.size()) {
-                throw new IllegalArgumentException("Invalid snapshot provided - doesn't appear"
-                        + " to be a snapshot of this list");
-            }
-
-            if (newlyAppended != 0) {
-                final int changedCount = Math.min(previousTrailing, newlyAppended);
-                final int addedCount = newlyAppended - changedCount;
-
-                final int endPosition =
-                        snapshot.getLeadingNullCount() + snapshot.getLoadedCount();
-                if (changedCount != 0) {
-                    callback.onChanged(endPosition, changedCount);
-                }
-                if (addedCount != 0) {
-                    callback.onInserted(endPosition + changedCount, addedCount);
-                }
-            }
-            if (newlyPrepended != 0) {
-                final int changedCount = Math.min(previousLeading, newlyPrepended);
-                final int addedCount = newlyPrepended - changedCount;
-
-                if (changedCount != 0) {
-                    callback.onChanged(previousLeading, changedCount);
-                }
-                if (addedCount != 0) {
-                    callback.onInserted(0, addedCount);
-                }
-            }
-        }
-        mCallbacks.add(new WeakReference<>(callback));
-    }
-
-    @Override
-    public void removeWeakCallback(@NonNull Callback callback) {
-        for (int i = mCallbacks.size() - 1; i >= 0; i--) {
-            Callback currentCallback = mCallbacks.get(i).get();
-            if (currentCallback == null || currentCallback == callback) {
-                mCallbacks.remove(i);
-            }
-        }
-    }
-
-    @Override
-    public boolean isDetached() {
-        return mDetached.get();
-    }
-
-    @SuppressWarnings("WeakerAccess")
-    public void detach() {
-        mDetached.set(true);
-    }
-
-    @Nullable
-    @Override
-    public Object getLastKey() {
-        return mDataSource.getKey(mLastLoad, mLastItem);
+    public void onPageInserted(int start, int count) {
+        throw new IllegalStateException("Tiled callback on ContiguousPagedList");
     }
 }
diff --git a/android/arch/paging/ContiguousPagedListTest.java b/android/arch/paging/ContiguousPagedListTest.java
deleted file mode 100644
index ee7ea6a..0000000
--- a/android/arch/paging/ContiguousPagedListTest.java
+++ /dev/null
@@ -1,333 +0,0 @@
-/*
- * Copyright (C) 2017 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.arch.paging;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertSame;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.verifyZeroInteractions;
-
-import android.support.annotation.Nullable;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-
-@RunWith(Parameterized.class)
-public class ContiguousPagedListTest {
-
-    @Parameterized.Parameters(name = "counted:{0}")
-    public static List<Object[]> parameters() {
-        return Arrays.asList(new Object[][]{{true}, {false}});
-    }
-
-    public ContiguousPagedListTest(boolean counted) {
-        mCounted = counted;
-    }
-
-    private final boolean mCounted;
-    private TestExecutor mMainThread = new TestExecutor();
-    private TestExecutor mBackgroundThread = new TestExecutor();
-
-    private static final ArrayList<Item> ITEMS = new ArrayList<>();
-
-    static {
-        for (int i = 0; i < 100; i++) {
-            ITEMS.add(new Item(i));
-        }
-    }
-
-    @SuppressWarnings("WeakerAccess")
-    private static class Item {
-        private Item(int position) {
-            this.position = position;
-            this.name = "Item " + position;
-        }
-
-        public final int position;
-        public final String name;
-
-        @Override
-        public String toString() {
-            return name;
-        }
-    }
-
-    private class TestSource extends PositionalDataSource<Item> {
-        @Override
-        public int countItems() {
-            if (mCounted) {
-                return ITEMS.size();
-            } else {
-                return COUNT_UNDEFINED;
-            }
-        }
-
-        private List<Item> getClampedRange(int startInc, int endExc, boolean reverse) {
-            startInc = Math.max(0, startInc);
-            endExc = Math.min(ITEMS.size(), endExc);
-            List<Item> list = ITEMS.subList(startInc, endExc);
-            if (reverse) {
-                Collections.reverse(list);
-            }
-            return list;
-        }
-
-        @Nullable
-        @Override
-        public List<Item> loadAfter(int startIndex, int pageSize) {
-            return getClampedRange(startIndex, startIndex + pageSize, false);
-        }
-
-        @Nullable
-        @Override
-        public List<Item> loadBefore(int startIndex, int pageSize) {
-            return getClampedRange(startIndex - pageSize + 1, startIndex + 1, true);
-        }
-    }
-
-    private void verifyRange(int start, int count, NullPaddedList<Item> actual) {
-        if (mCounted) {
-            int expectedLeading = start;
-            int expectedTrailing = ITEMS.size() - start - count;
-            assertEquals(ITEMS.size(), actual.size());
-            assertEquals(ITEMS.size() - expectedLeading - expectedTrailing,
-                    actual.getLoadedCount());
-            assertEquals(expectedLeading, actual.getLeadingNullCount());
-            assertEquals(expectedTrailing, actual.getTrailingNullCount());
-
-            for (int i = 0; i < actual.getLoadedCount(); i++) {
-                assertSame(ITEMS.get(i + start), actual.get(i + start));
-            }
-        } else {
-            assertEquals(count, actual.size());
-            assertEquals(actual.size(), actual.getLoadedCount());
-            assertEquals(0, actual.getLeadingNullCount());
-            assertEquals(0, actual.getTrailingNullCount());
-
-            for (int i = 0; i < actual.getLoadedCount(); i++) {
-                assertSame(ITEMS.get(i + start), actual.get(i));
-            }
-        }
-    }
-
-    private void verifyCallback(PagedList.Callback callback, int countedPosition,
-            int uncountedPosition) {
-        if (mCounted) {
-            verify(callback).onChanged(countedPosition, 20);
-        } else {
-            verify(callback).onInserted(uncountedPosition, 20);
-        }
-    }
-
-    @Test
-    public void initialLoad() {
-        verifyRange(30, 40,
-                new TestSource().loadInitial(50, 40, true));
-
-        verifyRange(0, 10,
-                new TestSource().loadInitial(5, 10, true));
-
-        verifyRange(90, 10,
-                new TestSource().loadInitial(95, 10, true));
-    }
-
-
-    private ContiguousPagedList<Item> createCountedPagedList(
-            PagedList.Config config, int initialPosition) {
-        TestSource source = new TestSource();
-        return new ContiguousPagedList<>(
-                source, mMainThread, mBackgroundThread,
-                config,
-                initialPosition);
-    }
-
-    private ContiguousPagedList<Item> createCountedPagedList(int initialPosition) {
-        return createCountedPagedList(
-                new PagedList.Config.Builder()
-                        .setInitialLoadSizeHint(40)
-                        .setPageSize(20)
-                        .setPrefetchDistance(20)
-                        .build(),
-                initialPosition);
-    }
-
-    @Test
-    public void append() {
-        ContiguousPagedList<Item> pagedList = createCountedPagedList(0);
-        PagedList.Callback callback = mock(PagedList.Callback.class);
-        pagedList.addWeakCallback(null, callback);
-        verifyRange(0, 40, pagedList);
-        verifyZeroInteractions(callback);
-
-        pagedList.loadAround(35);
-        drain();
-
-        verifyRange(0, 60, pagedList);
-        verifyCallback(callback, 40, 40);
-        verifyNoMoreInteractions(callback);
-    }
-
-
-    @Test
-    public void prepend() {
-        ContiguousPagedList<Item> pagedList = createCountedPagedList(80);
-        PagedList.Callback callback = mock(PagedList.Callback.class);
-        pagedList.addWeakCallback(null, callback);
-        verifyRange(60, 40, pagedList);
-        verifyZeroInteractions(callback);
-
-        pagedList.loadAround(mCounted ? 65 : 5);
-        drain();
-
-        verifyRange(40, 60, pagedList);
-        verifyCallback(callback, 40, 0);
-        verifyNoMoreInteractions(callback);
-    }
-
-    @Test
-    public void outwards() {
-        ContiguousPagedList<Item> pagedList = createCountedPagedList(50);
-        PagedList.Callback callback = mock(PagedList.Callback.class);
-        pagedList.addWeakCallback(null, callback);
-        verifyRange(30, 40, pagedList);
-        verifyZeroInteractions(callback);
-
-        pagedList.loadAround(mCounted ? 65 : 35);
-        drain();
-
-        verifyRange(30, 60, pagedList);
-        verifyCallback(callback, 70, 40);
-        verifyNoMoreInteractions(callback);
-
-        pagedList.loadAround(mCounted ? 35 : 5);
-        drain();
-
-        verifyRange(10, 80, pagedList);
-        verifyCallback(callback, 10, 0);
-        verifyNoMoreInteractions(callback);
-    }
-
-    @Test
-    public void multiAppend() {
-        ContiguousPagedList<Item> pagedList = createCountedPagedList(0);
-        PagedList.Callback callback = mock(PagedList.Callback.class);
-        pagedList.addWeakCallback(null, callback);
-        verifyRange(0, 40, pagedList);
-        verifyZeroInteractions(callback);
-
-        pagedList.loadAround(55);
-        drain();
-
-        verifyRange(0, 80, pagedList);
-        verifyCallback(callback, 40, 40);
-        verifyCallback(callback, 60, 60);
-        verifyNoMoreInteractions(callback);
-    }
-
-    @Test
-    public void distantPrefetch() {
-        ContiguousPagedList<Item> pagedList = createCountedPagedList(
-                new PagedList.Config.Builder()
-                        .setInitialLoadSizeHint(10)
-                        .setPageSize(10)
-                        .setPrefetchDistance(30)
-                        .build(),
-                0);
-        PagedList.Callback callback = mock(PagedList.Callback.class);
-        pagedList.addWeakCallback(null, callback);
-        verifyRange(0, 10, pagedList);
-        verifyZeroInteractions(callback);
-
-        pagedList.loadAround(5);
-        drain();
-
-        verifyRange(0, 40, pagedList);
-
-        pagedList.loadAround(6);
-        drain();
-
-        // although our prefetch window moves forward, no new load triggered
-        verifyRange(0, 40, pagedList);
-    }
-
-    @Test
-    public void appendCallbackAddedLate() {
-        ContiguousPagedList<Item> pagedList = createCountedPagedList(0);
-        verifyRange(0, 40, pagedList);
-
-        pagedList.loadAround(35);
-        drain();
-        verifyRange(0, 60, pagedList);
-
-        // snapshot at 60 items
-        NullPaddedList<Item> snapshot = (NullPaddedList<Item>) pagedList.snapshot();
-        verifyRange(0, 60, snapshot);
-
-
-        pagedList.loadAround(55);
-        drain();
-        verifyRange(0, 80, pagedList);
-        verifyRange(0, 60, snapshot);
-
-        PagedList.Callback callback = mock(PagedList.Callback.class);
-        pagedList.addWeakCallback(snapshot, callback);
-        verifyCallback(callback, 60, 60);
-        verifyNoMoreInteractions(callback);
-    }
-
-
-    @Test
-    public void prependCallbackAddedLate() {
-        ContiguousPagedList<Item> pagedList = createCountedPagedList(80);
-        verifyRange(60, 40, pagedList);
-
-        pagedList.loadAround(mCounted ? 65 : 5);
-        drain();
-        verifyRange(40, 60, pagedList);
-
-        // snapshot at 60 items
-        NullPaddedList<Item> snapshot = (NullPaddedList<Item>) pagedList.snapshot();
-        verifyRange(40, 60, snapshot);
-
-
-        pagedList.loadAround(mCounted ? 45 : 5);
-        drain();
-        verifyRange(20, 80, pagedList);
-        verifyRange(40, 60, snapshot);
-
-        PagedList.Callback callback = mock(PagedList.Callback.class);
-        pagedList.addWeakCallback(snapshot, callback);
-        verifyCallback(callback, 40, 0);
-        verifyNoMoreInteractions(callback);
-    }
-
-    private void drain() {
-        boolean executed;
-        do {
-            executed = mBackgroundThread.executeAll();
-            executed |= mMainThread.executeAll();
-        } while (executed);
-    }
-}
diff --git a/android/arch/paging/DataSource.java b/android/arch/paging/DataSource.java
index 48fbec5..524e570 100644
--- a/android/arch/paging/DataSource.java
+++ b/android/arch/paging/DataSource.java
@@ -17,6 +17,7 @@
 package android.arch.paging;
 
 import android.support.annotation.AnyThread;
+import android.support.annotation.NonNull;
 import android.support.annotation.WorkerThread;
 
 import java.util.concurrent.CopyOnWriteArrayList;
@@ -60,15 +61,6 @@
     public static int COUNT_UNDEFINED = -1;
 
     /**
-     * Number of items that this DataSource can provide in total, or {@link #COUNT_UNDEFINED}.
-     *
-     * @return number of items that this DataSource can provide in total, or
-     * {@link #COUNT_UNDEFINED} if expensive or undesired to compute.
-     */
-    @WorkerThread
-    public abstract int countItems();
-
-    /**
      * Returns true if the data source guaranteed to produce a contiguous set of items,
      * never producing gaps.
      */
@@ -111,7 +103,7 @@
      */
     @AnyThread
     @SuppressWarnings("WeakerAccess")
-    public void addInvalidatedCallback(InvalidatedCallback onInvalidatedCallback) {
+    public void addInvalidatedCallback(@NonNull InvalidatedCallback onInvalidatedCallback) {
         mOnInvalidatedCallbacks.add(onInvalidatedCallback);
     }
 
@@ -122,7 +114,7 @@
      */
     @AnyThread
     @SuppressWarnings("WeakerAccess")
-    public void removeInvalidatedCallback(InvalidatedCallback onInvalidatedCallback) {
+    public void removeInvalidatedCallback(@NonNull InvalidatedCallback onInvalidatedCallback) {
         mOnInvalidatedCallbacks.remove(onInvalidatedCallback);
     }
 
diff --git a/android/arch/paging/KeyedDataSource.java b/android/arch/paging/KeyedDataSource.java
index 8cf6829..0d45294 100644
--- a/android/arch/paging/KeyedDataSource.java
+++ b/android/arch/paging/KeyedDataSource.java
@@ -103,10 +103,6 @@
  * @param <Value> Type of items being loaded by the DataSource.
  */
 public abstract class KeyedDataSource<Key, Value> extends ContiguousDataSource<Key, Value> {
-    @Override
-    public final int countItems() {
-        return 0; // method not called, can't be overridden
-    }
 
     @Nullable
     @Override
@@ -118,7 +114,14 @@
     @Override
     List<Value> loadBeforeImpl(
             int currentBeginIndex, @NonNull Value currentBeginItem, int pageSize) {
-        return loadBefore(getKey(currentBeginItem), pageSize);
+        List<Value> list = loadBefore(getKey(currentBeginItem), pageSize);
+
+        if (list != null && list.size() > 1) {
+            // TODO: move out of keyed entirely, into the DB DataSource.
+            list = new ArrayList<>(list);
+            Collections.reverse(list);
+        }
+        return list;
     }
 
     @Nullable
@@ -191,6 +194,8 @@
 
     /** @hide */
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    @WorkerThread
+    @Override
     public NullPaddedList<Value> loadInitial(
             @Nullable Key key, int initialLoadSize, boolean enablePlaceholders) {
         if (isInvalid()) {
diff --git a/android/arch/paging/ListDataSource.java b/android/arch/paging/ListDataSource.java
deleted file mode 100644
index f3e83d0..0000000
--- a/android/arch/paging/ListDataSource.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2017 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.arch.paging;
-
-import java.util.List;
-
-public class ListDataSource<T> extends TiledDataSource<T> {
-    private List<T> mList;
-
-    ListDataSource(List<T> data) {
-        mList = data;
-    }
-
-    @Override
-    public int countItems() {
-        return mList.size();
-    }
-
-    @Override
-    public List<T> loadRange(int startPosition, int count) {
-        int endExclusive = Math.min(mList.size(), startPosition + count);
-        return mList.subList(startPosition, endExclusive);
-    }
-}
diff --git a/android/arch/paging/LivePagedListProvider.java b/android/arch/paging/LivePagedListProvider.java
index b7c68dd..07dd84b 100644
--- a/android/arch/paging/LivePagedListProvider.java
+++ b/android/arch/paging/LivePagedListProvider.java
@@ -16,5 +16,133 @@
 
 package android.arch.paging;
 
-abstract public class LivePagedListProvider<K, T> {
-}
\ No newline at end of file
+import android.arch.core.executor.ArchTaskExecutor;
+import android.arch.lifecycle.ComputableLiveData;
+import android.arch.lifecycle.LiveData;
+import android.support.annotation.AnyThread;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.WorkerThread;
+
+/**
+ * Provides a {@code LiveData<PagedList>}, given a means to construct a DataSource.
+ * <p>
+ * Return type for data-loading system of an application or library to produce a
+ * {@code LiveData<PagedList>}, while leaving the details of the paging mechanism up to the
+ * consumer.
+ * <p>
+ * If you're using Room, it can generate a LivePagedListProvider from a query:
+ * <pre>
+ * {@literal @}Dao
+ * interface UserDao {
+ *     {@literal @}Query("SELECT * FROM user ORDER BY lastName ASC")
+ *     public abstract LivePagedListProvider&lt;Integer, User> usersByLastName();
+ * }</pre>
+ * In the above sample, {@code Integer} is used because it is the {@code Key} type of
+ * {@link TiledDataSource}. Currently, Room can only generate a {@code LIMIT}/{@code OFFSET},
+ * position based loader that uses TiledDataSource under the hood, and specifying {@code Integer}
+ * here lets you pass an initial loading position as an integer.
+ * <p>
+ * In the future, Room plans to offer other key types to support paging content with a
+ * {@link KeyedDataSource}.
+ *
+ * @param <Key> Type of input valued used to load data from the DataSource. Must be integer if
+ *             you're using TiledDataSource.
+ * @param <Value> Data type produced by the DataSource, and held by the PagedLists.
+ *
+ * @see PagedListAdapter
+ * @see DataSource
+ * @see PagedList
+ */
+public abstract class LivePagedListProvider<Key, Value> {
+
+    /**
+     * Construct a new data source to be wrapped in a new PagedList, which will be returned
+     * through the LiveData.
+     *
+     * @return The data source.
+     */
+    @WorkerThread
+    protected abstract DataSource<Key, Value> createDataSource();
+
+    /**
+     * Creates a LiveData of PagedLists, given the page size.
+     * <p>
+     * This LiveData can be passed to a {@link PagedListAdapter} to be displayed with a
+     * {@link android.support.v7.widget.RecyclerView}.
+     *
+     * @param initialLoadKey Initial key used to load initial data from the data source.
+     * @param pageSize Page size defining how many items are loaded from a data source at a time.
+     *                 Recommended to be multiple times the size of item displayed at once.
+     *
+     * @return The LiveData of PagedLists.
+     */
+    @AnyThread
+    @NonNull
+    public LiveData<PagedList<Value>> create(@Nullable Key initialLoadKey, int pageSize) {
+        return create(initialLoadKey,
+                new PagedList.Config.Builder()
+                        .setPageSize(pageSize)
+                        .build());
+    }
+
+    /**
+     * Creates a LiveData of PagedLists, given the PagedList.Config.
+     * <p>
+     * This LiveData can be passed to a {@link PagedListAdapter} to be displayed with a
+     * {@link android.support.v7.widget.RecyclerView}.
+     *
+     * @param initialLoadKey Initial key to pass to the data source to initialize data with.
+     * @param config PagedList.Config to use with created PagedLists. This specifies how the
+     *               lists will load data.
+     *
+     * @return The LiveData of PagedLists.
+     */
+    @AnyThread
+    @NonNull
+    public LiveData<PagedList<Value>> create(@Nullable final Key initialLoadKey,
+            final PagedList.Config config) {
+        return new ComputableLiveData<PagedList<Value>>() {
+            @Nullable
+            private PagedList<Value> mList;
+            @Nullable
+            private DataSource<Key, Value> mDataSource;
+
+            private final DataSource.InvalidatedCallback mCallback =
+                    new DataSource.InvalidatedCallback() {
+                @Override
+                public void onInvalidated() {
+                    invalidate();
+                }
+            };
+
+            @Override
+            protected PagedList<Value> compute() {
+                @Nullable Key initializeKey = initialLoadKey;
+                if (mList != null) {
+                    //noinspection unchecked
+                    initializeKey = (Key) mList.getLastKey();
+                }
+
+                do {
+                    if (mDataSource != null) {
+                        mDataSource.removeInvalidatedCallback(mCallback);
+                    }
+
+                    mDataSource = createDataSource();
+                    mDataSource.addInvalidatedCallback(mCallback);
+
+                    mList = new PagedList.Builder<Key, Value>()
+                            .setDataSource(mDataSource)
+                            .setMainThreadExecutor(ArchTaskExecutor.getMainThreadExecutor())
+                            .setBackgroundThreadExecutor(
+                                    ArchTaskExecutor.getIOThreadExecutor())
+                            .setConfig(config)
+                            .setInitialKey(initializeKey)
+                            .build();
+                } while (mList.isDetached());
+                return mList;
+            }
+        }.getLiveData();
+    }
+}
diff --git a/android/arch/paging/NullPaddedList.java b/android/arch/paging/NullPaddedList.java
index 4300030..c7b0b23 100644
--- a/android/arch/paging/NullPaddedList.java
+++ b/android/arch/paging/NullPaddedList.java
@@ -16,11 +16,9 @@
 
 package android.arch.paging;
 
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
 import android.support.annotation.RestrictTo;
 
-import java.util.ArrayList;
+import java.util.AbstractList;
 import java.util.List;
 
 /**
@@ -31,18 +29,11 @@
  * @hide
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public class NullPaddedList<Type> extends PagedList<Type> {
+public class NullPaddedList<Type> extends AbstractList<Type> {
     List<Type> mList;
-    int mTrailingNullCount;
-    int mLeadingNullCount;
-    int mPositionOffset;
-
-    // track the items prepended/appended since the PagedList was initialized
-    int mNumberPrepended;
-    int mNumberAppended;
-
-    NullPaddedList() {
-    }
+    private int mTrailingNullCount;
+    private int mLeadingNullCount;
+    private int mPositionOffset;
 
     @Override
     public String toString() {
@@ -91,20 +82,6 @@
         mPositionOffset = positionOffset;
     }
 
-    /**
-     * Create a copy of the passed NullPaddedList.
-     *
-     * @param other Other list to copy.
-     */
-    NullPaddedList(NullPaddedList<Type> other) {
-        mLeadingNullCount = other.getLeadingNullCount();
-        mList = other.isImmutable() ? other.mList : new ArrayList<>(other.mList);
-        mTrailingNullCount = other.getTrailingNullCount();
-
-        mNumberPrepended = other.getNumberPrepended();
-        mNumberAppended = other.getNumberAppended();
-    }
-
     // --------------- PagedList API ---------------
 
     @Override
@@ -124,46 +101,12 @@
     }
 
     @Override
-    public void loadAround(int index) {
-        // do nothing - immutable, so no fetching will be done
-    }
-
-    @Override
     public final int size() {
         return getLoadedCount() + getLeadingNullCount() + getTrailingNullCount();
     }
 
-    public boolean isImmutable() {
-        return true;
-    }
-
-    @Override
-    public PagedList<Type> snapshot() {
-        if (isImmutable()) {
-            return this;
-        }
-        return new NullPaddedList<>(this);
-    }
-
-    @Override
-    boolean isContiguous() {
-        return true;
-    }
-
-    @Override
-    public void addWeakCallback(@Nullable PagedList<Type> previousSnapshot,
-            @NonNull Callback callback) {
-        // no op, immutable
-    }
-
-    @Override
-    public void removeWeakCallback(Callback callback) {
-        // no op, immutable
-    }
-
     // --------------- Contiguous API ---------------
 
-    @Override
     public int getPositionOffset() {
         return mPositionOffset;
     }
@@ -194,12 +137,4 @@
     public int getTrailingNullCount() {
         return mTrailingNullCount;
     }
-
-    int getNumberPrepended() {
-        return mNumberPrepended;
-    }
-
-    int getNumberAppended() {
-        return mNumberAppended;
-    }
 }
diff --git a/android/arch/paging/NullPaddedListTest.java b/android/arch/paging/NullPaddedListTest.java
deleted file mode 100644
index 0c38485..0000000
--- a/android/arch/paging/NullPaddedListTest.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2017 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.arch.paging;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertSame;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-@RunWith(JUnit4.class)
-public class NullPaddedListTest {
-    @Test
-    public void simple() {
-        List<String> data = Arrays.asList("A", "B", "C", "D", "E", "F");
-        NullPaddedList<String> list = new NullPaddedList<>(
-                2, data.subList(2, 4), 2);
-
-        assertNull(list.get(0));
-        assertNull(list.get(1));
-        assertSame(data.get(2), list.get(2));
-        assertSame(data.get(3), list.get(3));
-        assertNull(list.get(4));
-        assertNull(list.get(5));
-
-        assertEquals(6, list.size());
-        assertEquals(2, list.getLeadingNullCount());
-        assertEquals(2, list.getTrailingNullCount());
-    }
-
-    @Test(expected = IndexOutOfBoundsException.class)
-    public void getEmpty() {
-        NullPaddedList<String> list = new NullPaddedList<>(0, new ArrayList<String>(), 0);
-        list.get(0);
-    }
-
-    @Test(expected = IndexOutOfBoundsException.class)
-    public void getNegative() {
-        NullPaddedList<String> list = new NullPaddedList<>(0, Arrays.asList("a", "b"), 0);
-        list.get(-1);
-    }
-
-    @Test(expected = IndexOutOfBoundsException.class)
-    public void getPastEnd() {
-        NullPaddedList<String> list = new NullPaddedList<>(0, Arrays.asList("a", "b"), 0);
-        list.get(2);
-    }
-}
diff --git a/android/arch/paging/Page.java b/android/arch/paging/Page.java
new file mode 100644
index 0000000..e9890ed
--- /dev/null
+++ b/android/arch/paging/Page.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2017 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.arch.paging;
+
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+import java.util.List;
+
+/**
+ * Immutable class representing a page of data loaded from a DataSource.
+ * <p>
+ * Optionally stores before/after keys for cases where they cannot be computed, but the DataSource
+ * can provide them as part of loading a page.
+ * <p>
+ * A page's list must never be modified.
+ */
+class Page<K, V> {
+    @SuppressWarnings("WeakerAccess")
+    @Nullable
+    public final K beforeKey;
+    @NonNull
+    public final List<V> items;
+    @SuppressWarnings("WeakerAccess")
+    @Nullable
+    public K afterKey;
+
+    Page(@NonNull List<V> items) {
+        this(null, items, null);
+    }
+
+    Page(@Nullable K beforeKey, @NonNull List<V> items, @Nullable K afterKey) {
+        this.beforeKey = beforeKey;
+        this.items = items;
+        this.afterKey = afterKey;
+    }
+}
diff --git a/android/arch/paging/PageArrayList.java b/android/arch/paging/PageArrayList.java
deleted file mode 100644
index b90d055..0000000
--- a/android/arch/paging/PageArrayList.java
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * Copyright (C) 2017 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.arch.paging;
-
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.annotation.RestrictTo;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/** @hide */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-class PageArrayList<T> extends PagedList<T> {
-    // partial list of pages, doesn't include pages below the lowest accessed, or above the highest
-    final ArrayList<List<T>> mPages;
-
-    // to access page at index N, do mPages.get(N - mPageIndexOffset), but do bounds checking first!
-    int mPageIndexOffset;
-
-    final int mPageSize;
-    final int mCount;
-    final int mMaxPageCount;
-
-    PageArrayList(int pageSize, int count) {
-        mPages = new ArrayList<>();
-        mPageSize = pageSize;
-        mCount = count;
-        mMaxPageCount = (mCount + mPageSize - 1) / mPageSize;
-    }
-
-    private PageArrayList(PageArrayList<T> other) {
-        mPages = other.isImmutable() ? other.mPages : new ArrayList<>(other.mPages);
-        mPageIndexOffset = other.mPageIndexOffset;
-        mPageSize = other.mPageSize;
-        mCount = other.size();
-        mMaxPageCount = other.mMaxPageCount;
-    }
-
-    @Override
-    public T get(int index) {
-        if (index < 0 || index >= mCount) {
-            throw new IllegalArgumentException();
-        }
-
-        int localPageIndex = getLocalPageIndex(index);
-
-        List<T> page = getPage(localPageIndex);
-
-        if (page == null) {
-            // page empty
-            return null;
-        }
-
-        return page.get(index % mPageSize);
-    }
-
-    @Nullable
-    private List<T> getPage(int localPageIndex) {
-        if (localPageIndex < 0 || localPageIndex >= mPages.size()) {
-            // page not present
-            return null;
-        }
-
-        return mPages.get(localPageIndex);
-    }
-
-    private int getLocalPageIndex(int index) {
-        return index / mPageSize - mPageIndexOffset;
-    }
-
-    @Override
-    public void loadAround(int index) {
-        // do nothing - immutable, so no fetching will be done
-    }
-
-    @Override
-    public int size() {
-        return mCount;
-    }
-
-    @Override
-    public boolean isImmutable() {
-        return true;
-    }
-
-    boolean hasPage(int pageIndex) {
-        final int localPageIndex = pageIndex - mPageIndexOffset;
-        List<T> page = getPage(localPageIndex);
-        return page != null && page.size() != 0;
-    }
-
-    @Override
-    public PagedList<T> snapshot() {
-        if (isImmutable()) {
-            return this;
-        }
-        return new PageArrayList<>(this);
-    }
-
-    @Override
-    boolean isContiguous() {
-        return false;
-    }
-
-    @Override
-    public void addWeakCallback(@Nullable PagedList<T> previousSnapshot,
-            @NonNull Callback callback) {
-        // no op, immutable
-    }
-
-    @Override
-    public void removeWeakCallback(Callback callback) {
-        // no op, immutable
-    }
-}
diff --git a/android/arch/paging/PageArrayListTest.java b/android/arch/paging/PageArrayListTest.java
deleted file mode 100644
index 135e640..0000000
--- a/android/arch/paging/PageArrayListTest.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2017 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.arch.paging;
-
-import static org.junit.Assert.assertEquals;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import java.util.Arrays;
-import java.util.List;
-
-@RunWith(JUnit4.class)
-public class PageArrayListTest {
-    @Test
-    public void simple() {
-        List<String> data = Arrays.asList("A", "B", "C", "D", "E", "F");
-        PageArrayList<String> list = new PageArrayList<>(2, data.size());
-
-        assertEquals(2, list.mPageSize);
-        assertEquals(data.size(), list.size());
-        assertEquals(3, list.mMaxPageCount);
-
-        for (int i = 0; i < data.size(); i++) {
-            assertEquals(null, list.get(i));
-        }
-        for (int i = 0; i < data.size(); i += list.mPageSize) {
-            list.mPages.add(data.subList(i, i + 2));
-        }
-        for (int i = 0; i < data.size(); i++) {
-            assertEquals(data.get(i), list.get(i));
-        }
-    }
-}
diff --git a/android/arch/paging/PageResult.java b/android/arch/paging/PageResult.java
new file mode 100644
index 0000000..a4090f6
--- /dev/null
+++ b/android/arch/paging/PageResult.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2017 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.arch.paging;
+
+import android.support.annotation.AnyThread;
+import android.support.annotation.MainThread;
+import android.support.annotation.NonNull;
+
+class PageResult<K, V> {
+    static final int INIT = 0;
+
+    // contiguous results
+    static final int APPEND = 1;
+    static final int PREPEND = 2;
+
+    // non-contiguous, tile result
+    static final int TILE = 3;
+
+    public final int type;
+    public final Page<K, V> page;
+    @SuppressWarnings("WeakerAccess")
+    public final int leadingNulls;
+    @SuppressWarnings("WeakerAccess")
+    public final int trailingNulls;
+    @SuppressWarnings("WeakerAccess")
+    public final int positionOffset;
+
+    PageResult(int type, Page<K, V> page, int leadingNulls, int trailingNulls, int positionOffset) {
+        this.type = type;
+        this.page = page;
+        this.leadingNulls = leadingNulls;
+        this.trailingNulls = trailingNulls;
+        this.positionOffset = positionOffset;
+    }
+
+    interface Receiver<K, V> {
+        @AnyThread
+        void postOnPageResult(@NonNull PageResult<K, V> pageResult);
+        @MainThread
+        void onPageResult(@NonNull PageResult<K, V> pageResult);
+    }
+}
diff --git a/android/arch/paging/PagedList.java b/android/arch/paging/PagedList.java
index 6a31b68..51f524a 100644
--- a/android/arch/paging/PagedList.java
+++ b/android/arch/paging/PagedList.java
@@ -20,9 +20,12 @@
 import android.support.annotation.Nullable;
 import android.support.annotation.WorkerThread;
 
+import java.lang.ref.WeakReference;
 import java.util.AbstractList;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
  * Lazy loading list that pages in content from a {@link DataSource}.
@@ -90,9 +93,30 @@
  * @param <T> The type of the entries in the list.
  */
 public abstract class PagedList<T> extends AbstractList<T> {
-    // Since we currently rely on implementation details of two implementations,
-    // prevent external subclassing
-    PagedList() {
+    @NonNull
+    final Executor mMainThreadExecutor;
+    @NonNull
+    final Executor mBackgroundThreadExecutor;
+    @NonNull
+    final Config mConfig;
+    @NonNull
+    final PagedStorage<?, T> mStorage;
+
+    int mLastLoad = 0;
+    T mLastItem = null;
+
+    private final AtomicBoolean mDetached = new AtomicBoolean(false);
+
+    protected final ArrayList<WeakReference<Callback>> mCallbacks = new ArrayList<>();
+
+    PagedList(@NonNull PagedStorage<?, T> storage,
+            @NonNull Executor mainThreadExecutor,
+            @NonNull Executor backgroundThreadExecutor,
+            @NonNull Config config) {
+        mStorage = storage;
+        mMainThreadExecutor = mainThreadExecutor;
+        mBackgroundThreadExecutor = backgroundThreadExecutor;
+        mConfig = config;
     }
 
     /**
@@ -117,7 +141,7 @@
             @NonNull Executor backgroundThreadExecutor,
             @NonNull Config config,
             @Nullable K key) {
-        if (dataSource.isContiguous() || !config.mEnablePlaceholders) {
+        if (dataSource.isContiguous() || !config.enablePlaceholders) {
             if (!dataSource.isContiguous()) {
                 //noinspection unchecked
                 dataSource = (DataSource<K, T>) ((TiledDataSource<T>) dataSource).getAsContiguous();
@@ -280,7 +304,13 @@
      */
     @Override
     @Nullable
-    public abstract T get(int index);
+    public T get(int index) {
+        T item = mStorage.get(index);
+        if (item != null) {
+            mLastItem = item;
+        }
+        return item;
+    }
 
 
     /**
@@ -288,7 +318,10 @@
      *
      * @param index Index at which to load.
      */
-    public abstract void loadAround(int index);
+    public void loadAround(int index) {
+        mLastLoad = index + getPositionOffset();
+        loadAroundInternal(index);
+    }
 
 
     /**
@@ -297,7 +330,9 @@
      * @return Current total size of the list.
      */
     @Override
-    public abstract int size();
+    public int size() {
+        return mStorage.size();
+    }
 
     /**
      * Returns whether the list is immutable. Immutable lists may not become mutable again, and may
@@ -305,19 +340,39 @@
      *
      * @return True if the PagedList is immutable.
      */
-    public abstract boolean isImmutable();
+    @SuppressWarnings("WeakerAccess")
+    public boolean isImmutable() {
+        return isDetached();
+    }
 
     /**
      * Returns an immutable snapshot of the PagedList. If this PagedList is already
      * immutable, it will be returned.
      *
-     * @return Immutable snapshot of PagedList, which may be the PagedList itself.
+     * @return Immutable snapshot of PagedList data.
      */
-    public abstract List<T> snapshot();
+    @NonNull
+    public List<T> snapshot() {
+        if (isImmutable()) {
+            return this;
+        }
+
+        return new SnapshotPagedList<>(this);
+    }
 
     abstract boolean isContiguous();
 
     /**
+     * Return the Config used to construct this PagedList.
+     *
+     * @return the Config of this PagedList
+     */
+    @NonNull
+    public Config getConfig() {
+        return mConfig;
+    }
+
+    /**
      * Return the key for the position passed most recently to {@link #loadAround(int)}.
      * <p>
      * When a PagedList is invalidated, you can pass the key returned by this function to initialize
@@ -328,9 +383,7 @@
      * @return Key of position most recently passed to {@link #loadAround(int)}.
      */
     @Nullable
-    public Object getLastKey() {
-        return null;
-    }
+    public abstract Object getLastKey();
 
     /**
      * True if the PagedList has detached the DataSource it was loading from, and will no longer
@@ -338,8 +391,9 @@
      *
      * @return True if the data source is detached.
      */
+    @SuppressWarnings("WeakerAccess")
     public boolean isDetached() {
-        return true;
+        return mDetached.get();
     }
 
     /**
@@ -349,7 +403,9 @@
      * signal to stop loading. The PagedList will continue to present existing data, but will not
      * initiate new loads.
      */
+    @SuppressWarnings("WeakerAccess")
     public void detach() {
+        mDetached.set(true);
     }
 
     /**
@@ -361,7 +417,7 @@
      * If the DataSource is a {@link KeyedDataSource}, and thus doesn't use positions, returns 0.
      */
     public int getPositionOffset() {
-        return 0;
+        return mStorage.getPositionOffset();
     }
 
     /**
@@ -385,16 +441,69 @@
      * @param callback         Callback to dispatch to.
      * @see #removeWeakCallback(Callback)
      */
-    public abstract void addWeakCallback(@Nullable PagedList<T> previousSnapshot,
-            @NonNull Callback callback);
+    @SuppressWarnings("WeakerAccess")
+    public void addWeakCallback(@Nullable List<T> previousSnapshot, @NonNull Callback callback) {
+        if (previousSnapshot != null && previousSnapshot != this) {
+            PagedList<T> storageSnapshot = (PagedList<T>) previousSnapshot;
+            //noinspection unchecked
+            dispatchUpdatesSinceSnapshot(storageSnapshot, callback);
+        }
 
+        // first, clean up any empty weak refs
+        for (int i = mCallbacks.size() - 1; i >= 0; i--) {
+            Callback currentCallback = mCallbacks.get(i).get();
+            if (currentCallback == null) {
+                mCallbacks.remove(i);
+            }
+        }
+
+        // then add the new one
+        mCallbacks.add(new WeakReference<>(callback));
+    }
     /**
      * Removes a previously added callback.
      *
      * @param callback Callback, previously added.
-     * @see #addWeakCallback(PagedList, Callback)
+     * @see #addWeakCallback(List, Callback)
      */
-    public abstract void removeWeakCallback(Callback callback);
+    @SuppressWarnings("WeakerAccess")
+    public void removeWeakCallback(@NonNull Callback callback) {
+        for (int i = mCallbacks.size() - 1; i >= 0; i--) {
+            Callback currentCallback = mCallbacks.get(i).get();
+            if (currentCallback == null || currentCallback == callback) {
+                // found callback, or empty weak ref
+                mCallbacks.remove(i);
+            }
+        }
+    }
+
+    void notifyInserted(int position, int count) {
+        if (count != 0) {
+            for (int i = mCallbacks.size() - 1; i >= 0; i--) {
+                Callback callback = mCallbacks.get(i).get();
+                if (callback != null) {
+                    callback.onInserted(position, count);
+                }
+            }
+        }
+    }
+
+    void notifyChanged(int position, int count) {
+        if (count != 0) {
+            for (int i = mCallbacks.size() - 1; i >= 0; i--) {
+                Callback callback = mCallbacks.get(i).get();
+
+                if (callback != null) {
+                    callback.onChanged(position, count);
+                }
+            }
+        }
+    }
+
+    abstract void dispatchUpdatesSinceSnapshot(@NonNull PagedList<T> snapshot,
+            @NonNull Callback callback);
+
+    abstract void loadAroundInternal(int index);
 
     /**
      * Callback signaling when content is loaded into the list.
@@ -442,17 +551,41 @@
      * {@link Builder#setPageSize(int)}, which defines number of items loaded at a time}.
      */
     public static class Config {
-        final int mPageSize;
-        final int mPrefetchDistance;
-        final boolean mEnablePlaceholders;
-        final int mInitialLoadSizeHint;
+        /**
+         * Size of each page loaded by the PagedList.
+         */
+        public final int pageSize;
+
+        /**
+         * Prefetch distance which defines how far ahead to load.
+         * <p>
+         * If this value is set to 50, the paged list will attempt to load 50 items in advance of
+         * data that's already been accessed.
+         *
+         * @see PagedList#loadAround(int)
+         */
+        @SuppressWarnings("WeakerAccess")
+        public final int prefetchDistance;
+
+        /**
+         * Defines whether the PagedList may display null placeholders, if the DataSource provides
+         * them.
+         */
+        @SuppressWarnings("WeakerAccess")
+        public final boolean enablePlaceholders;
+
+        /**
+         * Size hint for initial load of PagedList, often larger than a regular page.
+         */
+        @SuppressWarnings("WeakerAccess")
+        public final int initialLoadSizeHint;
 
         private Config(int pageSize, int prefetchDistance,
                 boolean enablePlaceholders, int initialLoadSizeHint) {
-            mPageSize = pageSize;
-            mPrefetchDistance = prefetchDistance;
-            mEnablePlaceholders = enablePlaceholders;
-            mInitialLoadSizeHint = initialLoadSizeHint;
+            this.pageSize = pageSize;
+            this.prefetchDistance = prefetchDistance;
+            this.enablePlaceholders = enablePlaceholders;
+            this.initialLoadSizeHint = initialLoadSizeHint;
         }
 
         /**
@@ -545,10 +678,15 @@
              * Defines how many items to load when first load occurs, if you are using a
              * {@link KeyedDataSource}.
              * <p>
-             * If you are using an {@link TiledDataSource}, this value is currently ignored.
-             * Otherwise, this value will be passed to
-             * {@link KeyedDataSource#loadInitial(int)} to load a (typically) larger amount
-             * of data on first load.
+             * This value is typically larger than page size, so on first load data there's a large
+             * enough range of content loaded to cover small scrolls.
+             * <p>
+             * If used with a {@link TiledDataSource}, this value is rounded to the nearest number
+             * of pages, with a minimum of two pages, and loaded with a single call to
+             * {@link TiledDataSource#loadRange(int, int)}.
+             * <p>
+             * If used with a {@link KeyedDataSource}, this value will be passed to
+             * {@link KeyedDataSource#loadInitial(int)}.
              * <p>
              * If not set, defaults to three times page size.
              *
diff --git a/android/arch/paging/PagedListAdapterHelper.java b/android/arch/paging/PagedListAdapterHelper.java
index c7b61d9..abcff41 100644
--- a/android/arch/paging/PagedListAdapterHelper.java
+++ b/android/arch/paging/PagedListAdapterHelper.java
@@ -25,8 +25,6 @@
 import android.support.v7.util.ListUpdateCallback;
 import android.support.v7.widget.RecyclerView;
 
-import java.util.List;
-
 /**
  * Helper object for mapping a {@link PagedList} into a
  * {@link android.support.v7.widget.RecyclerView.Adapter RecyclerView.Adapter}.
@@ -120,15 +118,15 @@
  * @param <T> Type of the PagedLists this helper will receive.
  */
 public class PagedListAdapterHelper<T> {
+    // updateCallback notifications must only be notified *after* new data and item count are stored
+    // this ensures Adapter#notifyItemRangeInserted etc are accessing the new data
     private final ListUpdateCallback mUpdateCallback;
     private final ListAdapterConfig<T> mConfig;
 
-    // true if our listener is detached from mList, because it's been snapshotted
-    private boolean mUpdateScheduled;
-
     private boolean mIsContiguous;
 
-    private PagedList<T> mList;
+    private PagedList<T> mPagedList;
+    private PagedList<T> mSnapshot;
 
     // Max generation of currently scheduled runnable
     private int mMaxScheduledGeneration;
@@ -182,12 +180,17 @@
     @SuppressWarnings("WeakerAccess")
     @Nullable
     public T getItem(int index) {
-        if (mList == null) {
-            throw new IndexOutOfBoundsException("Item count is zero, getItem() call is invalid");
+        if (mPagedList == null) {
+            if (mSnapshot == null) {
+                throw new IndexOutOfBoundsException(
+                        "Item count is zero, getItem() call is invalid");
+            } else {
+                return mSnapshot.get(index);
+            }
         }
 
-        mList.loadAround(index);
-        return mList.get(index);
+        mPagedList.loadAround(index);
+        return mPagedList.get(index);
     }
 
     /**
@@ -198,7 +201,11 @@
      */
     @SuppressWarnings("WeakerAccess")
     public int getItemCount() {
-        return mList == null ? 0 : mList.size();
+        if (mPagedList != null) {
+            return mPagedList.size();
+        }
+
+        return mSnapshot == null ? 0 : mSnapshot.size();
     }
 
     /**
@@ -212,7 +219,7 @@
      */
     public void setList(final PagedList<T> pagedList) {
         if (pagedList != null) {
-            if (mList == null) {
+            if (mPagedList == null && mSnapshot == null) {
                 mIsContiguous = pagedList.isContiguous();
             } else {
                 if (pagedList.isContiguous() != mIsContiguous) {
@@ -222,7 +229,7 @@
             }
         }
 
-        if (pagedList == mList) {
+        if (pagedList == mPagedList) {
             // nothing to do
             return;
         }
@@ -231,49 +238,55 @@
         final int runGeneration = ++mMaxScheduledGeneration;
 
         if (pagedList == null) {
-            mUpdateCallback.onRemoved(0, mList.size());
-            mList.removeWeakCallback(mPagedListCallback);
-            mList = null;
+            int removedCount = getItemCount();
+            if (mPagedList != null) {
+                mPagedList.removeWeakCallback(mPagedListCallback);
+                mPagedList = null;
+            } else if (mSnapshot != null) {
+                mSnapshot = null;
+            }
+            // dispatch update callback after updating mPagedList/mSnapshot
+            mUpdateCallback.onRemoved(0, removedCount);
             return;
         }
 
-        if (mList == null) {
+        if (mPagedList == null && mSnapshot == null) {
             // fast simple first insert
-            mUpdateCallback.onInserted(0, pagedList.size());
-            mList = pagedList;
+            mPagedList = pagedList;
             pagedList.addWeakCallback(null, mPagedListCallback);
+
+            // dispatch update callback after updating mPagedList/mSnapshot
+            mUpdateCallback.onInserted(0, pagedList.size());
             return;
         }
 
-        if (!mList.isImmutable()) {
+        if (mPagedList != null) {
             // first update scheduled on this list, so capture mPages as a snapshot, removing
             // callbacks so we don't have resolve updates against a moving target
-            mList.removeWeakCallback(mPagedListCallback);
-            mList = (PagedList<T>) mList.snapshot();
+            mPagedList.removeWeakCallback(mPagedListCallback);
+            mSnapshot = (PagedList<T>) mPagedList.snapshot();
+            mPagedList = null;
         }
 
-        final PagedList<T> oldSnapshot = mList;
-        final List<T> newSnapshot = pagedList.snapshot();
-        mUpdateScheduled = true;
+        if (mSnapshot == null || mPagedList != null) {
+            throw new IllegalStateException("must be in snapshot state to diff");
+        }
+
+        final PagedList<T> oldSnapshot = mSnapshot;
+        final PagedList<T> newSnapshot = (PagedList<T>) pagedList.snapshot();
         mConfig.getBackgroundThreadExecutor().execute(new Runnable() {
             @Override
             public void run() {
                 final DiffUtil.DiffResult result;
-                if (mIsContiguous) {
-                    result = ContiguousDiffHelper.computeDiff(
-                            (NullPaddedList<T>) oldSnapshot, (NullPaddedList<T>) newSnapshot,
-                            mConfig.getDiffCallback(), true);
-                } else {
-                    result = SparseDiffHelper.computeDiff(
-                            (PageArrayList<T>) oldSnapshot, (PageArrayList<T>) newSnapshot,
-                            mConfig.getDiffCallback(), true);
-                }
+                result = PagedStorageDiffHelper.computeDiff(
+                        oldSnapshot.mStorage,
+                        newSnapshot.mStorage,
+                        mConfig.getDiffCallback());
 
                 mConfig.getMainThreadExecutor().execute(new Runnable() {
                     @Override
                     public void run() {
                         if (mMaxScheduledGeneration == runGeneration) {
-                            mUpdateScheduled = false;
                             latchPagedList(pagedList, newSnapshot, result);
                         }
                     }
@@ -283,16 +296,21 @@
     }
 
     private void latchPagedList(
-            PagedList<T> newList, List<T> diffSnapshot,
+            PagedList<T> newList, PagedList<T> diffSnapshot,
             DiffUtil.DiffResult diffResult) {
-        if (mIsContiguous) {
-            ContiguousDiffHelper.dispatchDiff(mUpdateCallback,
-                    (NullPaddedList<T>) mList, (ContiguousPagedList<T>) newList, diffResult);
-        } else {
-            SparseDiffHelper.dispatchDiff(mUpdateCallback, diffResult);
+        if (mSnapshot == null || mPagedList != null) {
+            throw new IllegalStateException("must be in snapshot state to apply diff");
         }
-        mList = newList;
-        newList.addWeakCallback((PagedList<T>) diffSnapshot, mPagedListCallback);
+
+        PagedList<T> previousSnapshot = mSnapshot;
+        mPagedList = newList;
+        mSnapshot = null;
+
+        // dispatch update callback after updating mPagedList/mSnapshot
+        PagedStorageDiffHelper.dispatchDiff(mUpdateCallback,
+                previousSnapshot.mStorage, newList.mStorage, diffResult);
+
+        newList.addWeakCallback(diffSnapshot, mPagedListCallback);
     }
 
     /**
@@ -307,6 +325,9 @@
     @SuppressWarnings("WeakerAccess")
     @Nullable
     public PagedList<T> getCurrentList() {
-        return mList;
+        if (mSnapshot != null) {
+            return mSnapshot;
+        }
+        return mPagedList;
     }
 }
diff --git a/android/arch/paging/PagedListAdapterHelperTest.java b/android/arch/paging/PagedListAdapterHelperTest.java
deleted file mode 100644
index 3518540..0000000
--- a/android/arch/paging/PagedListAdapterHelperTest.java
+++ /dev/null
@@ -1,308 +0,0 @@
-/*
- * Copyright (C) 2017 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.arch.paging;
-
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertNotNull;
-import static junit.framework.Assert.assertNull;
-import static junit.framework.Assert.assertTrue;
-
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.verifyZeroInteractions;
-
-import android.support.annotation.NonNull;
-import android.support.test.filters.SmallTest;
-import android.support.v7.recyclerview.extensions.DiffCallback;
-import android.support.v7.recyclerview.extensions.ListAdapterConfig;
-import android.support.v7.util.ListUpdateCallback;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import java.util.ArrayList;
-import java.util.List;
-
-@SmallTest
-@RunWith(JUnit4.class)
-public class PagedListAdapterHelperTest {
-    private TestExecutor mMainThread = new TestExecutor();
-    private TestExecutor mDiffThread = new TestExecutor();
-    private TestExecutor mPageLoadingThread = new TestExecutor();
-
-    private static final ArrayList<String> ALPHABET_LIST = new ArrayList<>();
-    static {
-        for (int i = 0; i < 26; i++) {
-            ALPHABET_LIST.add("" + 'a' + i);
-        }
-    }
-
-    private static final DiffCallback<String> STRING_DIFF_CALLBACK = new DiffCallback<String>() {
-        @Override
-        public boolean areItemsTheSame(@NonNull String oldItem, @NonNull String newItem) {
-            return oldItem.equals(newItem);
-        }
-
-        @Override
-        public boolean areContentsTheSame(@NonNull String oldItem, @NonNull String newItem) {
-            return oldItem.equals(newItem);
-        }
-    };
-
-    private static final ListUpdateCallback IGNORE_CALLBACK = new ListUpdateCallback() {
-        @Override
-        public void onInserted(int position, int count) {
-        }
-
-        @Override
-        public void onRemoved(int position, int count) {
-        }
-
-        @Override
-        public void onMoved(int fromPosition, int toPosition) {
-        }
-
-        @Override
-        public void onChanged(int position, int count, Object payload) {
-        }
-    };
-
-
-    private <T> PagedListAdapterHelper<T> createHelper(
-            ListUpdateCallback listUpdateCallback, DiffCallback<T> diffCallback) {
-        return new PagedListAdapterHelper<T>(listUpdateCallback,
-                new ListAdapterConfig.Builder<T>()
-                        .setDiffCallback(diffCallback)
-                        .setMainThreadExecutor(mMainThread)
-                        .setBackgroundThreadExecutor(mDiffThread)
-                        .build());
-    }
-
-    private <V> PagedList<V> createPagedListFromListAndPos(
-            PagedList.Config config, List<V> data, int initialKey) {
-        return new PagedList.Builder<Integer, V>()
-                .setInitialKey(initialKey)
-                .setConfig(config)
-                .setMainThreadExecutor(mMainThread)
-                .setBackgroundThreadExecutor(mPageLoadingThread)
-                .setDataSource(new ListDataSource<>(data))
-                .build();
-    }
-
-    @Test
-    public void initialState() {
-        ListUpdateCallback callback = mock(ListUpdateCallback.class);
-        PagedListAdapterHelper<String> helper = createHelper(callback, STRING_DIFF_CALLBACK);
-        assertEquals(null, helper.getCurrentList());
-        assertEquals(0, helper.getItemCount());
-        verifyZeroInteractions(callback);
-    }
-
-    @Test
-    public void setFullList() {
-        ListUpdateCallback callback = mock(ListUpdateCallback.class);
-        PagedListAdapterHelper<String> helper = createHelper(callback, STRING_DIFF_CALLBACK);
-        helper.setList(new StringPagedList(0, 0, "a", "b"));
-
-        assertEquals(2, helper.getItemCount());
-        assertEquals("a", helper.getItem(0));
-        assertEquals("b", helper.getItem(1));
-
-        verify(callback).onInserted(0, 2);
-        verifyNoMoreInteractions(callback);
-        drain();
-        verifyNoMoreInteractions(callback);
-    }
-
-    @Test(expected = IndexOutOfBoundsException.class)
-    public void getEmpty() {
-        PagedListAdapterHelper<String> helper = createHelper(IGNORE_CALLBACK, STRING_DIFF_CALLBACK);
-        helper.getItem(0);
-    }
-
-    @Test(expected = IndexOutOfBoundsException.class)
-    public void getNegative() {
-        PagedListAdapterHelper<String> helper = createHelper(IGNORE_CALLBACK, STRING_DIFF_CALLBACK);
-        helper.setList(new StringPagedList(0, 0, "a", "b"));
-        helper.getItem(-1);
-    }
-
-    @Test(expected = IndexOutOfBoundsException.class)
-    public void getPastEnd() {
-        PagedListAdapterHelper<String> helper = createHelper(IGNORE_CALLBACK, STRING_DIFF_CALLBACK);
-        helper.setList(new StringPagedList(0, 0, "a", "b"));
-        helper.getItem(2);
-    }
-
-    @Test
-    public void simpleStatic() {
-        ListUpdateCallback callback = mock(ListUpdateCallback.class);
-        PagedListAdapterHelper<String> helper = createHelper(callback, STRING_DIFF_CALLBACK);
-
-        assertEquals(0, helper.getItemCount());
-
-        helper.setList(new StringPagedList(2, 2, "a", "b"));
-
-        verify(callback).onInserted(0, 6);
-        verifyNoMoreInteractions(callback);
-        assertEquals(6, helper.getItemCount());
-
-        assertNull(helper.getItem(0));
-        assertNull(helper.getItem(1));
-        assertEquals("a", helper.getItem(2));
-        assertEquals("b", helper.getItem(3));
-        assertNull(helper.getItem(4));
-        assertNull(helper.getItem(5));
-    }
-
-    @Test
-    public void pagingInContent() {
-        PagedList.Config config = new PagedList.Config.Builder()
-                .setInitialLoadSizeHint(4)
-                .setPageSize(2)
-                .setPrefetchDistance(2)
-                .build();
-
-        final ListUpdateCallback callback = mock(ListUpdateCallback.class);
-        PagedListAdapterHelper<String> helper = createHelper(callback, STRING_DIFF_CALLBACK);
-
-        helper.setList(createPagedListFromListAndPos(config, ALPHABET_LIST, 2));
-        verify(callback).onInserted(0, ALPHABET_LIST.size());
-        verifyNoMoreInteractions(callback);
-        drain();
-        verifyNoMoreInteractions(callback);
-
-        // get without triggering prefetch...
-        helper.getItem(1);
-        verifyNoMoreInteractions(callback);
-        drain();
-        verifyNoMoreInteractions(callback);
-
-        // get triggering prefetch...
-        helper.getItem(2);
-        verifyNoMoreInteractions(callback);
-        drain();
-        verify(callback).onChanged(4, 2, null);
-        verifyNoMoreInteractions(callback);
-
-        // get with no data loaded nearby...
-        helper.getItem(12);
-        verifyNoMoreInteractions(callback);
-        drain();
-        verify(callback).onChanged(10, 2, null);
-        verify(callback).onChanged(12, 2, null);
-        verify(callback).onChanged(14, 2, null);
-        verifyNoMoreInteractions(callback);
-
-        // finally, clear
-        helper.setList(null);
-        verify(callback).onRemoved(0, 26);
-        drain();
-        verifyNoMoreInteractions(callback);
-    }
-
-    @Test
-    public void simpleSwap() {
-        // Page size large enough to load
-        PagedList.Config config = new PagedList.Config.Builder()
-                .setPageSize(50)
-                .build();
-
-        final ListUpdateCallback callback = mock(ListUpdateCallback.class);
-        PagedListAdapterHelper<String> helper = createHelper(callback, STRING_DIFF_CALLBACK);
-
-        // initial list missing one item (immediate)
-        helper.setList(createPagedListFromListAndPos(config, ALPHABET_LIST.subList(0, 25), 0));
-        verify(callback).onInserted(0, 25);
-        verifyNoMoreInteractions(callback);
-        assertEquals(helper.getItemCount(), 25);
-        drain();
-        verifyNoMoreInteractions(callback);
-
-        // pass second list with full data
-        helper.setList(createPagedListFromListAndPos(config, ALPHABET_LIST, 0));
-        verifyNoMoreInteractions(callback);
-        drain();
-        verify(callback).onInserted(25, 1);
-        verifyNoMoreInteractions(callback);
-        assertEquals(helper.getItemCount(), 26);
-
-        // finally, clear (immediate)
-        helper.setList(null);
-        verify(callback).onRemoved(0, 26);
-        verifyNoMoreInteractions(callback);
-        drain();
-        verifyNoMoreInteractions(callback);
-    }
-
-    @Test
-    public void newPageWhileDiffing() {
-        PagedList.Config config = new PagedList.Config.Builder()
-                .setInitialLoadSizeHint(4)
-                .setPageSize(2)
-                .setPrefetchDistance(2)
-                .build();
-
-        final ListUpdateCallback callback = mock(ListUpdateCallback.class);
-        PagedListAdapterHelper<String> helper = createHelper(callback, STRING_DIFF_CALLBACK);
-
-        helper.setList(createPagedListFromListAndPos(config, ALPHABET_LIST, 2));
-        verify(callback).onInserted(0, ALPHABET_LIST.size());
-        verifyNoMoreInteractions(callback);
-        drain();
-        verifyNoMoreInteractions(callback);
-        assertNotNull(helper.getCurrentList());
-        assertFalse(helper.getCurrentList().isImmutable());
-
-        // trigger page loading
-        helper.getItem(10);
-        helper.setList(createPagedListFromListAndPos(config, ALPHABET_LIST, 2));
-        verifyNoMoreInteractions(callback);
-
-        // drain page fetching, but list became immutable, page will be ignored
-        drainExceptDiffThread();
-        verifyNoMoreInteractions(callback);
-        assertNotNull(helper.getCurrentList());
-        assertTrue(helper.getCurrentList().isImmutable());
-
-        // finally full drain, which signals nothing, since 1st pagedlist == 2nd pagedlist
-        drain();
-        verifyNoMoreInteractions(callback);
-        assertNotNull(helper.getCurrentList());
-        assertFalse(helper.getCurrentList().isImmutable());
-    }
-
-    private void drainExceptDiffThread() {
-        boolean executed;
-        do {
-            executed = mPageLoadingThread.executeAll();
-            executed |= mMainThread.executeAll();
-        } while (executed);
-    }
-
-    private void drain() {
-        boolean executed;
-        do {
-            executed = mPageLoadingThread.executeAll();
-            executed |= mDiffThread.executeAll();
-            executed |= mMainThread.executeAll();
-        } while (executed);
-    }
-}
diff --git a/android/arch/paging/PagedStorage.java b/android/arch/paging/PagedStorage.java
new file mode 100644
index 0000000..7f91290
--- /dev/null
+++ b/android/arch/paging/PagedStorage.java
@@ -0,0 +1,433 @@
+/*
+ * Copyright (C) 2017 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.arch.paging;
+
+import android.support.annotation.NonNull;
+
+import java.util.AbstractList;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+final class PagedStorage<K, V> extends AbstractList<V> {
+    // Always set
+    private int mLeadingNullCount;
+    /**
+     * List of pages in storage.
+     *
+     * Two storage modes:
+     *
+     * Contiguous - all content in mPages is valid and loaded, but may return false from isTiled().
+     *     Safe to access any item in any page.
+     *
+     * Non-contiguous - mPages may have nulls or a placeholder page, isTiled() always returns true.
+     *     mPages may have nulls, or placeholder (empty) pages while content is loading.
+     */
+    private final ArrayList<Page<K, V>> mPages;
+    private int mTrailingNullCount;
+
+    private int mPositionOffset;
+    /**
+     * Number of items represented by {@link #mPages}. If tiling is enabled, unloaded items in
+     * {@link #mPages} may be null, but this value still counts them.
+     */
+    private int mStorageCount;
+
+    // If mPageSize > 0, tiling is enabled, 'mPages' may have gaps, and leadingPages is set
+    private int mPageSize;
+
+    private int mNumberPrepended;
+    private int mNumberAppended;
+
+    // only used in tiling case
+    private Page<K, V> mPlaceholderPage;
+
+    PagedStorage() {
+        mLeadingNullCount = 0;
+        mPages = new ArrayList<>();
+        mTrailingNullCount = 0;
+        mPositionOffset = 0;
+        mStorageCount = 0;
+        mPageSize = 1;
+        mNumberPrepended = 0;
+        mNumberAppended = 0;
+    }
+
+    PagedStorage(int leadingNulls, Page<K, V> page, int trailingNulls) {
+        this();
+        init(leadingNulls, page, trailingNulls, 0);
+    }
+
+    private PagedStorage(PagedStorage<K, V> other) {
+        mLeadingNullCount = other.mLeadingNullCount;
+        mPages = new ArrayList<>(other.mPages);
+        mTrailingNullCount = other.mTrailingNullCount;
+        mPositionOffset = other.mPositionOffset;
+        mStorageCount = other.mStorageCount;
+        mPageSize = other.mPageSize;
+        mNumberPrepended = other.mNumberPrepended;
+        mNumberAppended = other.mNumberAppended;
+
+        // preserve placeholder page so we can locate placeholder pages if needed later
+        mPlaceholderPage = other.mPlaceholderPage;
+    }
+
+    PagedStorage<K, V> snapshot() {
+        return new PagedStorage<>(this);
+    }
+
+    private void init(int leadingNulls, Page<K, V> page, int trailingNulls, int positionOffset) {
+        mLeadingNullCount = leadingNulls;
+        mPages.clear();
+        mPages.add(page);
+        mTrailingNullCount = trailingNulls;
+
+        mPositionOffset = positionOffset;
+        mStorageCount = page.items.size();
+
+        // initialized as tiled. There may be 3 nulls, 2 items, but we still call this tiled
+        // even if it will break if nulls convert.
+        mPageSize = page.items.size();
+
+        mNumberPrepended = 0;
+        mNumberAppended = 0;
+    }
+
+    void init(int leadingNulls, Page<K, V> page, int trailingNulls, int positionOffset,
+            @NonNull Callback callback) {
+        init(leadingNulls, page, trailingNulls, positionOffset);
+        callback.onInitialized(size());
+    }
+
+    @Override
+    public V get(int i) {
+        if (i < 0 || i >= size()) {
+            throw new IndexOutOfBoundsException("Index: " + i + ", Size: " + size());
+        }
+
+        // is it definitely outside 'mPages'?
+        int localIndex = i - mLeadingNullCount;
+        if (localIndex < 0 || localIndex >= mStorageCount) {
+            return null;
+        }
+
+        int localPageIndex;
+        int pageInternalIndex;
+
+        if (isTiled()) {
+            // it's inside mPages, and we're tiled. Jump to correct tile.
+            localPageIndex = localIndex / mPageSize;
+            pageInternalIndex = localIndex % mPageSize;
+        } else {
+            // it's inside mPages, but page sizes aren't regular. Walk to correct tile.
+            // Pages can only be null while tiled, so accessing page count is safe.
+            pageInternalIndex = localIndex;
+            final int localPageCount = mPages.size();
+            for (localPageIndex = 0; localPageIndex < localPageCount; localPageIndex++) {
+                int pageSize = mPages.get(localPageIndex).items.size();
+                if (pageSize > pageInternalIndex) {
+                    // stop, found the page
+                    break;
+                }
+                pageInternalIndex -= pageSize;
+            }
+        }
+
+        Page<?, V> page = mPages.get(localPageIndex);
+        if (page == null || page.items.size() == 0) {
+            // can only occur in tiled case, with untouched inner/placeholder pages
+            return null;
+        }
+        return page.items.get(pageInternalIndex);
+    }
+
+    /**
+     * Returns true if all pages are the same size, except for the last, which may be smaller
+     */
+    boolean isTiled() {
+        return mPageSize > 0;
+    }
+
+    int getLeadingNullCount() {
+        return mLeadingNullCount;
+    }
+
+    int getTrailingNullCount() {
+        return mTrailingNullCount;
+    }
+
+    int getStorageCount() {
+        return mStorageCount;
+    }
+
+    int getNumberAppended() {
+        return mNumberAppended;
+    }
+
+    int getNumberPrepended() {
+        return mNumberPrepended;
+    }
+
+    int getPageCount() {
+        return mPages.size();
+    }
+
+    interface Callback {
+        void onInitialized(int count);
+        void onPagePrepended(int leadingNulls, int changed, int added);
+        void onPageAppended(int endPosition, int changed, int added);
+        void onPagePlaceholderInserted(int pageIndex);
+        void onPageInserted(int start, int count);
+    }
+
+    int getPositionOffset() {
+        return mPositionOffset;
+    }
+
+    @Override
+    public int size() {
+        return mLeadingNullCount + mStorageCount + mTrailingNullCount;
+    }
+
+    int computeLeadingNulls() {
+        int total = mLeadingNullCount;
+        final int pageCount = mPages.size();
+        for (int i = 0; i < pageCount; i++) {
+            Page page = mPages.get(i);
+            if (page != null && page != mPlaceholderPage) {
+                break;
+            }
+            total += mPageSize;
+        }
+        return total;
+    }
+
+    int computeTrailingNulls() {
+        int total = mTrailingNullCount;
+        for (int i = mPages.size() - 1; i >= 0; i--) {
+            Page page = mPages.get(i);
+            if (page != null && page != mPlaceholderPage) {
+                break;
+            }
+            total += mPageSize;
+        }
+        return total;
+    }
+
+    // ---------------- Contiguous API -------------------
+
+    V getFirstContiguousItem() {
+        // safe to access first page's first item here:
+        // If contiguous, mPages can't be empty, can't hold null Pages, and items can't be empty
+        return mPages.get(0).items.get(0);
+    }
+
+    V getLastContiguousItem() {
+        // safe to access last page's last item here:
+        // If contiguous, mPages can't be empty, can't hold null Pages, and items can't be empty
+        Page<K, V> page = mPages.get(mPages.size() - 1);
+        return page.items.get(page.items.size() - 1);
+    }
+
+    public void prependPage(@NonNull Page<K, V> page, @NonNull Callback callback) {
+        final int count = page.items.size();
+        if (count == 0) {
+            // Nothing returned from source, stop loading in this direction
+            return;
+        }
+        if (mPageSize > 0 && count != mPageSize) {
+            if (mPages.size() == 1 && count > mPageSize) {
+                // prepending to a single item - update current page size to that of 'inner' page
+                mPageSize = count;
+            } else {
+                // no longer tiled
+                mPageSize = -1;
+            }
+        }
+
+        mPages.add(0, page);
+        mStorageCount += count;
+
+        final int changedCount = Math.min(mLeadingNullCount, count);
+        final int addedCount = count - changedCount;
+
+        if (changedCount != 0) {
+            mLeadingNullCount -= changedCount;
+        }
+        mPositionOffset -= addedCount;
+        mNumberPrepended += count;
+
+        callback.onPagePrepended(mLeadingNullCount, changedCount, addedCount);
+    }
+
+    public void appendPage(@NonNull Page<K, V> page, @NonNull Callback callback) {
+        final int count = page.items.size();
+        if (count == 0) {
+            // Nothing returned from source, stop loading in this direction
+            return;
+        }
+
+        if (mPageSize > 0) {
+            // if the previous page was smaller than mPageSize,
+            // or if this page is larger than the previous, disable tiling
+            if (mPages.get(mPages.size() - 1).items.size() != mPageSize
+                    || count > mPageSize) {
+                mPageSize = -1;
+            }
+        }
+
+        mPages.add(page);
+        mStorageCount += count;
+
+        final int changedCount = Math.min(mTrailingNullCount, count);
+        final int addedCount = count - changedCount;
+
+        if (changedCount != 0) {
+            mTrailingNullCount -= changedCount;
+        }
+        mNumberAppended += count;
+        callback.onPageAppended(mLeadingNullCount + mStorageCount - count,
+                changedCount, addedCount);
+    }
+
+    // ------------------ Non-Contiguous API (tiling required) ----------------------
+
+    public void insertPage(int position, @NonNull Page<K, V> page, Callback callback) {
+        final int newPageSize = page.items.size();
+        if (newPageSize != mPageSize) {
+            // differing page size is OK in 2 cases, when the page is being added:
+            // 1) to the end (in which case, ignore new smaller size)
+            // 2) only the last page has been added so far (in which case, adopt new bigger size)
+
+            int size = size();
+            boolean addingLastPage = position == (size - size % mPageSize)
+                    && newPageSize < mPageSize;
+            boolean onlyEndPagePresent = mTrailingNullCount == 0 && mPages.size() == 1
+                    && newPageSize > mPageSize;
+
+            // OK only if existing single page, and it's the last one
+            if (!onlyEndPagePresent && !addingLastPage) {
+                throw new IllegalArgumentException("page introduces incorrect tiling");
+            }
+            if (onlyEndPagePresent) {
+                mPageSize = newPageSize;
+            }
+        }
+
+        int pageIndex = position / mPageSize;
+
+        allocatePageRange(pageIndex, pageIndex);
+
+        int localPageIndex = pageIndex - mLeadingNullCount / mPageSize;
+
+        Page<K, V> oldPage = mPages.get(localPageIndex);
+        if (oldPage != null && oldPage != mPlaceholderPage) {
+            throw new IllegalArgumentException(
+                    "Invalid position " + position + ": data already loaded");
+        }
+        mPages.set(localPageIndex, page);
+        callback.onPageInserted(position, page.items.size());
+    }
+
+    private Page<K, V> getPlaceholderPage() {
+        if (mPlaceholderPage == null) {
+            @SuppressWarnings("unchecked")
+            List<V> list = Collections.emptyList();
+            mPlaceholderPage = new Page<>(null, list, null);
+        }
+        return mPlaceholderPage;
+    }
+
+    private void allocatePageRange(final int minimumPage, final int maximumPage) {
+        int leadingNullPages = mLeadingNullCount / mPageSize;
+
+        if (minimumPage < leadingNullPages) {
+            for (int i = 0; i < leadingNullPages - minimumPage; i++) {
+                mPages.add(0, null);
+            }
+            int newStorageAllocated = (leadingNullPages - minimumPage) * mPageSize;
+            mStorageCount += newStorageAllocated;
+            mLeadingNullCount -= newStorageAllocated;
+
+            leadingNullPages = minimumPage;
+        }
+        if (maximumPage >= leadingNullPages + mPages.size()) {
+            int newStorageAllocated = Math.min(mTrailingNullCount,
+                    (maximumPage + 1 - (leadingNullPages + mPages.size())) * mPageSize);
+            for (int i = mPages.size(); i <= maximumPage - leadingNullPages; i++) {
+                mPages.add(mPages.size(), null);
+            }
+            mStorageCount += newStorageAllocated;
+            mTrailingNullCount -= newStorageAllocated;
+        }
+    }
+
+    public void allocatePlaceholders(int index, int prefetchDistance,
+            int pageSize, Callback callback) {
+        if (pageSize != mPageSize) {
+            if (pageSize < mPageSize) {
+                throw new IllegalArgumentException("Page size cannot be reduced");
+            }
+            if (mPages.size() != 1 || mTrailingNullCount != 0) {
+                // not in single, last page allocated case - can't change page size
+                throw new IllegalArgumentException(
+                        "Page size can change only if last page is only one present");
+            }
+            mPageSize = pageSize;
+        }
+
+        final int maxPageCount = (size() + mPageSize - 1) / mPageSize;
+        int minimumPage = Math.max((index - prefetchDistance) / mPageSize, 0);
+        int maximumPage = Math.min((index + prefetchDistance) / mPageSize, maxPageCount - 1);
+
+        allocatePageRange(minimumPage, maximumPage);
+        int leadingNullPages = mLeadingNullCount / mPageSize;
+        for (int pageIndex = minimumPage; pageIndex <= maximumPage; pageIndex++) {
+            int localPageIndex = pageIndex - leadingNullPages;
+            if (mPages.get(localPageIndex) == null) {
+                mPages.set(localPageIndex, getPlaceholderPage());
+                callback.onPagePlaceholderInserted(pageIndex);
+            }
+        }
+    }
+
+    public boolean hasPage(int pageSize, int index) {
+        // NOTE: we pass pageSize here to avoid in case mPageSize
+        // not fully initialized (when last page only one loaded)
+        int leadingNullPages = mLeadingNullCount / pageSize;
+
+        if (index < leadingNullPages || index >= leadingNullPages + mPages.size()) {
+            return false;
+        }
+
+        Page<K, V> page = mPages.get(index - leadingNullPages);
+
+        return page != null && page != mPlaceholderPage;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder ret = new StringBuilder("leading " + mLeadingNullCount
+                + ", storage " + mStorageCount
+                + ", trailing " + getTrailingNullCount());
+
+        for (int i = 0; i < mPages.size(); i++) {
+            ret.append(" ").append(mPages.get(i));
+        }
+        return ret.toString();
+    }
+}
diff --git a/android/arch/paging/ContiguousDiffHelper.java b/android/arch/paging/PagedStorageDiffHelper.java
similarity index 74%
rename from android/arch/paging/ContiguousDiffHelper.java
rename to android/arch/paging/PagedStorageDiffHelper.java
index 7dd194b..6fc7039 100644
--- a/android/arch/paging/ContiguousDiffHelper.java
+++ b/android/arch/paging/PagedStorageDiffHelper.java
@@ -16,36 +16,31 @@
 
 package android.arch.paging;
 
-import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
-import android.support.annotation.RestrictTo;
 import android.support.v7.recyclerview.extensions.DiffCallback;
 import android.support.v7.util.DiffUtil;
 import android.support.v7.util.ListUpdateCallback;
 
-/** @hide */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-class ContiguousDiffHelper {
-    private ContiguousDiffHelper() {
+class PagedStorageDiffHelper {
+    private PagedStorageDiffHelper() {
     }
 
-    @NonNull
     static <T> DiffUtil.DiffResult computeDiff(
-            final NullPaddedList<T> oldList, final NullPaddedList<T> newList,
-            final DiffCallback<T> diffCallback, boolean detectMoves) {
+            final PagedStorage<?, T> oldList,
+            final PagedStorage<?, T> newList,
+            final DiffCallback<T> diffCallback) {
+        final int oldOffset = oldList.computeLeadingNulls();
+        final int newOffset = newList.computeLeadingNulls();
 
-        if (!oldList.isImmutable()) {
-            throw new IllegalArgumentException("list must be immutable to safely perform diff");
-        }
-        if (!newList.isImmutable()) {
-            throw new IllegalArgumentException("list must be immutable to safely perform diff");
-        }
+        final int oldSize = oldList.size() - oldOffset - oldList.computeTrailingNulls();
+        final int newSize = newList.size() - newOffset - newList.computeTrailingNulls();
+
         return DiffUtil.calculateDiff(new DiffUtil.Callback() {
             @Nullable
             @Override
             public Object getChangePayload(int oldItemPosition, int newItemPosition) {
-                T oldItem = oldList.mList.get(oldItemPosition);
-                T newItem = newList.mList.get(newItemPosition);
+                T oldItem = oldList.get(oldItemPosition + oldOffset);
+                T newItem = newList.get(newItemPosition + newList.getLeadingNullCount());
                 if (oldItem == null || newItem == null) {
                     return null;
                 }
@@ -54,21 +49,22 @@
 
             @Override
             public int getOldListSize() {
-                return oldList.mList.size();
+                return oldSize;
             }
 
             @Override
             public int getNewListSize() {
-                return newList.mList.size();
+                return newSize;
             }
 
             @Override
             public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
-                T oldItem = oldList.mList.get(oldItemPosition);
-                T newItem = newList.mList.get(newItemPosition);
+                T oldItem = oldList.get(oldItemPosition + oldOffset);
+                T newItem = newList.get(newItemPosition + newList.getLeadingNullCount());
                 if (oldItem == newItem) {
                     return true;
                 }
+                //noinspection SimplifiableIfStatement
                 if (oldItem == null || newItem == null) {
                     return false;
                 }
@@ -77,18 +73,19 @@
 
             @Override
             public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
-                T oldItem = oldList.mList.get(oldItemPosition);
-                T newItem = newList.mList.get(newItemPosition);
+                T oldItem = oldList.get(oldItemPosition + oldOffset);
+                T newItem = newList.get(newItemPosition + newList.getLeadingNullCount());
                 if (oldItem == newItem) {
                     return true;
                 }
+                //noinspection SimplifiableIfStatement
                 if (oldItem == null || newItem == null) {
                     return false;
                 }
 
                 return diffCallback.areContentsTheSame(oldItem, newItem);
             }
-        }, detectMoves);
+        }, true);
     }
 
     private static class OffsettingListUpdateCallback implements ListUpdateCallback {
@@ -134,21 +131,25 @@
      * immediately after dispatching this diff.
      */
     static <T> void dispatchDiff(ListUpdateCallback callback,
-            final NullPaddedList<T> oldList, final NullPaddedList<T> newList,
+            final PagedStorage<?, T> oldList,
+            final PagedStorage<?, T> newList,
             final DiffUtil.DiffResult diffResult) {
 
-        if (oldList.getLeadingNullCount() == 0
-                && oldList.getTrailingNullCount() == 0
-                && newList.getLeadingNullCount() == 0
-                && newList.getTrailingNullCount() == 0) {
+        final int trailingOld = oldList.computeTrailingNulls();
+        final int trailingNew = newList.computeTrailingNulls();
+        final int leadingOld = oldList.computeLeadingNulls();
+        final int leadingNew = newList.computeLeadingNulls();
+
+        if (trailingOld == 0
+                && trailingNew == 0
+                && leadingOld == 0
+                && leadingNew == 0) {
             // Simple case, dispatch & return
             diffResult.dispatchUpdatesTo(callback);
             return;
         }
 
         // First, remove or insert trailing nulls
-        final int trailingOld = oldList.getTrailingNullCount();
-        final int trailingNew = newList.getTrailingNullCount();
         if (trailingOld > trailingNew) {
             int count = trailingOld - trailingNew;
             callback.onRemoved(oldList.size() - count, count);
@@ -157,8 +158,6 @@
         }
 
         // Second, remove or insert leading nulls
-        final int leadingOld = oldList.getLeadingNullCount();
-        final int leadingNew = newList.getLeadingNullCount();
         if (leadingOld > leadingNew) {
             callback.onRemoved(0, leadingOld - leadingNew);
         } else if (leadingOld < leadingNew) {
diff --git a/android/arch/paging/PositionalDataSource.java b/android/arch/paging/PositionalDataSource.java
index deb51e9..c538cb6 100644
--- a/android/arch/paging/PositionalDataSource.java
+++ b/android/arch/paging/PositionalDataSource.java
@@ -42,6 +42,17 @@
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
 public abstract class PositionalDataSource<Value> extends ContiguousDataSource<Integer, Value> {
+
+    /**
+     * Number of items that this DataSource can provide in total, or COUNT_UNDEFINED.
+     *
+     * @return number of items that this DataSource can provide in total, or COUNT_UNDEFINED
+     * if difficult or undesired to compute.
+     */
+    public int countItems() {
+        return COUNT_UNDEFINED;
+    }
+
     @Nullable
     @Override
     List<Value> loadAfterImpl(int currentEndIndex, @NonNull Value currentEndItem, int pageSize) {
@@ -55,16 +66,7 @@
         return loadBefore(currentBeginIndex - 1, pageSize);
     }
 
-
-    /**
-     * Load initial data, starting after the passed position.
-     *
-     * @param position Index just before the data to be loaded.
-     * @param initialLoadSize Suggested number of items to load.
-     * @return List of initial items, representing data starting at position. Null if the
-     *         DataSource is no longer valid, and should not be queried again.
-     * @hide
-     */
+    /** @hide */
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     @WorkerThread
     @Nullable
@@ -118,6 +120,9 @@
 
     @Override
     Integer getKey(int position, Value item) {
+        if (position < 0) {
+            return null;
+        }
         return position;
     }
 }
diff --git a/android/arch/paging/SnapshotPagedList.java b/android/arch/paging/SnapshotPagedList.java
new file mode 100644
index 0000000..7e965a0
--- /dev/null
+++ b/android/arch/paging/SnapshotPagedList.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2017 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.arch.paging;
+
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+class SnapshotPagedList<T> extends PagedList<T> {
+    private final boolean mContiguous;
+    private final Object mLastKey;
+
+    SnapshotPagedList(@NonNull PagedList<T> pagedList) {
+        super(pagedList.mStorage.snapshot(),
+                pagedList.mMainThreadExecutor,
+                pagedList.mBackgroundThreadExecutor,
+                pagedList.mConfig);
+        mContiguous = pagedList.isContiguous();
+        mLastKey = pagedList.getLastKey();
+    }
+
+    @Override
+    public boolean isImmutable() {
+        return true;
+    }
+
+    @Override
+    public boolean isDetached() {
+        return true;
+    }
+
+    @Override
+    boolean isContiguous() {
+        return mContiguous;
+    }
+
+    @Nullable
+    @Override
+    public Object getLastKey() {
+        return mLastKey;
+    }
+
+    @Override
+    void dispatchUpdatesSinceSnapshot(@NonNull PagedList<T> storageSnapshot,
+            @NonNull Callback callback) {
+    }
+
+    @Override
+    void loadAroundInternal(int index) {
+    }
+}
diff --git a/android/arch/paging/SparseDiffHelper.java b/android/arch/paging/SparseDiffHelper.java
deleted file mode 100644
index fe47897..0000000
--- a/android/arch/paging/SparseDiffHelper.java
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Copyright (C) 2017 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.arch.paging;
-
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.annotation.RestrictTo;
-import android.support.v7.recyclerview.extensions.DiffCallback;
-import android.support.v7.util.DiffUtil;
-import android.support.v7.util.ListUpdateCallback;
-
-/** @hide */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-class SparseDiffHelper {
-    private SparseDiffHelper() {
-    }
-
-    @NonNull
-    static <T> DiffUtil.DiffResult computeDiff(
-            final PageArrayList<T> oldList, final PageArrayList<T> newList,
-            final DiffCallback<T> diffCallback, boolean detectMoves) {
-
-        if (!oldList.isImmutable()) {
-            throw new IllegalArgumentException("list must be immutable to safely perform diff");
-        }
-        if (!newList.isImmutable()) {
-            throw new IllegalArgumentException("list must be immutable to safely perform diff");
-        }
-        return DiffUtil.calculateDiff(new DiffUtil.Callback() {
-            @Nullable
-            @Override
-            public Object getChangePayload(int oldItemPosition, int newItemPosition) {
-                T oldItem = oldList.get(oldItemPosition);
-                T newItem = newList.get(newItemPosition);
-                if (oldItem == null || newItem == null) {
-                    return null;
-                }
-                return diffCallback.getChangePayload(oldItem, newItem);
-            }
-
-            @Override
-            public int getOldListSize() {
-                return oldList.size();
-            }
-
-            @Override
-            public int getNewListSize() {
-                return newList.size();
-            }
-
-            @Override
-            public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
-                T oldItem = oldList.get(oldItemPosition);
-                T newItem = newList.get(newItemPosition);
-                if (oldItem == newItem) {
-                    return true;
-                }
-                if (oldItem == null || newItem == null) {
-                    return false;
-                }
-                return diffCallback.areItemsTheSame(oldItem, newItem);
-            }
-
-            @Override
-            public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
-                T oldItem = oldList.get(oldItemPosition);
-                T newItem = newList.get(newItemPosition);
-                if (oldItem == newItem) {
-                    return true;
-                }
-                if (oldItem == null || newItem == null) {
-                    return false;
-                }
-
-                return diffCallback.areContentsTheSame(oldItem, newItem);
-            }
-        }, detectMoves);
-    }
-
-    static <T> void dispatchDiff(ListUpdateCallback callback,
-            final DiffUtil.DiffResult diffResult) {
-        // Simple case, dispatch & return
-        diffResult.dispatchUpdatesTo(callback);
-    }
-}
diff --git a/android/arch/paging/TestExecutor.java b/android/arch/paging/TestExecutor.java
deleted file mode 100644
index 30809c3..0000000
--- a/android/arch/paging/TestExecutor.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2017 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.arch.paging;
-
-import android.support.annotation.NonNull;
-
-import java.util.LinkedList;
-import java.util.Queue;
-import java.util.concurrent.Executor;
-
-public class TestExecutor implements Executor {
-    private Queue<Runnable> mTasks = new LinkedList<>();
-
-    @Override
-    public void execute(@NonNull Runnable command) {
-        mTasks.add(command);
-    }
-
-    boolean executeAll() {
-        boolean consumed = !mTasks.isEmpty();
-        Runnable task;
-        while ((task = mTasks.poll()) != null) {
-            task.run();
-        }
-        return consumed;
-    }
-}
diff --git a/android/arch/paging/TiledDataSource.java b/android/arch/paging/TiledDataSource.java
index 36be423..61dead3 100644
--- a/android/arch/paging/TiledDataSource.java
+++ b/android/arch/paging/TiledDataSource.java
@@ -19,6 +19,7 @@
 import android.support.annotation.Nullable;
 import android.support.annotation.WorkerThread;
 
+import java.util.Collections;
 import java.util.List;
 
 /**
@@ -92,7 +93,6 @@
      * @return Number of items this DataSource can provide. Must be <code>0</code> or greater.
      */
     @WorkerThread
-    @Override
     public abstract int countItems();
 
     @Override
@@ -118,7 +118,61 @@
     @WorkerThread
     public abstract List<Type> loadRange(int startPosition, int count);
 
-    final List<Type> loadRangeWrapper(int startPosition, int count) {
+    /**
+     * blocking, and splits pages
+     */
+    void loadRangeInitial(int startPosition, int count, int pageSize, int itemCount,
+            PageResult.Receiver<Integer, Type> receiver) {
+
+        if (itemCount == 0) {
+            // no data to load, just immediately return empty
+            receiver.onPageResult(new PageResult<>(
+                    PageResult.INIT, new Page<Integer, Type>(Collections.<Type>emptyList()),
+                    0, 0, startPosition));
+            return;
+        }
+
+
+        List<Type> list = loadRangeWrapper(startPosition, count);
+
+        count = Math.min(count, itemCount - startPosition);
+
+        if (list == null) {
+            // invalid data, pass to receiver
+            receiver.onPageResult(new PageResult<Integer, Type>(
+                    PageResult.INIT, null, 0, 0, startPosition));
+            return;
+        }
+
+        if (list.size() != count) {
+            throw new IllegalStateException("Invalid list, requested size: " + count
+                    + ", returned size: " + list.size());
+        }
+
+        // emit the results as multiple pages
+        int pageCount = (count + (pageSize - 1)) / pageSize;
+        for (int i = 0; i < pageCount; i++) {
+            int beginInclusive = i * pageSize;
+            int endExclusive = Math.min(count, (i + 1) * pageSize);
+
+            Page<Integer, Type> page = new Page<>(list.subList(beginInclusive, endExclusive));
+
+            int leadingNulls = startPosition + beginInclusive;
+            int trailingNulls = itemCount - leadingNulls - page.items.size();
+            receiver.onPageResult(new PageResult<>(
+                    PageResult.INIT, page, leadingNulls, trailingNulls, 0));
+        }
+    }
+
+    void loadRange(int startPosition, int count, PageResult.Receiver<Integer, Type> receiver) {
+        List<Type> list = loadRangeWrapper(startPosition, count);
+
+        Page<Integer, Type> page = list != null ? new Page<Integer, Type>(list) : null;
+        receiver.postOnPageResult(new PageResult<>(
+                PageResult.TILE, page, startPosition, 0, 0));
+    }
+
+    private List<Type> loadRangeWrapper(int startPosition, int count) {
         if (isInvalid()) {
             return null;
         }
diff --git a/android/arch/paging/TiledPagedList.java b/android/arch/paging/TiledPagedList.java
index a2fc064..934a0dd 100644
--- a/android/arch/paging/TiledPagedList.java
+++ b/android/arch/paging/TiledPagedList.java
@@ -16,214 +16,100 @@
 
 package android.arch.paging;
 
+import android.support.annotation.AnyThread;
+import android.support.annotation.MainThread;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
-import android.support.annotation.RestrictTo;
 import android.support.annotation.WorkerThread;
 
-import java.lang.ref.WeakReference;
-import java.util.AbstractList;
-import java.util.ArrayList;
-import java.util.List;
 import java.util.concurrent.Executor;
-import java.util.concurrent.atomic.AtomicBoolean;
 
-/** @hide */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-class TiledPagedList<T> extends PageArrayList<T> {
+class TiledPagedList<T> extends PagedList<T>
+        implements PagedStorage.Callback {
 
     private final TiledDataSource<T> mDataSource;
-    private final Executor mMainThreadExecutor;
-    private final Executor mBackgroundThreadExecutor;
-    private final Config mConfig;
 
-    @SuppressWarnings("MismatchedQueryAndUpdateOfCollection")
-    private final List<T> mLoadingPlaceholder = new AbstractList<T>() {
+    @SuppressWarnings("unchecked")
+    private final PagedStorage<Integer, T> mKeyedStorage = (PagedStorage<Integer, T>) mStorage;
+
+    private final PageResult.Receiver<Integer, T> mReceiver =
+            new PageResult.Receiver<Integer, T>() {
+        @AnyThread
         @Override
-        public T get(int i) {
-            return null;
+        public void postOnPageResult(@NonNull final PageResult<Integer, T> pageResult) {
+            // NOTE: if we're already on main thread, this can delay page receive by a frame
+            mMainThreadExecutor.execute(new Runnable() {
+                @Override
+                public void run() {
+                    onPageResult(pageResult);
+                }
+            });
         }
 
+        @MainThread
         @Override
-        public int size() {
-            return 0;
+        public void onPageResult(@NonNull PageResult<Integer, T> pageResult) {
+            if (pageResult.page == null) {
+                detach();
+                return;
+            }
+
+            if (isDetached()) {
+                // No op, have detached
+                return;
+            }
+
+            if (mStorage.getPageCount() == 0) {
+                mKeyedStorage.init(
+                        pageResult.leadingNulls, pageResult.page, pageResult.trailingNulls,
+                        pageResult.positionOffset, TiledPagedList.this);
+            } else {
+                mKeyedStorage.insertPage(pageResult.leadingNulls, pageResult.page,
+                        TiledPagedList.this);
+            }
         }
     };
 
-    private int mLastLoad = -1;
-
-    private AtomicBoolean mDetached = new AtomicBoolean(false);
-
-    private ArrayList<WeakReference<Callback>> mCallbacks = new ArrayList<>();
-
     @WorkerThread
     TiledPagedList(@NonNull TiledDataSource<T> dataSource,
             @NonNull Executor mainThreadExecutor,
             @NonNull Executor backgroundThreadExecutor,
-            Config config,
+            @NonNull Config config,
             int position) {
-        super(config.mPageSize, dataSource.countItems());
-
+        super(new PagedStorage<Integer, T>(),
+                mainThreadExecutor, backgroundThreadExecutor, config);
         mDataSource = dataSource;
-        mMainThreadExecutor = mainThreadExecutor;
-        mBackgroundThreadExecutor = backgroundThreadExecutor;
-        mConfig = config;
 
-        position = Math.min(Math.max(0, position), mCount);
+        final int pageSize = mConfig.pageSize;
 
-        int firstPage = position / mPageSize;
-        List<T> firstPageData = dataSource.loadRangeWrapper(firstPage * mPageSize, mPageSize);
-        if (firstPageData != null) {
-            mPageIndexOffset = firstPage;
-            mPages.add(firstPageData);
-            mLastLoad = position;
-        } else {
-            detach();
-            return;
-        }
+        final int itemCount = mDataSource.countItems();
+        final int firstLoadSize = Math.min(itemCount,
+                (Math.max(mConfig.initialLoadSizeHint / pageSize, 2)) * pageSize);
+        final int firstLoadPosition = computeFirstLoadPosition(
+                position, firstLoadSize, pageSize, itemCount);
 
-        int secondPage = (position % mPageSize < mPageSize / 2) ? firstPage - 1 : firstPage + 1;
-        if (secondPage < 0 || secondPage > mMaxPageCount) {
-            // no second page to load
-            return;
-        }
-        List<T> secondPageData = dataSource.loadRangeWrapper(secondPage * mPageSize, mPageSize);
-        if (secondPageData != null) {
-            boolean before = secondPage < firstPage;
-            mPages.add(before ? 0 : 1, secondPageData);
-            if (before) {
-                mPageIndexOffset--;
-            }
-            return;
-        }
-        detach();
+        mDataSource.loadRangeInitial(firstLoadPosition, firstLoadSize, pageSize,
+                itemCount, mReceiver);
+    }
+
+    static int computeFirstLoadPosition(int position, int firstLoadSize, int pageSize, int size) {
+        int idealStart = position - firstLoadSize / 2;
+
+        int roundedPageStart = Math.round(idealStart / pageSize) * pageSize;
+
+        // minimum start position is 0
+        roundedPageStart = Math.max(0, roundedPageStart);
+
+        // maximum start pos is that which will encompass end of list
+        int maximumLoadPage = ((size - firstLoadSize + pageSize - 1) / pageSize) * pageSize;
+        roundedPageStart = Math.min(maximumLoadPage, roundedPageStart);
+
+        return roundedPageStart;
     }
 
     @Override
-    public void loadAround(int index) {
-        mLastLoad = index;
-
-        int minimumPage = Math.max((index - mConfig.mPrefetchDistance) / mPageSize, 0);
-        int maximumPage = Math.min((index + mConfig.mPrefetchDistance) / mPageSize,
-                mMaxPageCount - 1);
-
-        if (minimumPage < mPageIndexOffset) {
-            for (int i = 0; i < mPageIndexOffset - minimumPage; i++) {
-                mPages.add(0, null);
-            }
-            mPageIndexOffset = minimumPage;
-        }
-        if (maximumPage >= mPageIndexOffset + mPages.size()) {
-            for (int i = mPages.size(); i <= maximumPage - mPageIndexOffset; i++) {
-                mPages.add(mPages.size(), null);
-            }
-        }
-        for (int i = minimumPage; i <= maximumPage; i++) {
-            scheduleLoadPage(i);
-        }
-    }
-
-    private void scheduleLoadPage(final int pageIndex) {
-        final int localPageIndex = pageIndex - mPageIndexOffset;
-
-        if (mPages.get(localPageIndex) != null) {
-            // page is present in list, and non-null - don't need to load
-            return;
-        }
-        mPages.set(localPageIndex, mLoadingPlaceholder);
-
-        mBackgroundThreadExecutor.execute(new Runnable() {
-            @Override
-            public void run() {
-                if (mDetached.get()) {
-                    return;
-                }
-                final List<T> data = mDataSource.loadRangeWrapper(
-                        pageIndex * mPageSize, mPageSize);
-                if (data != null) {
-                    mMainThreadExecutor.execute(new Runnable() {
-                        @Override
-                        public void run() {
-                            if (mDetached.get()) {
-                                return;
-                            }
-                            loadPageImpl(pageIndex, data);
-                        }
-                    });
-                } else {
-                    detach();
-                }
-            }
-        });
-
-    }
-
-    private void loadPageImpl(int pageIndex, List<T> data) {
-        int localPageIndex = pageIndex - mPageIndexOffset;
-
-        if (mPages.get(localPageIndex) != mLoadingPlaceholder) {
-            throw new IllegalStateException("Data inserted before requested.");
-        }
-        mPages.set(localPageIndex, data);
-        for (WeakReference<Callback> weakRef : mCallbacks) {
-            Callback callback = weakRef.get();
-            if (callback != null) {
-                callback.onChanged(pageIndex * mPageSize, data.size());
-            }
-        }
-    }
-
-    @Override
-    public boolean isImmutable() {
-        // TODO: consider counting loaded pages, return true if mLoadedPages == mMaxPageCount
-        // Note: could at some point want to support growing past max count, or grow dynamically
-        return isDetached();
-    }
-
-    @Override
-    public void addWeakCallback(@Nullable PagedList<T> previousSnapshot,
-            @NonNull Callback callback) {
-        PageArrayList<T> snapshot = (PageArrayList<T>) previousSnapshot;
-        if (snapshot != this && snapshot != null) {
-            // loop through each page and signal the callback for any pages that are present now,
-            // but not in the snapshot.
-            for (int i = 0; i < mPages.size(); i++) {
-                int pageIndex = i + mPageIndexOffset;
-                int pageCount = 0;
-                // count number of consecutive pages that were added since the snapshot...
-                while (pageCount < mPages.size()
-                        && hasPage(pageIndex + pageCount)
-                        && !snapshot.hasPage(pageIndex + pageCount)) {
-                    pageCount++;
-                }
-                // and signal them all at once to the callback
-                if (pageCount > 0) {
-                    callback.onChanged(pageIndex * mPageSize, mPageSize * pageCount);
-                    i += pageCount - 1;
-                }
-            }
-        }
-        mCallbacks.add(new WeakReference<>(callback));
-    }
-
-    @Override
-    public void removeWeakCallback(@NonNull Callback callback) {
-        for (int i = mCallbacks.size() - 1; i >= 0; i--) {
-            Callback currentCallback = mCallbacks.get(i).get();
-            if (currentCallback == null || currentCallback == callback) {
-                mCallbacks.remove(i);
-            }
-        }
-    }
-
-    @Override
-    public boolean isDetached() {
-        return mDetached.get();
-    }
-
-    @Override
-    public void detach() {
-        mDetached.set(true);
+    boolean isContiguous() {
+        return false;
     }
 
     @Nullable
@@ -231,4 +117,72 @@
     public Object getLastKey() {
         return mLastLoad;
     }
+
+    @Override
+    protected void dispatchUpdatesSinceSnapshot(@NonNull PagedList<T> pagedListSnapshot,
+            @NonNull Callback callback) {
+        //noinspection UnnecessaryLocalVariable
+        final PagedStorage<?, T> snapshot = pagedListSnapshot.mStorage;
+
+        // loop through each page and signal the callback for any pages that are present now,
+        // but not in the snapshot.
+        final int pageSize = mConfig.pageSize;
+        final int leadingNullPages = mStorage.getLeadingNullCount() / pageSize;
+        final int pageCount = mStorage.getPageCount();
+        for (int i = 0; i < pageCount; i++) {
+            int pageIndex = i + leadingNullPages;
+            int updatedPages = 0;
+            // count number of consecutive pages that were added since the snapshot...
+            while (updatedPages < mStorage.getPageCount()
+                    && mStorage.hasPage(pageSize, pageIndex + updatedPages)
+                    && !snapshot.hasPage(pageSize, pageIndex + updatedPages)) {
+                updatedPages++;
+            }
+            // and signal them all at once to the callback
+            if (updatedPages > 0) {
+                callback.onChanged(pageIndex * pageSize, pageSize * updatedPages);
+                i += updatedPages - 1;
+            }
+        }
+    }
+
+    @Override
+    protected void loadAroundInternal(int index) {
+        mStorage.allocatePlaceholders(index, mConfig.prefetchDistance, mConfig.pageSize, this);
+    }
+
+    @Override
+    public void onInitialized(int count) {
+        notifyInserted(0, count);
+    }
+
+    @Override
+    public void onPagePrepended(int leadingNulls, int changed, int added) {
+        throw new IllegalStateException("Contiguous callback on TiledPagedList");
+    }
+
+    @Override
+    public void onPageAppended(int endPosition, int changed, int added) {
+        throw new IllegalStateException("Contiguous callback on TiledPagedList");
+    }
+
+    @Override
+    public void onPagePlaceholderInserted(final int pageIndex) {
+        // placeholder means initialize a load
+        mBackgroundThreadExecutor.execute(new Runnable() {
+            @Override
+            public void run() {
+                if (isDetached()) {
+                    return;
+                }
+                final int pageSize = mConfig.pageSize;
+                mDataSource.loadRange(pageIndex * pageSize, pageSize, mReceiver);
+            }
+        });
+    }
+
+    @Override
+    public void onPageInserted(int start, int count) {
+        notifyChanged(start, count);
+    }
 }
diff --git a/android/arch/paging/TiledPagedListTest.java b/android/arch/paging/TiledPagedListTest.java
deleted file mode 100644
index 4ad02e1..0000000
--- a/android/arch/paging/TiledPagedListTest.java
+++ /dev/null
@@ -1,288 +0,0 @@
-/*
- * Copyright (C) 2017 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.arch.paging;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.verifyZeroInteractions;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-@RunWith(JUnit4.class)
-public class TiledPagedListTest {
-
-    private TestExecutor mMainThread = new TestExecutor();
-    private TestExecutor mBackgroundThread = new TestExecutor();
-
-    private static final ArrayList<Item> ITEMS = new ArrayList<>();
-
-    static {
-        for (int i = 0; i < 45; i++) {
-            ITEMS.add(new Item(i));
-        }
-    }
-
-    // use a page size that's not an even divisor of ITEMS.size() to test end conditions
-    private static final int PAGE_SIZE = 10;
-
-    private static class Item {
-        private Item(int position) {
-            this.position = position;
-            this.name = "Item " + position;
-        }
-
-        @SuppressWarnings("WeakerAccess")
-        public final int position;
-        public final String name;
-
-        @Override
-        public String toString() {
-            return name;
-        }
-    }
-
-    private static class TestTiledSource extends TiledDataSource<Item> {
-        @Override
-        public int countItems() {
-            return ITEMS.size();
-        }
-
-        @Override
-        public List<Item> loadRange(int startPosition, int count) {
-            int endPosition = Math.min(ITEMS.size(), startPosition + count);
-            return ITEMS.subList(startPosition, endPosition);
-        }
-    }
-
-    private void verifyRange(PageArrayList<Item> list, Integer... loadedPages) {
-        List<Integer> loadedPageList = Arrays.asList(loadedPages);
-        assertEquals(ITEMS.size(), list.size());
-        for (int i = 0; i < list.size(); i++) {
-            if (loadedPageList.contains(i / PAGE_SIZE)) {
-                assertSame(ITEMS.get(i), list.get(i));
-            } else {
-                assertEquals(null, list.get(i));
-            }
-        }
-    }
-    private TiledPagedList<Item> createTiledPagedList(int loadPosition) {
-        return createTiledPagedList(loadPosition, PAGE_SIZE);
-    }
-
-    private TiledPagedList<Item> createTiledPagedList(int loadPosition, int prefetchDistance) {
-        TestTiledSource source = new TestTiledSource();
-        return new TiledPagedList<>(
-                source, mMainThread, mBackgroundThread,
-                new PagedList.Config.Builder()
-                        .setPageSize(PAGE_SIZE)
-                        .setPrefetchDistance(prefetchDistance)
-                        .build(),
-                loadPosition);
-    }
-
-    @Test
-    public void initialLoad() {
-        TiledPagedList<Item> pagedList = createTiledPagedList(0);
-        verifyRange(pagedList, 0);
-    }
-
-    @Test
-    public void initialLoad_end() {
-        TiledPagedList<Item> pagedList = createTiledPagedList(44);
-        verifyRange(pagedList, 3, 4);
-    }
-
-    @Test
-    public void initialLoad_multiple() {
-        TiledPagedList<Item> pagedList = createTiledPagedList(9);
-        verifyRange(pagedList, 0, 1);
-    }
-
-    @Test
-    public void initialLoad_offset() {
-        TiledPagedList<Item> pagedList = createTiledPagedList(41);
-        verifyRange(pagedList, 3, 4);
-    }
-
-    @Test
-    public void append() {
-        TiledPagedList<Item> pagedList = createTiledPagedList(0);
-        PagedList.Callback callback = mock(PagedList.Callback.class);
-        pagedList.addWeakCallback(null, callback);
-        verifyRange(pagedList, 0);
-        verifyZeroInteractions(callback);
-
-        pagedList.loadAround(5);
-        drain();
-
-        verifyRange(pagedList, 0, 1);
-        verify(callback).onChanged(10, 10);
-        verifyNoMoreInteractions(callback);
-    }
-
-    @Test
-    public void prepend() {
-        TiledPagedList<Item> pagedList = createTiledPagedList(44);
-        PagedList.Callback callback = mock(PagedList.Callback.class);
-        pagedList.addWeakCallback(null, callback);
-        verifyRange(pagedList, 3, 4);
-        verifyZeroInteractions(callback);
-
-        pagedList.loadAround(35);
-        drain();
-
-        verifyRange(pagedList, 2, 3, 4);
-        verify(callback).onChanged(20, 10);
-        verifyNoMoreInteractions(callback);
-    }
-
-    @Test
-    public void loadWithGap() {
-        TiledPagedList<Item> pagedList = createTiledPagedList(0);
-        PagedList.Callback callback = mock(PagedList.Callback.class);
-        pagedList.addWeakCallback(null, callback);
-        verifyRange(pagedList, 0);
-        verifyZeroInteractions(callback);
-
-        pagedList.loadAround(44);
-        drain();
-
-        verifyRange(pagedList, 0, 3, 4);
-        verify(callback).onChanged(30, 10);
-        verify(callback).onChanged(40, 5);
-        verifyNoMoreInteractions(callback);
-    }
-
-    @Test
-    public void tinyPrefetchTest() {
-        TiledPagedList<Item> pagedList = createTiledPagedList(0, 1);
-        PagedList.Callback callback = mock(PagedList.Callback.class);
-        pagedList.addWeakCallback(null, callback);
-        verifyRange(pagedList, 0); // just 4 loaded
-        verifyZeroInteractions(callback);
-
-        pagedList.loadAround(23);
-        drain();
-
-        verifyRange(pagedList, 0, 2);
-        verify(callback).onChanged(20, 10);
-        verifyNoMoreInteractions(callback);
-
-        pagedList.loadAround(44);
-        drain();
-
-        verifyRange(pagedList, 0, 2, 4);
-        verify(callback).onChanged(40, 5);
-        verifyNoMoreInteractions(callback);
-    }
-
-    @Test
-    public void appendCallbackAddedLate() {
-        TiledPagedList<Item> pagedList = createTiledPagedList(0, 0);
-        verifyRange(pagedList, 0);
-
-        pagedList.loadAround(15);
-        drain();
-        verifyRange(pagedList, 0, 1);
-
-        // snapshot at 20 items
-        PageArrayList<Item> snapshot = (PageArrayList<Item>) pagedList.snapshot();
-        verifyRange(snapshot, 0, 1);
-
-
-        pagedList.loadAround(25);
-        pagedList.loadAround(35);
-        drain();
-        verifyRange(pagedList, 0, 1, 2, 3);
-        verifyRange(snapshot, 0, 1);
-
-        PagedList.Callback callback = mock(
-                PagedList.Callback.class);
-        pagedList.addWeakCallback(snapshot, callback);
-        verify(callback).onChanged(20, 20);
-        verifyNoMoreInteractions(callback);
-    }
-
-
-    @Test
-    public void prependCallbackAddedLate() {
-        TiledPagedList<Item> pagedList = createTiledPagedList(44, 0);
-        verifyRange(pagedList, 3, 4);
-
-        pagedList.loadAround(25);
-        drain();
-        verifyRange(pagedList, 2, 3, 4);
-
-        // snapshot at 30 items
-        PageArrayList<Item> snapshot = (PageArrayList<Item>) pagedList.snapshot();
-        verifyRange(snapshot, 2, 3, 4);
-
-
-        pagedList.loadAround(15);
-        pagedList.loadAround(5);
-        drain();
-        verifyRange(pagedList, 0, 1, 2, 3, 4);
-        verifyRange(snapshot, 2, 3, 4);
-
-        PagedList.Callback callback = mock(PagedList.Callback.class);
-        pagedList.addWeakCallback(snapshot, callback);
-        verify(callback).onChanged(0, 20);
-        verifyNoMoreInteractions(callback);
-    }
-
-    @Test
-    public void placeholdersDisabled() {
-        // disable placeholders with config, so we create a contiguous version of the pagedlist
-        PagedList<Item> pagedList = new PagedList.Builder<Integer, Item>()
-                .setDataSource(new TestTiledSource())
-                .setMainThreadExecutor(mMainThread)
-                .setBackgroundThreadExecutor(mBackgroundThread)
-                .setConfig(new PagedList.Config.Builder()
-                        .setPageSize(PAGE_SIZE)
-                        .setPrefetchDistance(PAGE_SIZE)
-                        .setInitialLoadSizeHint(PAGE_SIZE)
-                        .setEnablePlaceholders(false)
-                        .build())
-                .setInitialKey(20)
-                .build();
-
-        assertTrue(pagedList.isContiguous());
-
-        ContiguousPagedList<Item> contiguousPagedList = (ContiguousPagedList<Item>) pagedList;
-        assertEquals(0, contiguousPagedList.getLeadingNullCount());
-        assertEquals(PAGE_SIZE, contiguousPagedList.mList.size());
-        assertEquals(0, contiguousPagedList.getTrailingNullCount());
-    }
-
-    private void drain() {
-        boolean executed;
-        do {
-            executed = mBackgroundThread.executeAll();
-            executed |= mMainThread.executeAll();
-        } while (executed);
-    }
-}
diff --git a/android/arch/paging/User.java b/android/arch/paging/User.java
deleted file mode 100644
index a6d965a..0000000
--- a/android/arch/paging/User.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2017 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.arch.paging;
-
-public class User {
-    public final String name;
-    public final String info;
-
-    public User(String name, String info) {
-        this.name = name;
-        this.info = info;
-    }
-
-    @Override
-    public String toString() {
-        return name;
-    }
-}
diff --git a/android/arch/persistence/room/InvalidationTracker.java b/android/arch/persistence/room/InvalidationTracker.java
index 45ec028..b31dc13 100644
--- a/android/arch/persistence/room/InvalidationTracker.java
+++ b/android/arch/persistence/room/InvalidationTracker.java
@@ -219,7 +219,7 @@
      *
      * @param observer The observer which listens the database for changes.
      */
-    public void addObserver(Observer observer) {
+    public void addObserver(@NonNull Observer observer) {
         final String[] tableNames = observer.mTables;
         int[] tableIds = new int[tableNames.length];
         final int size = tableNames.length;
@@ -265,7 +265,7 @@
      * @param observer The observer to remove.
      */
     @SuppressWarnings("WeakerAccess")
-    public void removeObserver(final Observer observer) {
+    public void removeObserver(@NonNull final Observer observer) {
         ObserverWrapper wrapper;
         synchronized (mObserverMap) {
             wrapper = mObserverMap.remove(observer);
diff --git a/android/arch/persistence/room/Relation.java b/android/arch/persistence/room/Relation.java
index 7206699..d55bbfe 100644
--- a/android/arch/persistence/room/Relation.java
+++ b/android/arch/persistence/room/Relation.java
@@ -28,6 +28,8 @@
  * <pre>
  * {@literal @}Entity
  * public class Pet {
+ *     {@literal @} PrimaryKey
+ *     int id;
  *     int userId;
  *     String name;
  *     // other fields
@@ -41,8 +43,8 @@
  *
  * {@literal @}Dao
  * public interface UserPetDao {
- *     {@literal @}Query("SELECT id, name from User WHERE age &gt; :minAge")
- *     public List&lt;UserNameAndAllPets&gt; loadUserAndPets(int minAge);
+ *     {@literal @}Query("SELECT id, name from User")
+ *     public List&lt;UserNameAndAllPets&gt; loadUserAndPets();
  * }
  * </pre>
  * <p>
@@ -63,16 +65,16 @@
  *   {@literal @}Embedded
  *   public User user;
  *   {@literal @}Relation(parentColumn = "id", entityColumn = "userId", entity = Pet.class)
- *   public List<PetNameAndId> pets;
+ *   public List&lt;PetNameAndId&gt; pets;
  * }
  * {@literal @}Dao
  * public interface UserPetDao {
- *     {@literal @}Query("SELECT * from User WHERE age &gt; :minAge")
- *     public List&lt;UserAllPets&gt; loadUserAndPets(int minAge);
+ *     {@literal @}Query("SELECT * from User")
+ *     public List&lt;UserAllPets&gt; loadUserAndPets();
  * }
  * </pre>
  * <p>
- * In the example above, {@code PetNameAndId} is a regular but all of fields are fetched
+ * In the example above, {@code PetNameAndId} is a regular Pojo but all of fields are fetched
  * from the {@code entity} defined in the {@code @Relation} annotation (<i>Pet</i>).
  * {@code PetNameAndId} could also define its own relations all of which would also be fetched
  * automatically.
@@ -85,7 +87,7 @@
  *   public User user;
  *   {@literal @}Relation(parentColumn = "id", entityColumn = "userId", entity = Pet.class,
  *           projection = {"name"})
- *   public List<String> petNames;
+ *   public List&lt;String&gt; petNames;
  * }
  * </pre>
  * <p>
@@ -93,7 +95,7 @@
  * cannot have relations. This is a design decision to avoid common pitfalls in {@link Entity}
  * setups. You can read more about it in the main Room documentation. When loading data, you can
  * simply work around this limitation by creating Pojo classes that extend the {@link Entity}.
- *
+ * <p>
  * Note that the {@code @Relation} annotated field cannot be a constructor parameter, it must be
  * public or have a public setter.
  */
diff --git a/android/arch/persistence/room/Room.java b/android/arch/persistence/room/Room.java
index 8ce4be0..2850b55 100644
--- a/android/arch/persistence/room/Room.java
+++ b/android/arch/persistence/room/Room.java
@@ -43,6 +43,7 @@
      * @return A {@code RoomDatabaseBuilder<T>} which you can use to create the database.
      */
     @SuppressWarnings("WeakerAccess")
+    @NonNull
     public static <T extends RoomDatabase> RoomDatabase.Builder<T> databaseBuilder(
             @NonNull Context context, @NonNull Class<T> klass, @NonNull String name) {
         //noinspection ConstantConditions
@@ -65,6 +66,7 @@
      * @param <T>     The type of the database class.
      * @return A {@code RoomDatabaseBuilder<T>} which you can use to create the database.
      */
+    @NonNull
     public static <T extends RoomDatabase> RoomDatabase.Builder<T> inMemoryDatabaseBuilder(
             @NonNull Context context, @NonNull Class<T> klass) {
         return new RoomDatabase.Builder<>(context, klass, null);
diff --git a/android/arch/persistence/room/RoomDatabase.java b/android/arch/persistence/room/RoomDatabase.java
index cdad868..8c94024 100644
--- a/android/arch/persistence/room/RoomDatabase.java
+++ b/android/arch/persistence/room/RoomDatabase.java
@@ -49,7 +49,7 @@
  *
  * @see Database
  */
-@SuppressWarnings({"unused", "WeakerAccess"})
+//@SuppressWarnings({"unused", "WeakerAccess"})
 public abstract class RoomDatabase {
     private static final String DB_IMPL_SUFFIX = "_Impl";
     // set by the generated open helper.
@@ -153,7 +153,9 @@
      *
      * @hide
      */
+    @SuppressWarnings("WeakerAccess")
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    // used in generated code
     public void assertNotMainThread() {
         if (mAllowMainThreadQueries) {
             return;
@@ -298,6 +300,7 @@
      * @return True if there is an active transaction in current thread, false otherwise.
      * @see SupportSQLiteDatabase#inTransaction()
      */
+    @SuppressWarnings("WeakerAccess")
     public boolean inTransaction() {
         return mOpenHelper.getWritableDatabase().inTransaction();
     }
@@ -307,7 +310,6 @@
      *
      * @param <T> The type of the abstract database class.
      */
-    @SuppressWarnings("unused")
     public static class Builder<T extends RoomDatabase> {
         private final Class<T> mDatabaseClass;
         private final String mName;
@@ -337,7 +339,8 @@
          * @param factory The factory to use to access the database.
          * @return this
          */
-        public Builder<T> openHelperFactory(SupportSQLiteOpenHelper.Factory factory) {
+        @NonNull
+        public Builder<T> openHelperFactory(@Nullable SupportSQLiteOpenHelper.Factory factory) {
             mFactory = factory;
             return this;
         }
@@ -361,6 +364,7 @@
          *                   changes.
          * @return this
          */
+        @NonNull
         public Builder<T> addMigrations(Migration... migrations) {
             mMigrationContainer.addMigrations(migrations);
             return this;
@@ -378,6 +382,7 @@
          *
          * @return this
          */
+        @NonNull
         public Builder<T> allowMainThreadQueries() {
             mAllowMainThreadQueries = true;
             return this;
@@ -400,6 +405,7 @@
          *
          * @return this
          */
+        @NonNull
         public Builder<T> fallbackToDestructiveMigration() {
             mRequireMigration = false;
             return this;
@@ -411,6 +417,7 @@
          * @param callback The callback.
          * @return this
          */
+        @NonNull
         public Builder<T> addCallback(@NonNull Callback callback) {
             if (mCallbacks == null) {
                 mCallbacks = new ArrayList<>();
@@ -427,6 +434,7 @@
          *
          * @return A new database instance.
          */
+        @NonNull
         public T build() {
             //noinspection ConstantConditions
             if (mContext == null) {
@@ -493,6 +501,7 @@
          * @return An ordered list of {@link Migration} objects that should be run to migrate
          * between the given versions. If a migration path cannot be found, returns {@code null}.
          */
+        @SuppressWarnings("WeakerAccess")
         @Nullable
         public List<Migration> findMigrationPath(int start, int end) {
             if (start == end) {
diff --git a/android/arch/persistence/room/RoomWarnings.java b/android/arch/persistence/room/RoomWarnings.java
index c64be96..f05e6be 100644
--- a/android/arch/persistence/room/RoomWarnings.java
+++ b/android/arch/persistence/room/RoomWarnings.java
@@ -125,4 +125,12 @@
      * annotation.
      */
     public static final String DEFAULT_CONSTRUCTOR = "ROOM_DEFAULT_CONSTRUCTOR";
+
+    /**
+     * Reported when a @Query method returns a Pojo that has relations but the method is not
+     * annotated with @Transaction. Relations are run as separate queries and if the query is not
+     * run inside a transaction, it might return inconsistent results from the database.
+     */
+    public static final String RELATION_QUERY_WITHOUT_TRANSACTION =
+            "ROOM_RELATION_QUERY_WITHOUT_TRANSACTION";
 }
diff --git a/android/arch/persistence/room/Transaction.java b/android/arch/persistence/room/Transaction.java
index 914e4f4..3b6ede9 100644
--- a/android/arch/persistence/room/Transaction.java
+++ b/android/arch/persistence/room/Transaction.java
@@ -22,9 +22,10 @@
 import java.lang.annotation.Target;
 
 /**
- * Marks a method in an abstract {@link Dao} class as a transaction method.
+ * Marks a method in a {@link Dao} class as a transaction method.
  * <p>
- * The derived implementation of the method will execute the super method in a database transaction.
+ * When used on a non-abstract method of an abstract {@link Dao} class,
+ * the derived implementation of the method will execute the super method in a database transaction.
  * All the parameters and return types are preserved. The transaction will be marked as successful
  * unless an exception is thrown in the method body.
  * <p>
@@ -44,6 +45,38 @@
  *     }
  * }
  * </pre>
+ * <p>
+ * When used on a {@link Query} method that has a {@code Select} statement, the generated code for
+ * the Query will be run in a transaction. There are 2 main cases where you may want to do that:
+ * <ol>
+ *     <li>If the result of the query is fairly big, it is better to run it inside a transaction
+ *     to receive a consistent result. Otherwise, if the query result does not fit into a single
+ *     {@link android.database.CursorWindow CursorWindow}, the query result may be corrupted due to
+ *     changes in the database in between cursor window swaps.
+ *     <li>If the result of the query is a Pojo with {@link Relation} fields, these fields are
+ *     queried separately. To receive consistent results between these queries, you probably want
+ *     to run them in a single transaction.
+ * </ol>
+ * Example:
+ * <pre>
+ * class ProductWithReviews extends Product {
+ *     {@literal @}Relation(parentColumn = "id", entityColumn = "productId", entity = Review.class)
+ *     public List&lt;Review> reviews;
+ * }
+ * {@literal @}Dao
+ * public interface ProductDao {
+ *     {@literal @}Transaction {@literal @}Query("SELECT * from products")
+ *     public List&lt;ProductWithReviews> loadAll();
+ * }
+ * </pre>
+ * If the query is an async query (e.g. returns a {@link android.arch.lifecycle.LiveData LiveData}
+ * or RxJava Flowable, the transaction is properly handled when the query is run, not when the
+ * method is called.
+ * <p>
+ * Putting this annotation on an {@link Insert}, {@link Update} or {@link Delete} method has no
+ * impact because they are always run inside a transaction. Similarly, if it is annotated with
+ * {@link Query} but runs an update or delete statement, it is automatically wrapped in a
+ * transaction.
  */
 @Target({ElementType.METHOD})
 @Retention(RetentionPolicy.CLASS)
diff --git a/android/arch/persistence/room/integration/testapp/RoomPagedListActivity.java b/android/arch/persistence/room/integration/testapp/RoomPagedListActivity.java
index 818c46b..cdd464e 100644
--- a/android/arch/persistence/room/integration/testapp/RoomPagedListActivity.java
+++ b/android/arch/persistence/room/integration/testapp/RoomPagedListActivity.java
@@ -86,6 +86,7 @@
 
     @Override
     protected void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
         PagedList<Customer> list = mAdapter.getCurrentList();
         if (list == null) {
             // Can't find anything to restore
diff --git a/android/arch/persistence/room/integration/testapp/database/CustomerDao.java b/android/arch/persistence/room/integration/testapp/database/CustomerDao.java
index 9d40237..b5df914 100644
--- a/android/arch/persistence/room/integration/testapp/database/CustomerDao.java
+++ b/android/arch/persistence/room/integration/testapp/database/CustomerDao.java
@@ -59,7 +59,7 @@
 
     // Keyed
 
-    @Query("SELECT * from customer ORDER BY mLastName ASC LIMIT :limit")
+    @Query("SELECT * from customer ORDER BY mLastName DESC LIMIT :limit")
     List<Customer> customerNameInitial(int limit);
 
     @Query("SELECT * from customer WHERE mLastName < :key ORDER BY mLastName DESC LIMIT :limit")
diff --git a/android/arch/persistence/room/integration/testapp/db/JDBCOpenHelper.java b/android/arch/persistence/room/integration/testapp/db/JDBCOpenHelper.java
deleted file mode 100644
index 3cbffc8..0000000
--- a/android/arch/persistence/room/integration/testapp/db/JDBCOpenHelper.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2017 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.arch.persistence.room.integration.testapp.db;
-
-import android.arch.persistence.db.SupportSQLiteDatabase;
-import android.arch.persistence.db.SupportSQLiteOpenHelper;
-
-public class JDBCOpenHelper implements SupportSQLiteOpenHelper {
-    @Override
-    public String getDatabaseName() {
-        return null;
-    }
-
-    @Override
-    public void setWriteAheadLoggingEnabled(boolean enabled) {
-
-    }
-
-    @Override
-    public SupportSQLiteDatabase getWritableDatabase() {
-        return null;
-    }
-
-    @Override
-    public SupportSQLiteDatabase getReadableDatabase() {
-        return null;
-    }
-
-    @Override
-    public void close() {
-
-    }
-}
diff --git a/android/arch/persistence/room/integration/testapp/test/InvalidationTest.java b/android/arch/persistence/room/integration/testapp/test/InvalidationTest.java
index 84f20ec..33f4018 100644
--- a/android/arch/persistence/room/integration/testapp/test/InvalidationTest.java
+++ b/android/arch/persistence/room/integration/testapp/test/InvalidationTest.java
@@ -17,20 +17,17 @@
 package android.arch.persistence.room.integration.testapp.test;
 
 import static org.hamcrest.CoreMatchers.hasItem;
-import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.nullValue;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.collection.IsCollectionWithSize.hasSize;
 
-import android.arch.core.executor.ArchTaskExecutor;
-import android.arch.core.executor.TaskExecutor;
+import android.arch.core.executor.testing.CountingTaskExecutorRule;
 import android.arch.persistence.room.InvalidationTracker;
 import android.arch.persistence.room.Room;
 import android.arch.persistence.room.integration.testapp.TestDatabase;
 import android.arch.persistence.room.integration.testapp.dao.UserDao;
 import android.arch.persistence.room.integration.testapp.vo.User;
 import android.content.Context;
-import android.os.Handler;
-import android.os.Looper;
 import android.support.annotation.NonNull;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.SmallTest;
@@ -38,17 +35,13 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 import java.util.Set;
-import java.util.concurrent.Callable;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.FutureTask;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
 
 /**
  * Tests invalidation tracking.
@@ -56,138 +49,97 @@
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class InvalidationTest {
+    @Rule
+    public CountingTaskExecutorRule executorRule = new CountingTaskExecutorRule();
     private UserDao mUserDao;
     private TestDatabase mDb;
 
     @Before
-    public void createDb() {
+    public void createDb() throws TimeoutException, InterruptedException {
         Context context = InstrumentationRegistry.getTargetContext();
         mDb = Room.inMemoryDatabaseBuilder(context, TestDatabase.class).build();
         mUserDao = mDb.getUserDao();
-    }
-
-    @Before
-    public void setSingleThreadedIO() {
-        ArchTaskExecutor.getInstance().setDelegate(new TaskExecutor() {
-            ExecutorService mIOExecutor = Executors.newSingleThreadExecutor();
-            Handler mHandler = new Handler(Looper.getMainLooper());
-
-            @Override
-            public void executeOnDiskIO(Runnable runnable) {
-                mIOExecutor.execute(runnable);
-            }
-
-            @Override
-            public void postToMainThread(Runnable runnable) {
-                mHandler.post(runnable);
-            }
-
-            @Override
-            public boolean isMainThread() {
-                return Thread.currentThread() == Looper.getMainLooper().getThread();
-            }
-        });
+        drain();
     }
 
     @After
-    public void clearExecutor() {
-        ArchTaskExecutor.getInstance().setDelegate(null);
+    public void closeDb() throws TimeoutException, InterruptedException {
+        mDb.close();
+        drain();
     }
 
-    private void waitUntilIOThreadIsIdle() {
-        FutureTask<Void> future = new FutureTask<>(new Callable<Void>() {
-            @Override
-            public Void call() throws Exception {
-                return null;
-            }
-        });
-        ArchTaskExecutor.getInstance().executeOnDiskIO(future);
-        //noinspection TryWithIdenticalCatches
-        try {
-            future.get();
-        } catch (InterruptedException e) {
-            throw new RuntimeException(e);
-        } catch (ExecutionException e) {
-            throw new RuntimeException(e);
-        }
+    private void drain() throws TimeoutException, InterruptedException {
+        executorRule.drainTasks(1, TimeUnit.MINUTES);
     }
 
     @Test
-    public void testInvalidationOnUpdate() throws InterruptedException {
+    public void testInvalidationOnUpdate() throws InterruptedException, TimeoutException {
         User user = TestUtil.createUser(3);
         mUserDao.insert(user);
-        LatchObserver observer = new LatchObserver(1, "User");
+        LoggingObserver observer = new LoggingObserver("User");
         mDb.getInvalidationTracker().addObserver(observer);
+        drain();
         mUserDao.updateById(3, "foo2");
-        waitUntilIOThreadIsIdle();
-        assertThat(observer.await(), is(true));
+        drain();
         assertThat(observer.getInvalidatedTables(), hasSize(1));
         assertThat(observer.getInvalidatedTables(), hasItem("User"));
     }
 
     @Test
-    public void testInvalidationOnDelete() throws InterruptedException {
+    public void testInvalidationOnDelete() throws InterruptedException, TimeoutException {
         User user = TestUtil.createUser(3);
         mUserDao.insert(user);
-        LatchObserver observer = new LatchObserver(1, "User");
+        LoggingObserver observer = new LoggingObserver("User");
         mDb.getInvalidationTracker().addObserver(observer);
+        drain();
         mUserDao.delete(user);
-        waitUntilIOThreadIsIdle();
-        assertThat(observer.await(), is(true));
+        drain();
         assertThat(observer.getInvalidatedTables(), hasSize(1));
         assertThat(observer.getInvalidatedTables(), hasItem("User"));
     }
 
     @Test
-    public void testInvalidationOnInsert() throws InterruptedException {
-        LatchObserver observer = new LatchObserver(1, "User");
+    public void testInvalidationOnInsert() throws InterruptedException, TimeoutException {
+        LoggingObserver observer = new LoggingObserver("User");
         mDb.getInvalidationTracker().addObserver(observer);
+        drain();
         mUserDao.insert(TestUtil.createUser(3));
-        waitUntilIOThreadIsIdle();
-        assertThat(observer.await(), is(true));
+        drain();
         assertThat(observer.getInvalidatedTables(), hasSize(1));
         assertThat(observer.getInvalidatedTables(), hasItem("User"));
     }
 
     @Test
-    public void testDontInvalidateOnLateInsert() throws InterruptedException {
-        LatchObserver observer = new LatchObserver(1, "User");
+    public void testDontInvalidateOnLateInsert() throws InterruptedException, TimeoutException {
+        LoggingObserver observer = new LoggingObserver("User");
         mUserDao.insert(TestUtil.createUser(3));
-        waitUntilIOThreadIsIdle();
+        drain();
         mDb.getInvalidationTracker().addObserver(observer);
-        waitUntilIOThreadIsIdle();
-        assertThat(observer.await(), is(false));
+        drain();
+        assertThat(observer.getInvalidatedTables(), nullValue());
     }
 
     @Test
-    public void testMultipleTables() throws InterruptedException {
-        LatchObserver observer = new LatchObserver(1, "User", "Pet");
+    public void testMultipleTables() throws InterruptedException, TimeoutException {
+        LoggingObserver observer = new LoggingObserver("User", "Pet");
         mDb.getInvalidationTracker().addObserver(observer);
+        drain();
         mUserDao.insert(TestUtil.createUser(3));
-        waitUntilIOThreadIsIdle();
-        assertThat(observer.await(), is(true));
+        drain();
         assertThat(observer.getInvalidatedTables(), hasSize(1));
         assertThat(observer.getInvalidatedTables(), hasItem("User"));
     }
 
-    private static class LatchObserver extends InvalidationTracker.Observer {
-        CountDownLatch mLatch;
-
+    private static class LoggingObserver extends InvalidationTracker.Observer {
         private Set<String> mInvalidatedTables;
 
-        LatchObserver(int permits, String... tables) {
+        LoggingObserver(String... tables) {
             super(tables);
-            mLatch = new CountDownLatch(permits);
-        }
-
-        boolean await() throws InterruptedException {
-            return mLatch.await(5, TimeUnit.SECONDS);
         }
 
         @Override
         public void onInvalidated(@NonNull Set<String> tables) {
             mInvalidatedTables = tables;
-            mLatch.countDown();
         }
 
         Set<String> getInvalidatedTables() {
diff --git a/android/arch/persistence/room/integration/testapp/test/QueryDataSourceTest.java b/android/arch/persistence/room/integration/testapp/test/QueryDataSourceTest.java
index e11117e..2735c05 100644
--- a/android/arch/persistence/room/integration/testapp/test/QueryDataSourceTest.java
+++ b/android/arch/persistence/room/integration/testapp/test/QueryDataSourceTest.java
@@ -166,17 +166,13 @@
 
         p = dataSource.loadBefore(15, list.get(0), 10);
         assertNotNull(p);
-        for (User u : p) {
-            list.add(0, u);
-        }
+        list.addAll(0, p);
 
         assertArrayEquals(Arrays.copyOfRange(expected, 5, 35), list.toArray());
 
         p = dataSource.loadBefore(5, list.get(0), 10);
         assertNotNull(p);
-        for (User u : p) {
-            list.add(0, u);
-        }
+        list.addAll(0, p);
 
         assertArrayEquals(Arrays.copyOfRange(expected, 0, 35), list.toArray());
     }
diff --git a/android/arch/persistence/room/integration/testapp/test/QueryTransactionTest.java b/android/arch/persistence/room/integration/testapp/test/QueryTransactionTest.java
new file mode 100644
index 0000000..854c862
--- /dev/null
+++ b/android/arch/persistence/room/integration/testapp/test/QueryTransactionTest.java
@@ -0,0 +1,471 @@
+/*
+ * Copyright (C) 2017 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.arch.persistence.room.integration.testapp.test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import android.arch.core.executor.ArchTaskExecutor;
+import android.arch.core.executor.testing.CountingTaskExecutorRule;
+import android.arch.lifecycle.Lifecycle;
+import android.arch.lifecycle.LiveData;
+import android.arch.lifecycle.Observer;
+import android.arch.paging.LivePagedListProvider;
+import android.arch.paging.PagedList;
+import android.arch.paging.TiledDataSource;
+import android.arch.persistence.room.Dao;
+import android.arch.persistence.room.Database;
+import android.arch.persistence.room.Entity;
+import android.arch.persistence.room.Ignore;
+import android.arch.persistence.room.Insert;
+import android.arch.persistence.room.PrimaryKey;
+import android.arch.persistence.room.Query;
+import android.arch.persistence.room.Relation;
+import android.arch.persistence.room.Room;
+import android.arch.persistence.room.RoomDatabase;
+import android.arch.persistence.room.RoomWarnings;
+import android.arch.persistence.room.Transaction;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import io.reactivex.Flowable;
+import io.reactivex.Maybe;
+import io.reactivex.Single;
+import io.reactivex.observers.TestObserver;
+import io.reactivex.schedulers.Schedulers;
+import io.reactivex.subscribers.TestSubscriber;
+
+@SmallTest
+@RunWith(Parameterized.class)
+public class QueryTransactionTest {
+    @Rule
+    public CountingTaskExecutorRule countingTaskExecutorRule = new CountingTaskExecutorRule();
+    private static final AtomicInteger sStartedTransactionCount = new AtomicInteger(0);
+    private TransactionDb mDb;
+    private final boolean mUseTransactionDao;
+    private Entity1Dao mDao;
+    private final LiveDataQueryTest.TestLifecycleOwner mLifecycleOwner = new LiveDataQueryTest
+            .TestLifecycleOwner();
+
+    @NonNull
+    @Parameterized.Parameters(name = "useTransaction_{0}")
+    public static Boolean[] getParams() {
+        return new Boolean[]{false, true};
+    }
+
+    public QueryTransactionTest(boolean useTransactionDao) {
+        mUseTransactionDao = useTransactionDao;
+    }
+
+    @Before
+    public void initDb() {
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                mLifecycleOwner.handleEvent(Lifecycle.Event.ON_START);
+            }
+        });
+
+        resetTransactionCount();
+        mDb = Room.inMemoryDatabaseBuilder(InstrumentationRegistry.getTargetContext(),
+                TransactionDb.class).build();
+        mDao = mUseTransactionDao ? mDb.transactionDao() : mDb.dao();
+        drain();
+    }
+
+    @After
+    public void closeDb() {
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                mLifecycleOwner.handleEvent(Lifecycle.Event.ON_DESTROY);
+            }
+        });
+        drain();
+        mDb.close();
+    }
+
+    @Test
+    public void readList() {
+        mDao.insert(new Entity1(1, "foo"));
+        resetTransactionCount();
+
+        int expectedTransactionCount = mUseTransactionDao ? 1 : 0;
+        List<Entity1> allEntities = mDao.allEntities();
+        assertTransactionCount(allEntities, expectedTransactionCount);
+    }
+
+    @Test
+    public void liveData() {
+        LiveData<List<Entity1>> listLiveData = mDao.liveData();
+        observeForever(listLiveData);
+        drain();
+        assertThat(listLiveData.getValue(), is(Collections.<Entity1>emptyList()));
+
+        resetTransactionCount();
+        mDao.insert(new Entity1(1, "foo"));
+        drain();
+
+        //noinspection ConstantConditions
+        assertThat(listLiveData.getValue().size(), is(1));
+        int expectedTransactionCount = mUseTransactionDao ? 2 : 1;
+        assertTransactionCount(listLiveData.getValue(), expectedTransactionCount);
+    }
+
+    @Test
+    public void flowable() {
+        Flowable<List<Entity1>> flowable = mDao.flowable();
+        TestSubscriber<List<Entity1>> subscriber = observe(flowable);
+        drain();
+        assertThat(subscriber.values().size(), is(1));
+
+        resetTransactionCount();
+        mDao.insert(new Entity1(1, "foo"));
+        drain();
+
+        List<Entity1> allEntities = subscriber.values().get(1);
+        assertThat(allEntities.size(), is(1));
+        int expectedTransactionCount = mUseTransactionDao ? 2 : 1;
+        assertTransactionCount(allEntities, expectedTransactionCount);
+    }
+
+    @Test
+    public void maybe() {
+        mDao.insert(new Entity1(1, "foo"));
+        resetTransactionCount();
+
+        int expectedTransactionCount = mUseTransactionDao ? 1 : 0;
+        Maybe<List<Entity1>> listMaybe = mDao.maybe();
+        TestObserver<List<Entity1>> observer = observe(listMaybe);
+        drain();
+        List<Entity1> allEntities = observer.values().get(0);
+        assertTransactionCount(allEntities, expectedTransactionCount);
+    }
+
+    @Test
+    public void single() {
+        mDao.insert(new Entity1(1, "foo"));
+        resetTransactionCount();
+
+        int expectedTransactionCount = mUseTransactionDao ? 1 : 0;
+        Single<List<Entity1>> listMaybe = mDao.single();
+        TestObserver<List<Entity1>> observer = observe(listMaybe);
+        drain();
+        List<Entity1> allEntities = observer.values().get(0);
+        assertTransactionCount(allEntities, expectedTransactionCount);
+    }
+
+    @Test
+    public void relation() {
+        mDao.insert(new Entity1(1, "foo"));
+        mDao.insert(new Child(1, 1));
+        mDao.insert(new Child(2, 1));
+        resetTransactionCount();
+
+        List<Entity1WithChildren> result = mDao.withRelation();
+        int expectedTransactionCount = mUseTransactionDao ? 1 : 0;
+        assertTransactionCountWithChildren(result, expectedTransactionCount);
+    }
+
+    @Test
+    public void pagedList() {
+        LiveData<PagedList<Entity1>> pagedList = mDao.pagedList().create(null, 10);
+        observeForever(pagedList);
+        drain();
+        assertThat(sStartedTransactionCount.get(), is(mUseTransactionDao ? 0 : 0));
+
+        mDao.insert(new Entity1(1, "foo"));
+        drain();
+        //noinspection ConstantConditions
+        assertThat(pagedList.getValue().size(), is(1));
+        assertTransactionCount(pagedList.getValue(), mUseTransactionDao ? 2 : 1);
+
+        mDao.insert(new Entity1(2, "bar"));
+        drain();
+        assertThat(pagedList.getValue().size(), is(2));
+        assertTransactionCount(pagedList.getValue(), mUseTransactionDao ? 4 : 2);
+    }
+
+    @Test
+    public void dataSource() {
+        mDao.insert(new Entity1(2, "bar"));
+        drain();
+        resetTransactionCount();
+        TiledDataSource<Entity1> dataSource = mDao.dataSource();
+        dataSource.loadRange(0, 10);
+        assertThat(sStartedTransactionCount.get(), is(mUseTransactionDao ? 1 : 0));
+    }
+
+    private void assertTransactionCount(List<Entity1> allEntities, int expectedTransactionCount) {
+        assertThat(sStartedTransactionCount.get(), is(expectedTransactionCount));
+        assertThat(allEntities.isEmpty(), is(false));
+        for (Entity1 entity1 : allEntities) {
+            assertThat(entity1.transactionId, is(expectedTransactionCount));
+        }
+    }
+
+    private void assertTransactionCountWithChildren(List<Entity1WithChildren> allEntities,
+            int expectedTransactionCount) {
+        assertThat(sStartedTransactionCount.get(), is(expectedTransactionCount));
+        assertThat(allEntities.isEmpty(), is(false));
+        for (Entity1WithChildren entity1 : allEntities) {
+            assertThat(entity1.transactionId, is(expectedTransactionCount));
+            assertThat(entity1.children, notNullValue());
+            assertThat(entity1.children.isEmpty(), is(false));
+            for (Child child : entity1.children) {
+                assertThat(child.transactionId, is(expectedTransactionCount));
+            }
+        }
+    }
+
+    private void resetTransactionCount() {
+        sStartedTransactionCount.set(0);
+    }
+
+    private void drain() {
+        try {
+            countingTaskExecutorRule.drainTasks(30, TimeUnit.SECONDS);
+        } catch (InterruptedException e) {
+            throw new AssertionError("interrupted", e);
+        } catch (TimeoutException e) {
+            throw new AssertionError("drain timed out", e);
+        }
+    }
+
+    private <T> TestSubscriber<T> observe(final Flowable<T> flowable) {
+        TestSubscriber<T> subscriber = new TestSubscriber<>();
+        flowable.observeOn(Schedulers.from(ArchTaskExecutor.getMainThreadExecutor()))
+                .subscribeWith(subscriber);
+        return subscriber;
+    }
+
+    private <T> TestObserver<T> observe(final Maybe<T> maybe) {
+        TestObserver<T> observer = new TestObserver<>();
+        maybe.observeOn(Schedulers.from(ArchTaskExecutor.getMainThreadExecutor()))
+                .subscribeWith(observer);
+        return observer;
+    }
+
+    private <T> TestObserver<T> observe(final Single<T> single) {
+        TestObserver<T> observer = new TestObserver<>();
+        single.observeOn(Schedulers.from(ArchTaskExecutor.getMainThreadExecutor()))
+                .subscribeWith(observer);
+        return observer;
+    }
+
+    private <T> void observeForever(final LiveData<T> liveData) {
+        FutureTask<Void> futureTask = new FutureTask<>(new Callable<Void>() {
+            @Override
+            public Void call() throws Exception {
+                liveData.observe(mLifecycleOwner, new Observer<T>() {
+                    @Override
+                    public void onChanged(@Nullable T t) {
+
+                    }
+                });
+                return null;
+            }
+        });
+        ArchTaskExecutor.getMainThreadExecutor().execute(futureTask);
+        try {
+            futureTask.get();
+        } catch (InterruptedException e) {
+            throw new AssertionError("interrupted", e);
+        } catch (ExecutionException e) {
+            throw new AssertionError("execution error", e);
+        }
+    }
+
+    @SuppressWarnings("WeakerAccess")
+    static class Entity1WithChildren extends Entity1 {
+        @Relation(entity = Child.class, parentColumn = "id",
+                entityColumn = "entity1Id")
+        public List<Child> children;
+
+        Entity1WithChildren(int id, String value) {
+            super(id, value);
+        }
+    }
+
+    @SuppressWarnings("WeakerAccess")
+    @Entity
+    static class Child {
+        @PrimaryKey(autoGenerate = true)
+        public int id;
+        public int entity1Id;
+        @Ignore
+        public final int transactionId = sStartedTransactionCount.get();
+
+        Child(int id, int entity1Id) {
+            this.id = id;
+            this.entity1Id = entity1Id;
+        }
+    }
+
+    @SuppressWarnings("WeakerAccess")
+    @Entity
+    static class Entity1 {
+        @PrimaryKey(autoGenerate = true)
+        public int id;
+        public String value;
+        @Ignore
+        public final int transactionId = sStartedTransactionCount.get();
+
+        Entity1(int id, String value) {
+            this.id = id;
+            this.value = value;
+        }
+    }
+
+    // we don't support dao inheritance for queries so for now, go with this
+    interface Entity1Dao {
+        String SELECT_ALL = "select * from Entity1";
+
+        List<Entity1> allEntities();
+
+        Flowable<List<Entity1>> flowable();
+
+        Maybe<List<Entity1>> maybe();
+
+        Single<List<Entity1>> single();
+
+        LiveData<List<Entity1>> liveData();
+
+        List<Entity1WithChildren> withRelation();
+
+        LivePagedListProvider<Integer, Entity1> pagedList();
+
+        TiledDataSource<Entity1> dataSource();
+
+        @Insert
+        void insert(Entity1 entity1);
+
+        @Insert
+        void insert(Child entity1);
+    }
+
+    @Dao
+    interface EntityDao extends Entity1Dao {
+        @Override
+        @Query(SELECT_ALL)
+        List<Entity1> allEntities();
+
+        @Override
+        @Query(SELECT_ALL)
+        Flowable<List<Entity1>> flowable();
+
+        @Override
+        @Query(SELECT_ALL)
+        LiveData<List<Entity1>> liveData();
+
+        @Override
+        @Query(SELECT_ALL)
+        Maybe<List<Entity1>> maybe();
+
+        @Override
+        @Query(SELECT_ALL)
+        Single<List<Entity1>> single();
+
+        @Override
+        @Query(SELECT_ALL)
+        @SuppressWarnings(RoomWarnings.RELATION_QUERY_WITHOUT_TRANSACTION)
+        List<Entity1WithChildren> withRelation();
+
+        @Override
+        @Query(SELECT_ALL)
+        LivePagedListProvider<Integer, Entity1> pagedList();
+
+        @Override
+        @Query(SELECT_ALL)
+        TiledDataSource<Entity1> dataSource();
+    }
+
+    @Dao
+    interface TransactionDao extends Entity1Dao {
+        @Override
+        @Transaction
+        @Query(SELECT_ALL)
+        List<Entity1> allEntities();
+
+        @Override
+        @Transaction
+        @Query(SELECT_ALL)
+        Flowable<List<Entity1>> flowable();
+
+        @Override
+        @Transaction
+        @Query(SELECT_ALL)
+        LiveData<List<Entity1>> liveData();
+
+        @Override
+        @Transaction
+        @Query(SELECT_ALL)
+        Maybe<List<Entity1>> maybe();
+
+        @Override
+        @Transaction
+        @Query(SELECT_ALL)
+        Single<List<Entity1>> single();
+
+        @Override
+        @Transaction
+        @Query(SELECT_ALL)
+        List<Entity1WithChildren> withRelation();
+
+        @Override
+        @Transaction
+        @Query(SELECT_ALL)
+        LivePagedListProvider<Integer, Entity1> pagedList();
+
+        @Override
+        @Transaction
+        @Query(SELECT_ALL)
+        TiledDataSource<Entity1> dataSource();
+    }
+
+    @Database(version = 1, entities = {Entity1.class, Child.class}, exportSchema = false)
+    abstract static class TransactionDb extends RoomDatabase {
+        abstract EntityDao dao();
+
+        abstract TransactionDao transactionDao();
+
+        @Override
+        public void beginTransaction() {
+            super.beginTransaction();
+            sStartedTransactionCount.incrementAndGet();
+        }
+    }
+}
diff --git a/android/arch/persistence/room/migration/Migration.java b/android/arch/persistence/room/migration/Migration.java
index 907e624..d69ea0d 100644
--- a/android/arch/persistence/room/migration/Migration.java
+++ b/android/arch/persistence/room/migration/Migration.java
@@ -17,6 +17,7 @@
 package android.arch.persistence.room.migration;
 
 import android.arch.persistence.db.SupportSQLiteDatabase;
+import android.support.annotation.NonNull;
 
 /**
  * Base class for a database migration.
@@ -58,5 +59,5 @@
      *
      * @param database The database instance
      */
-    public abstract void migrate(SupportSQLiteDatabase database);
+    public abstract void migrate(@NonNull SupportSQLiteDatabase database);
 }
diff --git a/android/arch/persistence/room/migration/bundle/ForeignKeyBundle.java b/android/arch/persistence/room/migration/bundle/ForeignKeyBundle.java
index 1467a4f..d72cf8c 100644
--- a/android/arch/persistence/room/migration/bundle/ForeignKeyBundle.java
+++ b/android/arch/persistence/room/migration/bundle/ForeignKeyBundle.java
@@ -16,13 +16,18 @@
 
 package android.arch.persistence.room.migration.bundle;
 
+import android.support.annotation.RestrictTo;
+
 import com.google.gson.annotations.SerializedName;
 
 import java.util.List;
 
 /**
  * Holds the information about a foreign key reference.
+ *
+ * @hide
  */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
 public class ForeignKeyBundle {
     @SerializedName("table")
     private String mTable;
diff --git a/android/arch/persistence/room/paging/LimitOffsetDataSource.java b/android/arch/persistence/room/paging/LimitOffsetDataSource.java
index 800514c..2f9a888 100644
--- a/android/arch/persistence/room/paging/LimitOffsetDataSource.java
+++ b/android/arch/persistence/room/paging/LimitOffsetDataSource.java
@@ -49,10 +49,13 @@
     private final RoomDatabase mDb;
     @SuppressWarnings("FieldCanBeLocal")
     private final InvalidationTracker.Observer mObserver;
+    private final boolean mInTransaction;
 
-    protected LimitOffsetDataSource(RoomDatabase db, RoomSQLiteQuery query, String... tables) {
+    protected LimitOffsetDataSource(RoomDatabase db, RoomSQLiteQuery query,
+            boolean inTransaction, String... tables) {
         mDb = db;
         mSourceQuery = query;
+        mInTransaction = inTransaction;
         mCountQuery = "SELECT COUNT(*) FROM ( " + mSourceQuery.getSql() + " )";
         mLimitOffsetQuery = "SELECT * FROM ( " + mSourceQuery.getSql() + " ) LIMIT ? OFFSET ?";
         mObserver = new InvalidationTracker.Observer(tables) {
@@ -98,13 +101,30 @@
         sqLiteQuery.copyArgumentsFrom(mSourceQuery);
         sqLiteQuery.bindLong(sqLiteQuery.getArgCount() - 1, loadCount);
         sqLiteQuery.bindLong(sqLiteQuery.getArgCount(), startPosition);
-        Cursor cursor = mDb.query(sqLiteQuery);
-
-        try {
-            return convertRows(cursor);
-        } finally {
-            cursor.close();
-            sqLiteQuery.release();
+        if (mInTransaction) {
+            mDb.beginTransaction();
+            Cursor cursor = null;
+            try {
+                cursor = mDb.query(sqLiteQuery);
+                List<T> rows = convertRows(cursor);
+                mDb.setTransactionSuccessful();
+                return rows;
+            } finally {
+                if (cursor != null) {
+                    cursor.close();
+                }
+                mDb.endTransaction();
+                sqLiteQuery.release();
+            }
+        } else {
+            Cursor cursor = mDb.query(sqLiteQuery);
+            //noinspection TryFinallyCanBeTryWithResources
+            try {
+                return convertRows(cursor);
+            } finally {
+                cursor.close();
+                sqLiteQuery.release();
+            }
         }
     }
 }
diff --git a/android/arch/persistence/room/util/StringUtil.java b/android/arch/persistence/room/util/StringUtil.java
index bee05dd..d01e3c5 100644
--- a/android/arch/persistence/room/util/StringUtil.java
+++ b/android/arch/persistence/room/util/StringUtil.java
@@ -17,6 +17,7 @@
 package android.arch.persistence.room.util;
 
 import android.support.annotation.Nullable;
+import android.support.annotation.RestrictTo;
 import android.util.Log;
 
 import java.util.ArrayList;
@@ -24,10 +25,14 @@
 import java.util.StringTokenizer;
 
 /**
+ * @hide
+ *
  * String utilities for Room
  */
-@SuppressWarnings("WeakerAccess")
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
 public class StringUtil {
+
+    @SuppressWarnings("unused")
     public static final String[] EMPTY_STRING_ARRAY = new String[0];
     /**
      * Returns a new StringBuilder to be used while producing SQL queries.
diff --git a/android/bluetooth/BluetoothAdapter.java b/android/bluetooth/BluetoothAdapter.java
index 84765f6..578a5b8 100644
--- a/android/bluetooth/BluetoothAdapter.java
+++ b/android/bluetooth/BluetoothAdapter.java
@@ -1134,8 +1134,32 @@
     }
 
     /**
-     * Sets the {@link BluetoothClass} Bluetooth Class of Device (CoD) of
-     * the local Bluetooth adapter.
+     * Returns the {@link BluetoothClass} Bluetooth Class of Device (CoD) of the local Bluetooth
+     * adapter.
+     *
+     * @return {@link BluetoothClass} Bluetooth CoD of local Bluetooth device.
+     *
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+    public BluetoothClass getBluetoothClass() {
+        if (getState() != STATE_ON) return null;
+        try {
+            mServiceLock.readLock().lock();
+            if (mService != null) return mService.getBluetoothClass();
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        } finally {
+            mServiceLock.readLock().unlock();
+        }
+        return null;
+    }
+
+    /**
+     * Sets the {@link BluetoothClass} Bluetooth Class of Device (CoD) of the local Bluetooth
+     * adapter.
+     *
+     * <p>Note: This value persists across system reboot.
      *
      * @param bluetoothClass {@link BluetoothClass} to set the local Bluetooth adapter to.
      * @return true if successful, false if unsuccessful.
@@ -2104,8 +2128,8 @@
         } else if (profile == BluetoothProfile.AVRCP_CONTROLLER) {
             BluetoothAvrcpController avrcp = new BluetoothAvrcpController(context, listener);
             return true;
-        } else if (profile == BluetoothProfile.INPUT_DEVICE) {
-            BluetoothInputDevice iDev = new BluetoothInputDevice(context, listener);
+        } else if (profile == BluetoothProfile.HID_HOST) {
+            BluetoothHidHost iDev = new BluetoothHidHost(context, listener);
             return true;
         } else if (profile == BluetoothProfile.PAN) {
             BluetoothPan pan = new BluetoothPan(context, listener);
@@ -2128,8 +2152,8 @@
         } else if (profile == BluetoothProfile.MAP_CLIENT) {
             BluetoothMapClient mapClient = new BluetoothMapClient(context, listener);
             return true;
-        } else if (profile == BluetoothProfile.INPUT_HOST) {
-            BluetoothInputHost iHost = new BluetoothInputHost(context, listener);
+        } else if (profile == BluetoothProfile.HID_DEVICE) {
+            BluetoothHidDevice hidDevice = new BluetoothHidDevice(context, listener);
             return true;
         } else {
             return false;
@@ -2167,8 +2191,8 @@
                 BluetoothAvrcpController avrcp = (BluetoothAvrcpController) proxy;
                 avrcp.close();
                 break;
-            case BluetoothProfile.INPUT_DEVICE:
-                BluetoothInputDevice iDev = (BluetoothInputDevice) proxy;
+            case BluetoothProfile.HID_HOST:
+                BluetoothHidHost iDev = (BluetoothHidHost) proxy;
                 iDev.close();
                 break;
             case BluetoothProfile.PAN:
@@ -2207,9 +2231,9 @@
                 BluetoothMapClient mapClient = (BluetoothMapClient) proxy;
                 mapClient.close();
                 break;
-            case BluetoothProfile.INPUT_HOST:
-                BluetoothInputHost iHost = (BluetoothInputHost) proxy;
-                iHost.close();
+            case BluetoothProfile.HID_DEVICE:
+                BluetoothHidDevice hidDevice = (BluetoothHidDevice) proxy;
+                hidDevice.close();
                 break;
         }
     }
@@ -2277,6 +2301,8 @@
      *
      * @hide
      */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN)
     public boolean enableNoAutoConnect() {
         if (isEnabled()) {
             if (DBG) Log.d(TAG, "enableNoAutoConnect(): BT already enabled!");
diff --git a/android/bluetooth/BluetoothDevice.java b/android/bluetooth/BluetoothDevice.java
index d982bb7..ad7a93c 100644
--- a/android/bluetooth/BluetoothDevice.java
+++ b/android/bluetooth/BluetoothDevice.java
@@ -1098,6 +1098,8 @@
      * @return true on success, false on error
      * @hide
      */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN)
     public boolean cancelBondProcess() {
         final IBluetooth service = sService;
         if (service == null) {
@@ -1125,6 +1127,8 @@
      * @return true on success, false on error
      * @hide
      */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN)
     public boolean removeBond() {
         final IBluetooth service = sService;
         if (service == null) {
@@ -1174,6 +1178,7 @@
      * @hide
      */
     @SystemApi
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH)
     public boolean isConnected() {
         final IBluetooth service = sService;
         if (service == null) {
@@ -1197,6 +1202,7 @@
      * @hide
      */
     @SystemApi
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH)
     public boolean isEncrypted() {
         final IBluetooth service = sService;
         if (service == null) {
@@ -1444,6 +1450,8 @@
      * @return Whether the value has been successfully set.
      * @hide
      */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
     public boolean setPhonebookAccessPermission(int value) {
         final IBluetooth service = sService;
         if (service == null) {
diff --git a/android/bluetooth/BluetoothHeadset.java b/android/bluetooth/BluetoothHeadset.java
index 85550c7..1241f23 100644
--- a/android/bluetooth/BluetoothHeadset.java
+++ b/android/bluetooth/BluetoothHeadset.java
@@ -16,8 +16,10 @@
 
 package android.bluetooth;
 
+import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SystemApi;
 import android.content.ComponentName;
 import android.content.Context;
 import android.os.Binder;
@@ -416,6 +418,8 @@
      * @return false on immediate error, true otherwise
      * @hide
      */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN)
     public boolean connect(BluetoothDevice device) {
         if (DBG) log("connect(" + device + ")");
         final IBluetoothHeadset service = mService;
@@ -456,6 +460,8 @@
      * @return false on immediate error, true otherwise
      * @hide
      */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN)
     public boolean disconnect(BluetoothDevice device) {
         if (DBG) log("disconnect(" + device + ")");
         final IBluetoothHeadset service = mService;
@@ -543,6 +549,8 @@
      * @return true if priority is set, false on error
      * @hide
      */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN)
     public boolean setPriority(BluetoothDevice device, int priority) {
         if (DBG) log("setPriority(" + device + ", " + priority + ")");
         final IBluetoothHeadset service = mService;
diff --git a/android/bluetooth/BluetoothInputHost.java b/android/bluetooth/BluetoothHidDevice.java
similarity index 93%
rename from android/bluetooth/BluetoothInputHost.java
rename to android/bluetooth/BluetoothHidDevice.java
index e18d9d1..179f36d 100644
--- a/android/bluetooth/BluetoothInputHost.java
+++ b/android/bluetooth/BluetoothHidDevice.java
@@ -33,9 +33,9 @@
 /**
  * @hide
  */
-public final class BluetoothInputHost implements BluetoothProfile {
+public final class BluetoothHidDevice implements BluetoothProfile {
 
-    private static final String TAG = BluetoothInputHost.class.getSimpleName();
+    private static final String TAG = BluetoothHidDevice.class.getSimpleName();
 
     /**
      * Intent used to broadcast the change in connection state of the Input
@@ -57,7 +57,7 @@
      */
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     public static final String ACTION_CONNECTION_STATE_CHANGED =
-            "android.bluetooth.inputhost.profile.action.CONNECTION_STATE_CHANGED";
+            "android.bluetooth.hiddevice.profile.action.CONNECTION_STATE_CHANGED";
 
     /**
      * Constants representing device subclass.
@@ -113,7 +113,7 @@
 
     private ServiceListener mServiceListener;
 
-    private volatile IBluetoothInputHost mService;
+    private volatile IBluetoothHidDevice mService;
 
     private BluetoothAdapter mAdapter;
 
@@ -205,23 +205,23 @@
     private final ServiceConnection mConnection = new ServiceConnection() {
         public void onServiceConnected(ComponentName className, IBinder service) {
             Log.d(TAG, "onServiceConnected()");
-            mService = IBluetoothInputHost.Stub.asInterface(service);
+            mService = IBluetoothHidDevice.Stub.asInterface(service);
             if (mServiceListener != null) {
-                mServiceListener.onServiceConnected(BluetoothProfile.INPUT_HOST,
-                        BluetoothInputHost.this);
+                mServiceListener.onServiceConnected(BluetoothProfile.HID_DEVICE,
+                        BluetoothHidDevice.this);
             }
         }
         public void onServiceDisconnected(ComponentName className) {
             Log.d(TAG, "onServiceDisconnected()");
             mService = null;
             if (mServiceListener != null) {
-                mServiceListener.onServiceDisconnected(BluetoothProfile.INPUT_HOST);
+                mServiceListener.onServiceDisconnected(BluetoothProfile.HID_DEVICE);
             }
         }
     };
 
-    BluetoothInputHost(Context context, ServiceListener listener) {
-        Log.v(TAG, "BluetoothInputHost");
+    BluetoothHidDevice(Context context, ServiceListener listener) {
+        Log.v(TAG, "BluetoothHidDevice");
 
         mContext = context;
         mServiceListener = listener;
@@ -240,7 +240,7 @@
     }
 
     boolean doBind() {
-        Intent intent = new Intent(IBluetoothInputHost.class.getName());
+        Intent intent = new Intent(IBluetoothHidDevice.class.getName());
         ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
         intent.setComponent(comp);
         if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
@@ -285,7 +285,7 @@
     public List<BluetoothDevice> getConnectedDevices() {
         Log.v(TAG, "getConnectedDevices()");
 
-        final IBluetoothInputHost service = mService;
+        final IBluetoothHidDevice service = mService;
         if (service != null) {
             try {
                 return service.getConnectedDevices();
@@ -306,7 +306,7 @@
     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
         Log.v(TAG, "getDevicesMatchingConnectionStates(): states=" + Arrays.toString(states));
 
-        final IBluetoothInputHost service = mService;
+        final IBluetoothHidDevice service = mService;
         if (service != null) {
             try {
                 return service.getDevicesMatchingConnectionStates(states);
@@ -327,7 +327,7 @@
     public int getConnectionState(BluetoothDevice device) {
         Log.v(TAG, "getConnectionState(): device=" + device);
 
-        final IBluetoothInputHost service = mService;
+        final IBluetoothHidDevice service = mService;
         if (service != null) {
             try {
                 return service.getConnectionState(device);
@@ -367,7 +367,7 @@
             return false;
         }
 
-        final IBluetoothInputHost service = mService;
+        final IBluetoothHidDevice service = mService;
         if (service != null) {
             try {
                 BluetoothHidDeviceAppConfiguration config =
@@ -401,7 +401,7 @@
 
         boolean result = false;
 
-        final IBluetoothInputHost service = mService;
+        final IBluetoothHidDevice service = mService;
         if (service != null) {
             try {
                 result = service.unregisterApp(config);
@@ -426,7 +426,7 @@
     public boolean sendReport(BluetoothDevice device, int id, byte[] data) {
         boolean result = false;
 
-        final IBluetoothInputHost service = mService;
+        final IBluetoothHidDevice service = mService;
         if (service != null) {
             try {
                 result = service.sendReport(device, id, data);
@@ -454,7 +454,7 @@
 
         boolean result = false;
 
-        final IBluetoothInputHost service = mService;
+        final IBluetoothHidDevice service = mService;
         if (service != null) {
             try {
                 result = service.replyReport(device, type, id, data);
@@ -480,7 +480,7 @@
 
         boolean result = false;
 
-        final IBluetoothInputHost service = mService;
+        final IBluetoothHidDevice service = mService;
         if (service != null) {
             try {
                 result = service.reportError(device, error);
@@ -504,7 +504,7 @@
 
         boolean result = false;
 
-        final IBluetoothInputHost service = mService;
+        final IBluetoothHidDevice service = mService;
         if (service != null) {
             try {
                 result = service.unplug(device);
@@ -529,7 +529,7 @@
 
         boolean result = false;
 
-        final IBluetoothInputHost service = mService;
+        final IBluetoothHidDevice service = mService;
         if (service != null) {
             try {
                 result = service.connect(device);
@@ -553,7 +553,7 @@
 
         boolean result = false;
 
-        final IBluetoothInputHost service = mService;
+        final IBluetoothHidDevice service = mService;
         if (service != null) {
             try {
                 result = service.disconnect(device);
diff --git a/android/bluetooth/BluetoothInputDevice.java b/android/bluetooth/BluetoothHidHost.java
similarity index 91%
rename from android/bluetooth/BluetoothInputDevice.java
rename to android/bluetooth/BluetoothHidHost.java
index 3261576..8ad0f9d 100644
--- a/android/bluetooth/BluetoothInputDevice.java
+++ b/android/bluetooth/BluetoothHidHost.java
@@ -35,16 +35,16 @@
  * This class provides the public APIs to control the Bluetooth Input
  * Device Profile.
  *
- * <p>BluetoothInputDevice is a proxy object for controlling the Bluetooth
+ * <p>BluetoothHidHost is a proxy object for controlling the Bluetooth
  * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get
- * the BluetoothInputDevice proxy object.
+ * the BluetoothHidHost proxy object.
  *
  * <p>Each method is protected with its appropriate permission.
  *
  * @hide
  */
-public final class BluetoothInputDevice implements BluetoothProfile {
-    private static final String TAG = "BluetoothInputDevice";
+public final class BluetoothHidHost implements BluetoothProfile {
+    private static final String TAG = "BluetoothHidHost";
     private static final boolean DBG = true;
     private static final boolean VDBG = false;
 
@@ -177,52 +177,52 @@
      * @hide
      */
     public static final String EXTRA_PROTOCOL_MODE =
-            "android.bluetooth.BluetoothInputDevice.extra.PROTOCOL_MODE";
+            "android.bluetooth.BluetoothHidHost.extra.PROTOCOL_MODE";
 
     /**
      * @hide
      */
     public static final String EXTRA_REPORT_TYPE =
-            "android.bluetooth.BluetoothInputDevice.extra.REPORT_TYPE";
+            "android.bluetooth.BluetoothHidHost.extra.REPORT_TYPE";
 
     /**
      * @hide
      */
     public static final String EXTRA_REPORT_ID =
-            "android.bluetooth.BluetoothInputDevice.extra.REPORT_ID";
+            "android.bluetooth.BluetoothHidHost.extra.REPORT_ID";
 
     /**
      * @hide
      */
     public static final String EXTRA_REPORT_BUFFER_SIZE =
-            "android.bluetooth.BluetoothInputDevice.extra.REPORT_BUFFER_SIZE";
+            "android.bluetooth.BluetoothHidHost.extra.REPORT_BUFFER_SIZE";
 
     /**
      * @hide
      */
-    public static final String EXTRA_REPORT = "android.bluetooth.BluetoothInputDevice.extra.REPORT";
+    public static final String EXTRA_REPORT = "android.bluetooth.BluetoothHidHost.extra.REPORT";
 
     /**
      * @hide
      */
-    public static final String EXTRA_STATUS = "android.bluetooth.BluetoothInputDevice.extra.STATUS";
+    public static final String EXTRA_STATUS = "android.bluetooth.BluetoothHidHost.extra.STATUS";
 
     /**
      * @hide
      */
     public static final String EXTRA_VIRTUAL_UNPLUG_STATUS =
-            "android.bluetooth.BluetoothInputDevice.extra.VIRTUAL_UNPLUG_STATUS";
+            "android.bluetooth.BluetoothHidHost.extra.VIRTUAL_UNPLUG_STATUS";
 
     /**
      * @hide
      */
     public static final String EXTRA_IDLE_TIME =
-            "android.bluetooth.BluetoothInputDevice.extra.IDLE_TIME";
+            "android.bluetooth.BluetoothHidHost.extra.IDLE_TIME";
 
     private Context mContext;
     private ServiceListener mServiceListener;
     private BluetoothAdapter mAdapter;
-    private volatile IBluetoothInputDevice mService;
+    private volatile IBluetoothHidHost mService;
 
     private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
             new IBluetoothStateChangeCallback.Stub() {
@@ -254,10 +254,10 @@
             };
 
     /**
-     * Create a BluetoothInputDevice proxy object for interacting with the local
+     * Create a BluetoothHidHost proxy object for interacting with the local
      * Bluetooth Service which handles the InputDevice profile
      */
-    /*package*/ BluetoothInputDevice(Context context, ServiceListener l) {
+    /*package*/ BluetoothHidHost(Context context, ServiceListener l) {
         mContext = context;
         mServiceListener = l;
         mAdapter = BluetoothAdapter.getDefaultAdapter();
@@ -275,7 +275,7 @@
     }
 
     boolean doBind() {
-        Intent intent = new Intent(IBluetoothInputDevice.class.getName());
+        Intent intent = new Intent(IBluetoothHidHost.class.getName());
         ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
         intent.setComponent(comp);
         if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
@@ -331,7 +331,7 @@
      */
     public boolean connect(BluetoothDevice device) {
         if (DBG) log("connect(" + device + ")");
-        final IBluetoothInputDevice service = mService;
+        final IBluetoothHidHost service = mService;
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
                 return service.connect(device);
@@ -371,7 +371,7 @@
      */
     public boolean disconnect(BluetoothDevice device) {
         if (DBG) log("disconnect(" + device + ")");
-        final IBluetoothInputDevice service = mService;
+        final IBluetoothHidHost service = mService;
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
                 return service.disconnect(device);
@@ -390,7 +390,7 @@
     @Override
     public List<BluetoothDevice> getConnectedDevices() {
         if (VDBG) log("getConnectedDevices()");
-        final IBluetoothInputDevice service = mService;
+        final IBluetoothHidHost service = mService;
         if (service != null && isEnabled()) {
             try {
                 return service.getConnectedDevices();
@@ -409,7 +409,7 @@
     @Override
     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
         if (VDBG) log("getDevicesMatchingStates()");
-        final IBluetoothInputDevice service = mService;
+        final IBluetoothHidHost service = mService;
         if (service != null && isEnabled()) {
             try {
                 return service.getDevicesMatchingConnectionStates(states);
@@ -428,7 +428,7 @@
     @Override
     public int getConnectionState(BluetoothDevice device) {
         if (VDBG) log("getState(" + device + ")");
-        final IBluetoothInputDevice service = mService;
+        final IBluetoothHidHost service = mService;
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
                 return service.getConnectionState(device);
@@ -458,7 +458,7 @@
      */
     public boolean setPriority(BluetoothDevice device, int priority) {
         if (DBG) log("setPriority(" + device + ", " + priority + ")");
-        final IBluetoothInputDevice service = mService;
+        final IBluetoothHidHost service = mService;
         if (service != null && isEnabled() && isValidDevice(device)) {
             if (priority != BluetoothProfile.PRIORITY_OFF
                     && priority != BluetoothProfile.PRIORITY_ON) {
@@ -490,7 +490,7 @@
      */
     public int getPriority(BluetoothDevice device) {
         if (VDBG) log("getPriority(" + device + ")");
-        final IBluetoothInputDevice service = mService;
+        final IBluetoothHidHost service = mService;
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
                 return service.getPriority(device);
@@ -506,11 +506,11 @@
     private final ServiceConnection mConnection = new ServiceConnection() {
         public void onServiceConnected(ComponentName className, IBinder service) {
             if (DBG) Log.d(TAG, "Proxy object connected");
-            mService = IBluetoothInputDevice.Stub.asInterface(Binder.allowBlocking(service));
+            mService = IBluetoothHidHost.Stub.asInterface(Binder.allowBlocking(service));
 
             if (mServiceListener != null) {
-                mServiceListener.onServiceConnected(BluetoothProfile.INPUT_DEVICE,
-                        BluetoothInputDevice.this);
+                mServiceListener.onServiceConnected(BluetoothProfile.HID_HOST,
+                        BluetoothHidHost.this);
             }
         }
 
@@ -518,7 +518,7 @@
             if (DBG) Log.d(TAG, "Proxy object disconnected");
             mService = null;
             if (mServiceListener != null) {
-                mServiceListener.onServiceDisconnected(BluetoothProfile.INPUT_DEVICE);
+                mServiceListener.onServiceDisconnected(BluetoothProfile.HID_HOST);
             }
         }
     };
@@ -542,7 +542,7 @@
      */
     public boolean virtualUnplug(BluetoothDevice device) {
         if (DBG) log("virtualUnplug(" + device + ")");
-        final IBluetoothInputDevice service = mService;
+        final IBluetoothHidHost service = mService;
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
                 return service.virtualUnplug(device);
@@ -568,7 +568,7 @@
      */
     public boolean getProtocolMode(BluetoothDevice device) {
         if (VDBG) log("getProtocolMode(" + device + ")");
-        final IBluetoothInputDevice service = mService;
+        final IBluetoothHidHost service = mService;
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
                 return service.getProtocolMode(device);
@@ -592,7 +592,7 @@
      */
     public boolean setProtocolMode(BluetoothDevice device, int protocolMode) {
         if (DBG) log("setProtocolMode(" + device + ")");
-        final IBluetoothInputDevice service = mService;
+        final IBluetoothHidHost service = mService;
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
                 return service.setProtocolMode(device, protocolMode);
@@ -623,7 +623,7 @@
             log("getReport(" + device + "), reportType=" + reportType + " reportId=" + reportId
                     + "bufferSize=" + bufferSize);
         }
-        final IBluetoothInputDevice service = mService;
+        final IBluetoothHidHost service = mService;
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
                 return service.getReport(device, reportType, reportId, bufferSize);
@@ -649,7 +649,7 @@
      */
     public boolean setReport(BluetoothDevice device, byte reportType, String report) {
         if (VDBG) log("setReport(" + device + "), reportType=" + reportType + " report=" + report);
-        final IBluetoothInputDevice service = mService;
+        final IBluetoothHidHost service = mService;
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
                 return service.setReport(device, reportType, report);
@@ -674,7 +674,7 @@
      */
     public boolean sendData(BluetoothDevice device, String report) {
         if (DBG) log("sendData(" + device + "), report=" + report);
-        final IBluetoothInputDevice service = mService;
+        final IBluetoothHidHost service = mService;
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
                 return service.sendData(device, report);
@@ -698,7 +698,7 @@
      */
     public boolean getIdleTime(BluetoothDevice device) {
         if (DBG) log("getIdletime(" + device + ")");
-        final IBluetoothInputDevice service = mService;
+        final IBluetoothHidHost service = mService;
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
                 return service.getIdleTime(device);
@@ -723,7 +723,7 @@
      */
     public boolean setIdleTime(BluetoothDevice device, byte idleTime) {
         if (DBG) log("setIdletime(" + device + "), idleTime=" + idleTime);
-        final IBluetoothInputDevice service = mService;
+        final IBluetoothHidHost service = mService;
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
                 return service.setIdleTime(device, idleTime);
diff --git a/android/bluetooth/BluetoothProfile.java b/android/bluetooth/BluetoothProfile.java
index bc8fa84..46a230b 100644
--- a/android/bluetooth/BluetoothProfile.java
+++ b/android/bluetooth/BluetoothProfile.java
@@ -73,11 +73,11 @@
     public static final int HEALTH = 3;
 
     /**
-     * Input Device Profile
+     * HID Host
      *
      * @hide
      */
-    public static final int INPUT_DEVICE = 4;
+    public static final int HID_HOST = 4;
 
     /**
      * PAN Profile
@@ -152,11 +152,11 @@
     public static final int MAP_CLIENT = 18;
 
     /**
-     * Input Host
+     * HID Device
      *
      * @hide
      */
-    public static final int INPUT_HOST = 19;
+    public static final int HID_DEVICE = 19;
 
     /**
      * Max profile ID. This value should be updated whenever a new profile is added to match
diff --git a/android/content/ContentProvider.java b/android/content/ContentProvider.java
index 5b2bf45..cdeaea3 100644
--- a/android/content/ContentProvider.java
+++ b/android/content/ContentProvider.java
@@ -2099,8 +2099,7 @@
     public static Uri maybeAddUserId(Uri uri, int userId) {
         if (uri == null) return null;
         if (userId != UserHandle.USER_CURRENT
-                && (ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())
-                        || ContentResolver.SCHEME_SLICE.equals(uri.getScheme()))) {
+                && ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) {
             if (!uriHasUserId(uri)) {
                 //We don't add the user Id if there's already one
                 Uri.Builder builder = uri.buildUpon();
diff --git a/android/content/ContentResolver.java b/android/content/ContentResolver.java
index 02e70f5..9ccc552 100644
--- a/android/content/ContentResolver.java
+++ b/android/content/ContentResolver.java
@@ -47,8 +47,6 @@
 import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.UserHandle;
-import android.slice.Slice;
-import android.slice.SliceProvider;
 import android.text.TextUtils;
 import android.util.EventLog;
 import android.util.Log;
@@ -180,8 +178,6 @@
     public static final Intent ACTION_SYNC_CONN_STATUS_CHANGED =
             new Intent("com.android.sync.SYNC_CONN_STATUS_CHANGED");
 
-    /** @hide */
-    public static final String SCHEME_SLICE = "slice";
     public static final String SCHEME_CONTENT = "content";
     public static final String SCHEME_ANDROID_RESOURCE = "android.resource";
     public static final String SCHEME_FILE = "file";
@@ -1722,36 +1718,6 @@
     }
 
     /**
-     * Turns a slice Uri into slice content.
-     *
-     * @param uri The URI to a slice provider
-     * @return The Slice provided by the app or null if none is given.
-     * @see Slice
-     * @hide
-     */
-    public final @Nullable Slice bindSlice(@NonNull Uri uri) {
-        Preconditions.checkNotNull(uri, "uri");
-        IContentProvider provider = acquireProvider(uri);
-        if (provider == null) {
-            throw new IllegalArgumentException("Unknown URI " + uri);
-        }
-        try {
-            Bundle extras = new Bundle();
-            extras.putParcelable(SliceProvider.EXTRA_BIND_URI, uri);
-            final Bundle res = provider.call(mPackageName, SliceProvider.METHOD_SLICE, null,
-                    extras);
-            Bundle.setDefusable(res, true);
-            return res.getParcelable(SliceProvider.EXTRA_SLICE);
-        } catch (RemoteException e) {
-            // Arbitrary and not worth documenting, as Activity
-            // Manager will kill this process shortly anyway.
-            return null;
-        } finally {
-            releaseProvider(provider);
-        }
-    }
-
-    /**
      * Returns the content provider for the given content URI.
      *
      * @param uri The URI to a content provider
@@ -1759,7 +1725,7 @@
      * @hide
      */
     public final IContentProvider acquireProvider(Uri uri) {
-        if (!SCHEME_CONTENT.equals(uri.getScheme()) && !SCHEME_SLICE.equals(uri.getScheme())) {
+        if (!SCHEME_CONTENT.equals(uri.getScheme())) {
             return null;
         }
         final String auth = uri.getAuthority();
diff --git a/android/content/Context.java b/android/content/Context.java
index 20fbf04..c165fb3 100644
--- a/android/content/Context.java
+++ b/android/content/Context.java
@@ -3604,7 +3604,6 @@
 
     /**
      * Use with {@link #getSystemService} to retrieve a
-     * {@link android.text.ClipboardManager} for accessing and modifying
      * {@link android.content.ClipboardManager} for accessing and modifying
      * the contents of the global clipboard.
      *
diff --git a/android/content/Intent.java b/android/content/Intent.java
index c9ad951..e47de75 100644
--- a/android/content/Intent.java
+++ b/android/content/Intent.java
@@ -53,6 +53,7 @@
 import android.util.ArraySet;
 import android.util.AttributeSet;
 import android.util.Log;
+import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.util.XmlUtils;
 
@@ -9371,6 +9372,57 @@
         }
     }
 
+    /** @hide */
+    public void writeToProto(ProtoOutputStream proto, long fieldId, boolean secure, boolean comp,
+            boolean extras, boolean clip) {
+        long token = proto.start(fieldId);
+        if (mAction != null) {
+            proto.write(IntentProto.ACTION, mAction);
+        }
+        if (mCategories != null)  {
+            for (String category : mCategories) {
+                proto.write(IntentProto.CATEGORIES, category);
+            }
+        }
+        if (mData != null) {
+            proto.write(IntentProto.DATA, secure ? mData.toSafeString() : mData.toString());
+        }
+        if (mType != null) {
+            proto.write(IntentProto.TYPE, mType);
+        }
+        if (mFlags != 0) {
+            proto.write(IntentProto.FLAG, "0x" + Integer.toHexString(mFlags));
+        }
+        if (mPackage != null) {
+            proto.write(IntentProto.PACKAGE, mPackage);
+        }
+        if (comp && mComponent != null) {
+            proto.write(IntentProto.COMPONENT, mComponent.flattenToShortString());
+        }
+        if (mSourceBounds != null) {
+            proto.write(IntentProto.SOURCE_BOUNDS, mSourceBounds.toShortString());
+        }
+        if (mClipData != null) {
+            StringBuilder b = new StringBuilder();
+            if (clip) {
+                mClipData.toShortString(b);
+            } else {
+                mClipData.toShortStringShortItems(b, false);
+            }
+            proto.write(IntentProto.CLIP_DATA, b.toString());
+        }
+        if (extras && mExtras != null) {
+            proto.write(IntentProto.EXTRAS, mExtras.toShortString());
+        }
+        if (mContentUserHint != 0) {
+            proto.write(IntentProto.CONTENT_USER_HINT, mContentUserHint);
+        }
+        if (mSelector != null) {
+            proto.write(IntentProto.SELECTOR, mSelector.toShortString(secure, comp, extras, clip));
+        }
+        proto.end(token);
+    }
+
     /**
      * Call {@link #toUri} with 0 flags.
      * @deprecated Use {@link #toUri} instead.
diff --git a/android/content/IntentFilter.java b/android/content/IntentFilter.java
index c9bce53..a957aed 100644
--- a/android/content/IntentFilter.java
+++ b/android/content/IntentFilter.java
@@ -26,6 +26,7 @@
 import android.util.AndroidException;
 import android.util.Log;
 import android.util.Printer;
+import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.util.XmlUtils;
 
@@ -918,6 +919,15 @@
             dest.writeInt(mPort);
         }
 
+        void writeToProto(ProtoOutputStream proto, long fieldId) {
+            long token = proto.start(fieldId);
+            // The original host information is already contained in host and wild, no output now.
+            proto.write(AuthorityEntryProto.HOST, mHost);
+            proto.write(AuthorityEntryProto.WILD, mWild);
+            proto.write(AuthorityEntryProto.PORT, mPort);
+            proto.end(token);
+        }
+
         public String getHost() {
             return mOrigHost;
         }
@@ -1739,6 +1749,59 @@
         }
     }
 
+    /** @hide */
+    public void writeToProto(ProtoOutputStream proto, long fieldId) {
+        long token = proto.start(fieldId);
+        if (mActions.size() > 0) {
+            Iterator<String> it = mActions.iterator();
+            while (it.hasNext()) {
+                proto.write(IntentFilterProto.ACTIONS, it.next());
+            }
+        }
+        if (mCategories != null) {
+            Iterator<String> it = mCategories.iterator();
+            while (it.hasNext()) {
+                proto.write(IntentFilterProto.CATEGORIES, it.next());
+            }
+        }
+        if (mDataSchemes != null) {
+            Iterator<String> it = mDataSchemes.iterator();
+            while (it.hasNext()) {
+                proto.write(IntentFilterProto.DATA_SCHEMES, it.next());
+            }
+        }
+        if (mDataSchemeSpecificParts != null) {
+            Iterator<PatternMatcher> it = mDataSchemeSpecificParts.iterator();
+            while (it.hasNext()) {
+                it.next().writeToProto(proto, IntentFilterProto.DATA_SCHEME_SPECS);
+            }
+        }
+        if (mDataAuthorities != null) {
+            Iterator<AuthorityEntry> it = mDataAuthorities.iterator();
+            while (it.hasNext()) {
+                it.next().writeToProto(proto, IntentFilterProto.DATA_AUTHORITIES);
+            }
+        }
+        if (mDataPaths != null) {
+            Iterator<PatternMatcher> it = mDataPaths.iterator();
+            while (it.hasNext()) {
+                it.next().writeToProto(proto, IntentFilterProto.DATA_PATHS);
+            }
+        }
+        if (mDataTypes != null) {
+            Iterator<String> it = mDataTypes.iterator();
+            while (it.hasNext()) {
+                proto.write(IntentFilterProto.DATA_TYPES, it.next());
+            }
+        }
+        if (mPriority != 0 || mHasPartialTypes) {
+            proto.write(IntentFilterProto.PRIORITY, mPriority);
+            proto.write(IntentFilterProto.HAS_PARTIAL_TYPES, mHasPartialTypes);
+        }
+        proto.write(IntentFilterProto.GET_AUTO_VERIFY, getAutoVerify());
+        proto.end(token);
+    }
+
     public void dump(Printer du, String prefix) {
         StringBuilder sb = new StringBuilder(256);
         if (mActions.size() > 0) {
diff --git a/android/content/pm/FeatureInfo.java b/android/content/pm/FeatureInfo.java
index 9ee6fa2..ff9fd8e 100644
--- a/android/content/pm/FeatureInfo.java
+++ b/android/content/pm/FeatureInfo.java
@@ -18,6 +18,7 @@
 
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.util.proto.ProtoOutputStream;
 
 /**
  * Definition of a single optional hardware or software feature of an Android
@@ -113,6 +114,18 @@
         dest.writeInt(flags);
     }
 
+    /** @hide */
+    public void writeToProto(ProtoOutputStream proto, long fieldId) {
+        long token = proto.start(fieldId);
+        if (name != null) {
+            proto.write(FeatureInfoProto.NAME, name);
+        }
+        proto.write(FeatureInfoProto.VERSION, version);
+        proto.write(FeatureInfoProto.GLES_VERSION, getGlEsVersion());
+        proto.write(FeatureInfoProto.FLAGS, flags);
+        proto.end(token);
+    }
+
     public static final Creator<FeatureInfo> CREATOR = new Creator<FeatureInfo>() {
         @Override
         public FeatureInfo createFromParcel(Parcel source) {
diff --git a/android/content/pm/LauncherApps.java b/android/content/pm/LauncherApps.java
index aa9562f..b94a410 100644
--- a/android/content/pm/LauncherApps.java
+++ b/android/content/pm/LauncherApps.java
@@ -20,8 +20,8 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SdkConstant;
-import android.annotation.SystemService;
 import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SystemService;
 import android.annotation.TestApi;
 import android.app.PendingIntent;
 import android.appwidget.AppWidgetManager;
@@ -37,10 +37,10 @@
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.graphics.Rect;
+import android.graphics.drawable.AdaptiveIconDrawable;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Icon;
-import android.graphics.drawable.AdaptiveIconDrawable;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
@@ -282,12 +282,27 @@
         public static final int FLAG_GET_MANIFEST = FLAG_MATCH_MANIFEST;
 
         /**
-         * Does not retrieve CHOOSER only shortcuts.
-         * TODO: Add another flag for MATCH_ALL_PINNED
+         * @hide include all pinned shortcuts by any launchers, not just by the caller,
+         * in the result.
+         * If the caller doesn't havve the {@link android.Manifest.permission#ACCESS_SHORTCUTS}
+         * permission, this flag will be ignored.
+         */
+        @TestApi
+        public static final int FLAG_MATCH_ALL_PINNED = 1 << 10;
+
+        /**
+         * FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED | FLAG_MATCH_MANIFEST
          * @hide
          */
         public static final int FLAG_MATCH_ALL_KINDS =
-                FLAG_GET_DYNAMIC | FLAG_GET_PINNED | FLAG_GET_MANIFEST;
+                FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED | FLAG_MATCH_MANIFEST;
+
+        /**
+         * FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED | FLAG_MATCH_MANIFEST | FLAG_MATCH_ALL_PINNED
+         * @hide
+         */
+        public static final int FLAG_MATCH_ALL_KINDS_WITH_ALL_PINNED =
+                FLAG_MATCH_ALL_KINDS | FLAG_MATCH_ALL_PINNED;
 
         /** @hide kept for unit tests */
         @Deprecated
@@ -319,6 +334,7 @@
                         FLAG_MATCH_PINNED,
                         FLAG_MATCH_MANIFEST,
                         FLAG_GET_KEY_FIELDS_ONLY,
+                        FLAG_MATCH_MANIFEST,
                 })
         @Retention(RetentionPolicy.SOURCE)
         public @interface QueryFlags {}
@@ -678,6 +694,21 @@
         }
     }
 
+    private List<ShortcutInfo> maybeUpdateDisabledMessage(List<ShortcutInfo> shortcuts) {
+        if (shortcuts == null) {
+            return null;
+        }
+        for (int i = shortcuts.size() - 1; i >= 0; i--) {
+            final ShortcutInfo si = shortcuts.get(i);
+            final String message = ShortcutInfo.getDisabledReasonForRestoreIssue(mContext,
+                    si.getDisabledReason());
+            if (message != null) {
+                si.setDisabledMessage(message);
+            }
+        }
+        return shortcuts;
+    }
+
     /**
      * Returns {@link ShortcutInfo}s that match {@code query}.
      *
@@ -698,10 +729,16 @@
             @NonNull UserHandle user) {
         logErrorForInvalidProfileAccess(user);
         try {
-            return mService.getShortcuts(mContext.getPackageName(),
+            // Note this is the only case we need to update the disabled message for shortcuts
+            // that weren't restored.
+            // The restore problem messages are only shown by the user, and publishers will never
+            // see them. The only other API that the launcher gets shortcuts is the shortcut
+            // changed callback, but that only returns shortcuts with the "key" information, so
+            // that won't return disabled message.
+            return maybeUpdateDisabledMessage(mService.getShortcuts(mContext.getPackageName(),
                     query.mChangedSince, query.mPackage, query.mShortcutIds, query.mActivity,
                     query.mQueryFlags, user)
-                    .getList();
+                    .getList());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/android/content/pm/PackageManagerInternal.java b/android/content/pm/PackageManagerInternal.java
index be7f921..143c51d 100644
--- a/android/content/pm/PackageManagerInternal.java
+++ b/android/content/pm/PackageManagerInternal.java
@@ -467,6 +467,7 @@
     /** Updates the flags for the given permission. */
     public abstract void updatePermissionFlagsTEMP(@NonNull String permName,
             @NonNull String packageName, int flagMask, int flagValues, int userId);
-    /** temporary until mPermissionTrees is moved to PermissionManager */
-    public abstract Object enforcePermissionTreeTEMP(@NonNull String permName, int callingUid);
+    /** Returns a PermissionGroup. */
+    public abstract @Nullable PackageParser.PermissionGroup getPermissionGroupTEMP(
+            @NonNull String groupName);
 }
diff --git a/android/content/pm/PackageParser.java b/android/content/pm/PackageParser.java
index 6c7c8a0..ad36139 100644
--- a/android/content/pm/PackageParser.java
+++ b/android/content/pm/PackageParser.java
@@ -3711,17 +3711,15 @@
                 ai.flags |= ApplicationInfo.FLAG_IS_GAME;
             }
 
-            if (false) {
-                if (sa.getBoolean(
-                        com.android.internal.R.styleable.AndroidManifestApplication_cantSaveState,
-                        false)) {
-                    ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE;
+            if (sa.getBoolean(
+                    com.android.internal.R.styleable.AndroidManifestApplication_cantSaveState,
+                    false)) {
+                ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE;
 
-                    // A heavy-weight application can not be in a custom process.
-                    // We can do direct compare because we intern all strings.
-                    if (ai.processName != null && ai.processName != ai.packageName) {
-                        outError[0] = "cantSaveState applications can not use custom processes";
-                    }
+                // A heavy-weight application can not be in a custom process.
+                // We can do direct compare because we intern all strings.
+                if (ai.processName != null && !ai.processName.equals(ai.packageName)) {
+                    outError[0] = "cantSaveState applications can not use custom processes";
                 }
             }
         }
@@ -6849,6 +6847,11 @@
             dest.writeParcelable(group, flags);
         }
 
+        /** @hide */
+        public boolean isAppOp() {
+            return info.isAppOp();
+        }
+
         private Permission(Parcel in) {
             super(in);
             final ClassLoader boot = Object.class.getClassLoader();
diff --git a/android/content/pm/PermissionInfo.java b/android/content/pm/PermissionInfo.java
index b45c26c..5dd7aed 100644
--- a/android/content/pm/PermissionInfo.java
+++ b/android/content/pm/PermissionInfo.java
@@ -353,6 +353,11 @@
         return size;
     }
 
+    /** @hide */
+    public boolean isAppOp() {
+        return (protectionLevel & PermissionInfo.PROTECTION_FLAG_APPOP) != 0;
+    }
+
     public static final Creator<PermissionInfo> CREATOR =
         new Creator<PermissionInfo>() {
         @Override
diff --git a/android/content/pm/ResolveInfo.java b/android/content/pm/ResolveInfo.java
index 7993167..3f63d80 100644
--- a/android/content/pm/ResolveInfo.java
+++ b/android/content/pm/ResolveInfo.java
@@ -222,6 +222,40 @@
     }
 
     /**
+     * @return The resource that would be used when loading
+     * the label for this resolve info.
+     *
+     * @hide
+     */
+    public int resolveLabelResId() {
+        if (labelRes != 0) {
+            return labelRes;
+        }
+        final ComponentInfo componentInfo = getComponentInfo();
+        if (componentInfo.labelRes != 0) {
+            return componentInfo.labelRes;
+        }
+        return componentInfo.applicationInfo.labelRes;
+    }
+
+    /**
+     * @return The resource that would be used when loading
+     * the icon for this resolve info.
+     *
+     * @hide
+     */
+    public int resolveIconResId() {
+        if (icon != 0) {
+            return icon;
+        }
+        final ComponentInfo componentInfo = getComponentInfo();
+        if (componentInfo.icon != 0) {
+            return componentInfo.icon;
+        }
+        return componentInfo.applicationInfo.icon;
+    }
+
+    /**
      * Retrieve the current graphical icon associated with this resolution.  This
      * will call back on the given PackageManager to load the icon from
      * the application.
diff --git a/android/content/pm/ShortcutInfo.java b/android/content/pm/ShortcutInfo.java
index 6b9c753..9ff0775 100644
--- a/android/content/pm/ShortcutInfo.java
+++ b/android/content/pm/ShortcutInfo.java
@@ -18,6 +18,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.TestApi;
 import android.annotation.UserIdInt;
 import android.app.TaskStackBuilder;
 import android.content.ComponentName;
@@ -100,6 +101,13 @@
     /** @hide When this is set, the bitmap icon is waiting to be saved. */
     public static final int FLAG_ICON_FILE_PENDING_SAVE = 1 << 11;
 
+    /**
+     * "Shadow" shortcuts are the ones that are restored, but the owner package hasn't been
+     * installed yet.
+     * @hide
+     */
+    public static final int FLAG_SHADOW = 1 << 12;
+
     /** @hide */
     @IntDef(flag = true,
             value = {
@@ -158,6 +166,124 @@
     public @interface CloneFlags {}
 
     /**
+     * Shortcut is not disabled.
+     */
+    public static final int DISABLED_REASON_NOT_DISABLED = 0;
+
+    /**
+     * Shortcut has been disabled by the publisher app with the
+     * {@link ShortcutManager#disableShortcuts(List)} API.
+     */
+    public static final int DISABLED_REASON_BY_APP = 1;
+
+    /**
+     * Shortcut has been disabled due to changes to the publisher app. (e.g. a manifest shortcut
+     * no longer exists.)
+     */
+    public static final int DISABLED_REASON_APP_CHANGED = 2;
+
+    /**
+     * A disabled reason that's equal to or bigger than this is due to backup and restore issue.
+     * A shortcut with such a reason wil be visible to the launcher, but not to the publisher.
+     * ({@link #isVisibleToPublisher()} will be false.)
+     */
+    private static final int DISABLED_REASON_RESTORE_ISSUE_START = 100;
+
+    /**
+     * Shortcut has been restored from the previous device, but the publisher app on the current
+     * device is of a lower version. The shortcut will not be usable until the app is upgraded to
+     * the same version or higher.
+     */
+    public static final int DISABLED_REASON_VERSION_LOWER = 100;
+
+    /**
+     * Shortcut has not been restored because the publisher app does not support backup and restore.
+     */
+    public static final int DISABLED_REASON_BACKUP_NOT_SUPPORTED = 101;
+
+    /**
+     * Shortcut has not been restored because the publisher app's signature has changed.
+     */
+    public static final int DISABLED_REASON_SIGNATURE_MISMATCH = 102;
+
+    /**
+     * Shortcut has not been restored for unknown reason.
+     */
+    public static final int DISABLED_REASON_OTHER_RESTORE_ISSUE = 103;
+
+    /** @hide */
+    @IntDef(value = {
+            DISABLED_REASON_NOT_DISABLED,
+            DISABLED_REASON_BY_APP,
+            DISABLED_REASON_APP_CHANGED,
+            DISABLED_REASON_VERSION_LOWER,
+            DISABLED_REASON_BACKUP_NOT_SUPPORTED,
+            DISABLED_REASON_SIGNATURE_MISMATCH,
+            DISABLED_REASON_OTHER_RESTORE_ISSUE,
+            })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface DisabledReason{}
+
+    /**
+     * Return a label for disabled reasons, which are *not* supposed to be shown to the user.
+     * @hide
+     */
+    public static String getDisabledReasonDebugString(@DisabledReason int disabledReason) {
+        switch (disabledReason) {
+            case DISABLED_REASON_NOT_DISABLED:
+                return "[Not disabled]";
+            case DISABLED_REASON_BY_APP:
+                return "[Disabled: by app]";
+            case DISABLED_REASON_APP_CHANGED:
+                return "[Disabled: app changed]";
+            case DISABLED_REASON_VERSION_LOWER:
+                return "[Disabled: lower version]";
+            case DISABLED_REASON_BACKUP_NOT_SUPPORTED:
+                return "[Disabled: backup not supported]";
+            case DISABLED_REASON_SIGNATURE_MISMATCH:
+                return "[Disabled: signature mismatch]";
+            case DISABLED_REASON_OTHER_RESTORE_ISSUE:
+                return "[Disabled: unknown restore issue]";
+        }
+        return "[Disabled: unknown reason:" + disabledReason + "]";
+    }
+
+    /**
+     * Return a label for a disabled reason for shortcuts that are disabled due to a backup and
+     * restore issue. If the reason is not due to backup & restore, then it'll return null.
+     *
+     * This method returns localized, user-facing strings, which will be returned by
+     * {@link #getDisabledMessage()}.
+     *
+     * @hide
+     */
+    public static String getDisabledReasonForRestoreIssue(Context context,
+            @DisabledReason int disabledReason) {
+        final Resources res = context.getResources();
+
+        switch (disabledReason) {
+            case DISABLED_REASON_VERSION_LOWER:
+                return res.getString(
+                        com.android.internal.R.string.shortcut_restored_on_lower_version);
+            case DISABLED_REASON_BACKUP_NOT_SUPPORTED:
+                return res.getString(
+                        com.android.internal.R.string.shortcut_restore_not_supported);
+            case DISABLED_REASON_SIGNATURE_MISMATCH:
+                return res.getString(
+                        com.android.internal.R.string.shortcut_restore_signature_mismatch);
+            case DISABLED_REASON_OTHER_RESTORE_ISSUE:
+                return res.getString(
+                        com.android.internal.R.string.shortcut_restore_unknown_issue);
+        }
+        return null;
+    }
+
+    /** @hide */
+    public static boolean isDisabledForRestoreIssue(@DisabledReason int disabledReason) {
+        return disabledReason >= DISABLED_REASON_RESTORE_ISSUE_START;
+    }
+
+    /**
      * Shortcut category for messaging related actions, such as chat.
      */
     public static final String SHORTCUT_CATEGORY_CONVERSATION = "android.shortcut.conversation";
@@ -240,6 +366,11 @@
 
     private final int mUserId;
 
+    /** @hide */
+    public static final int VERSION_CODE_UNKNOWN = -1;
+
+    private int mDisabledReason;
+
     private ShortcutInfo(Builder b) {
         mUserId = b.mContext.getUserId();
 
@@ -352,6 +483,7 @@
         mActivity = source.mActivity;
         mFlags = source.mFlags;
         mLastChangedTimestamp = source.mLastChangedTimestamp;
+        mDisabledReason = source.mDisabledReason;
 
         // Just always keep it since it's cheep.
         mIconResId = source.mIconResId;
@@ -615,13 +747,23 @@
 
     /**
      * @hide
+     *
+     * @isUpdating set true if it's "update", as opposed to "replace".
      */
-    public void ensureUpdatableWith(ShortcutInfo source) {
+    public void ensureUpdatableWith(ShortcutInfo source, boolean isUpdating) {
+        if (isUpdating) {
+            Preconditions.checkState(isVisibleToPublisher(),
+                    "[Framework BUG] Invisible shortcuts can't be updated");
+        }
         Preconditions.checkState(mUserId == source.mUserId, "Owner User ID must match");
         Preconditions.checkState(mId.equals(source.mId), "ID must match");
         Preconditions.checkState(mPackageName.equals(source.mPackageName),
                 "Package name must match");
-        Preconditions.checkState(!isImmutable(), "Target ShortcutInfo is immutable");
+
+        if (isVisibleToPublisher()) {
+            // Don't do this check for restore-blocked shortcuts.
+            Preconditions.checkState(!isImmutable(), "Target ShortcutInfo is immutable");
+        }
     }
 
     /**
@@ -638,7 +780,7 @@
      * @hide
      */
     public void copyNonNullFieldsFrom(ShortcutInfo source) {
-        ensureUpdatableWith(source);
+        ensureUpdatableWith(source, /*isUpdating=*/ true);
 
         if (source.mActivity != null) {
             mActivity = source.mActivity;
@@ -1169,6 +1311,19 @@
         return mDisabledMessageResId;
     }
 
+    /** @hide */
+    public void setDisabledReason(@DisabledReason int reason) {
+        mDisabledReason = reason;
+    }
+
+    /**
+     * Returns why a shortcut has been disabled.
+     */
+    @DisabledReason
+    public int getDisabledReason() {
+        return mDisabledReason;
+    }
+
     /**
      * Return the shortcut's categories.
      *
@@ -1403,6 +1558,21 @@
         return hasFlags(FLAG_IMMUTABLE);
     }
 
+    /** @hide */
+    public boolean isDynamicVisible() {
+        return isDynamic() && isVisibleToPublisher();
+    }
+
+    /** @hide */
+    public boolean isPinnedVisible() {
+        return isPinned() && isVisibleToPublisher();
+    }
+
+    /** @hide */
+    public boolean isManifestVisible() {
+        return isDeclaredInManifest() && isVisibleToPublisher();
+    }
+
     /**
      * Return if a shortcut is immutable, in which case it cannot be modified with any of
      * {@link ShortcutManager} APIs.
@@ -1491,6 +1661,18 @@
     }
 
     /**
+     * When the system wasn't able to restore a shortcut, it'll still be registered to the system
+     * but disabled, and such shortcuts will not be visible to the publisher. They're still visible
+     * to launchers though.
+     *
+     * @hide
+     */
+    @TestApi
+    public boolean isVisibleToPublisher() {
+        return !isDisabledForRestoreIssue(mDisabledReason);
+    }
+
+    /**
      * Return whether a shortcut only contains "key" information only or not.  If true, only the
      * following fields are available.
      * <ul>
@@ -1668,6 +1850,7 @@
         mFlags = source.readInt();
         mIconResId = source.readInt();
         mLastChangedTimestamp = source.readLong();
+        mDisabledReason = source.readInt();
 
         if (source.readInt() == 0) {
             return; // key information only.
@@ -1711,6 +1894,7 @@
         dest.writeInt(mFlags);
         dest.writeInt(mIconResId);
         dest.writeLong(mLastChangedTimestamp);
+        dest.writeInt(mDisabledReason);
 
         if (hasKeyFieldsOnly()) {
             dest.writeInt(0);
@@ -1808,6 +1992,11 @@
         sb.append(", flags=0x");
         sb.append(Integer.toHexString(mFlags));
         sb.append(" [");
+        if ((mFlags & FLAG_SHADOW) != 0) {
+            // Note the shadow flag isn't actually used anywhere and it's just for dumpsys, so
+            // we don't have an isXxx for this.
+            sb.append("Sdw");
+        }
         if (!isEnabled()) {
             sb.append("Dis");
         }
@@ -1848,7 +2037,9 @@
         sb.append("packageName=");
         sb.append(mPackageName);
 
-        sb.append(", activity=");
+        addIndentOrComma(sb, indent);
+
+        sb.append("activity=");
         sb.append(mActivity);
 
         addIndentOrComma(sb, indent);
@@ -1883,6 +2074,11 @@
 
         addIndentOrComma(sb, indent);
 
+        sb.append("disabledReason=");
+        sb.append(getDisabledReasonDebugString(mDisabledReason));
+
+        addIndentOrComma(sb, indent);
+
         sb.append("categories=");
         sb.append(mCategories);
 
@@ -1953,7 +2149,7 @@
             CharSequence disabledMessage, int disabledMessageResId, String disabledMessageResName,
             Set<String> categories, Intent[] intentsWithExtras, int rank, PersistableBundle extras,
             long lastChangedTimestamp,
-            int flags, int iconResId, String iconResName, String bitmapPath) {
+            int flags, int iconResId, String iconResName, String bitmapPath, int disabledReason) {
         mUserId = userId;
         mId = id;
         mPackageName = packageName;
@@ -1978,5 +2174,6 @@
         mIconResId = iconResId;
         mIconResName = iconResName;
         mBitmapPath = bitmapPath;
+        mDisabledReason = disabledReason;
     }
 }
diff --git a/android/content/pm/ShortcutServiceInternal.java b/android/content/pm/ShortcutServiceInternal.java
index 7b7d8ae..7fc25d8 100644
--- a/android/content/pm/ShortcutServiceInternal.java
+++ b/android/content/pm/ShortcutServiceInternal.java
@@ -46,7 +46,7 @@
             @NonNull String callingPackage, long changedSince,
             @Nullable String packageName, @Nullable List<String> shortcutIds,
             @Nullable ComponentName componentName, @ShortcutQuery.QueryFlags int flags,
-            int userId);
+            int userId, int callingPid, int callingUid);
 
     public abstract boolean
             isPinnedByCaller(int launcherUserId, @NonNull String callingPackage,
@@ -58,7 +58,8 @@
 
     public abstract Intent[] createShortcutIntents(
             int launcherUserId, @NonNull String callingPackage,
-            @NonNull String packageName, @NonNull String shortcutId, int userId);
+            @NonNull String packageName, @NonNull String shortcutId, int userId,
+            int callingPid, int callingUid);
 
     public abstract void addListener(@NonNull ShortcutChangeListener listener);
 
@@ -70,7 +71,7 @@
             @NonNull String packageName, @NonNull String shortcutId, int userId);
 
     public abstract boolean hasShortcutHostPermission(int launcherUserId,
-            @NonNull String callingPackage);
+            @NonNull String callingPackage, int callingPid, int callingUid);
 
     public abstract boolean requestPinAppWidget(@NonNull String callingPackage,
             @NonNull AppWidgetProviderInfo appWidget, @Nullable Bundle extras,
diff --git a/android/content/res/FontResourcesParser.java b/android/content/res/FontResourcesParser.java
index 042eb87..28e9fce 100644
--- a/android/content/res/FontResourcesParser.java
+++ b/android/content/res/FontResourcesParser.java
@@ -15,7 +15,6 @@
  */
 package android.content.res;
 
-import com.android.internal.R;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.graphics.Typeface;
@@ -23,6 +22,8 @@
 import android.util.Log;
 import android.util.Xml;
 
+import com.android.internal.R;
+
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
@@ -78,12 +79,15 @@
         private final @NonNull String mFileName;
         private int mWeight;
         private int mItalic;
+        private int mTtcIndex;
         private int mResourceId;
 
-        public FontFileResourceEntry(@NonNull String fileName, int weight, int italic) {
+        public FontFileResourceEntry(@NonNull String fileName, int weight, int italic,
+                int ttcIndex) {
             mFileName = fileName;
             mWeight = weight;
             mItalic = italic;
+            mTtcIndex = ttcIndex;
         }
 
         public @NonNull String getFileName() {
@@ -97,6 +101,10 @@
         public int getItalic() {
             return mItalic;
         }
+
+        public int getTtcIndex() {
+            return mTtcIndex;
+        }
     }
 
     // A class represents file based font-family element in xml file.
@@ -203,6 +211,7 @@
                 Typeface.RESOLVE_BY_FONT_TABLE);
         int italic = array.getInt(R.styleable.FontFamilyFont_fontStyle,
                 Typeface.RESOLVE_BY_FONT_TABLE);
+        int ttcIndex = array.getInt(R.styleable.FontFamilyFont_ttcIndex, 0);
         String filename = array.getString(R.styleable.FontFamilyFont_font);
         array.recycle();
         while (parser.next() != XmlPullParser.END_TAG) {
@@ -211,7 +220,7 @@
         if (filename == null) {
             return null;
         }
-        return new FontFileResourceEntry(filename, weight, italic);
+        return new FontFileResourceEntry(filename, weight, italic, ttcIndex);
     }
 
     private static void skip(XmlPullParser parser) throws XmlPullParserException, IOException {
diff --git a/android/content/res/ResourcesImpl.java b/android/content/res/ResourcesImpl.java
index a8b8c4b..386239c 100644
--- a/android/content/res/ResourcesImpl.java
+++ b/android/content/res/ResourcesImpl.java
@@ -796,7 +796,7 @@
                 dr = Drawable.createFromResourceStream(wrapper, value, is, file, null);
                 is.close();
             }
-        } catch (Exception e) {
+        } catch (Exception | StackOverflowError e) {
             Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
             final NotFoundException rnf = new NotFoundException(
                     "File " + file + " from drawable resource ID #0x" + Integer.toHexString(id));
diff --git a/android/database/CursorWindow.java b/android/database/CursorWindow.java
index a75372f..f84ec65 100644
--- a/android/database/CursorWindow.java
+++ b/android/database/CursorWindow.java
@@ -16,8 +16,7 @@
 
 package android.database;
 
-import dalvik.system.CloseGuard;
-
+import android.annotation.BytesLong;
 import android.content.res.Resources;
 import android.database.sqlite.SQLiteClosable;
 import android.database.sqlite.SQLiteException;
@@ -26,8 +25,10 @@
 import android.os.Parcelable;
 import android.os.Process;
 import android.util.Log;
-import android.util.SparseIntArray;
 import android.util.LongSparseArray;
+import android.util.SparseIntArray;
+
+import dalvik.system.CloseGuard;
 
 /**
  * A buffer containing multiple cursor rows.
@@ -94,19 +95,29 @@
      * @param name The name of the cursor window, or null if none.
      */
     public CursorWindow(String name) {
+        this(name, getCursorWindowSize());
+    }
+
+    /**
+     * Creates a new empty cursor window and gives it a name.
+     * <p>
+     * The cursor initially has no rows or columns.  Call {@link #setNumColumns(int)} to
+     * set the number of columns before adding any rows to the cursor.
+     * </p>
+     *
+     * @param name The name of the cursor window, or null if none.
+     * @param windowSizeBytes Size of cursor window in bytes.
+     * <p><strong>Note:</strong> Memory is dynamically allocated as data rows are added to the
+     * window. Depending on the amount of data stored, the actual amount of memory allocated can be
+     * lower than specified size, but cannot exceed it.
+     */
+    public CursorWindow(String name, @BytesLong long windowSizeBytes) {
         mStartPos = 0;
         mName = name != null && name.length() != 0 ? name : "<unnamed>";
-        if (sCursorWindowSize < 0) {
-            /** The cursor window size. resource xml file specifies the value in kB.
-             * convert it to bytes here by multiplying with 1024.
-             */
-            sCursorWindowSize = Resources.getSystem().getInteger(
-                com.android.internal.R.integer.config_cursorWindowSize) * 1024;
-        }
-        mWindowPtr = nativeCreate(mName, sCursorWindowSize);
+        mWindowPtr = nativeCreate(mName, (int) windowSizeBytes);
         if (mWindowPtr == 0) {
             throw new CursorWindowAllocationException("Cursor window allocation of " +
-                    (sCursorWindowSize / 1024) + " kb failed. " + printStats());
+                    windowSizeBytes + " bytes failed. " + printStats());
         }
         mCloseGuard.open("close");
         recordNewWindow(Binder.getCallingPid(), mWindowPtr);
@@ -773,6 +784,16 @@
         return "# Open Cursors=" + total + s;
     }
 
+    private static int getCursorWindowSize() {
+        if (sCursorWindowSize < 0) {
+            // The cursor window size. resource xml file specifies the value in kB.
+            // convert it to bytes here by multiplying with 1024.
+            sCursorWindowSize = Resources.getSystem().getInteger(
+                    com.android.internal.R.integer.config_cursorWindowSize) * 1024;
+        }
+        return sCursorWindowSize;
+    }
+
     @Override
     public String toString() {
         return getName() + " {" + Long.toHexString(mWindowPtr) + "}";
diff --git a/android/database/SQLiteDatabaseIoPerfTest.java b/android/database/SQLiteDatabaseIoPerfTest.java
new file mode 100644
index 0000000..7c5316d
--- /dev/null
+++ b/android/database/SQLiteDatabaseIoPerfTest.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2017 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.database;
+
+import android.app.Activity;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.sqlite.SQLiteDatabase;
+import android.os.Bundle;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import com.android.internal.util.Preconditions;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Performance tests for measuring amount of data written during typical DB operations
+ *
+ * <p>To run: bit CorePerfTests:android.database.SQLiteDatabaseIoPerfTest
+ */
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class SQLiteDatabaseIoPerfTest {
+    private static final String TAG = "SQLiteDatabaseIoPerfTest";
+    private static final String DB_NAME = "db_io_perftest";
+    private static final int DEFAULT_DATASET_SIZE = 500;
+
+    private Long mWriteBytes;
+
+    private SQLiteDatabase mDatabase;
+    private Context mContext;
+
+    @Before
+    public void setUp() {
+        mContext = InstrumentationRegistry.getTargetContext();
+        mContext.deleteDatabase(DB_NAME);
+        mDatabase = mContext.openOrCreateDatabase(DB_NAME, Context.MODE_PRIVATE, null);
+        mDatabase.execSQL("CREATE TABLE T1 "
+                + "(_ID INTEGER PRIMARY KEY, COL_A INTEGER, COL_B VARCHAR(100), COL_C REAL)");
+    }
+
+    @After
+    public void tearDown() {
+        mDatabase.close();
+        mContext.deleteDatabase(DB_NAME);
+    }
+
+    @Test
+    public void testDatabaseModifications() {
+        startMeasuringWrites();
+        ContentValues cv = new ContentValues();
+        String[] whereArg = new String[1];
+        for (int i = 0; i < DEFAULT_DATASET_SIZE; i++) {
+            cv.put("_ID", i);
+            cv.put("COL_A", i);
+            cv.put("COL_B", "NewValue");
+            cv.put("COL_C", 1.0);
+            assertEquals(i, mDatabase.insert("T1", null, cv));
+        }
+        cv = new ContentValues();
+        for (int i = 0; i < DEFAULT_DATASET_SIZE; i++) {
+            cv.put("COL_B", "UpdatedValue");
+            cv.put("COL_C", 1.1);
+            whereArg[0] = String.valueOf(i);
+            assertEquals(1, mDatabase.update("T1", cv, "_ID=?", whereArg));
+        }
+        for (int i = 0; i < DEFAULT_DATASET_SIZE; i++) {
+            whereArg[0] = String.valueOf(i);
+            assertEquals(1, mDatabase.delete("T1", "_ID=?", whereArg));
+        }
+        // Make sure all changes are written to disk
+        mDatabase.close();
+        long bytes = endMeasuringWrites();
+        sendResults("testDatabaseModifications" , bytes);
+    }
+
+    @Test
+    public void testInsertsWithTransactions() {
+        startMeasuringWrites();
+        final int txSize = 10;
+        ContentValues cv = new ContentValues();
+        for (int i = 0; i < DEFAULT_DATASET_SIZE * 5; i++) {
+            if (i % txSize == 0) {
+                mDatabase.beginTransaction();
+            }
+            if (i % txSize == txSize-1) {
+                mDatabase.setTransactionSuccessful();
+                mDatabase.endTransaction();
+
+            }
+            cv.put("_ID", i);
+            cv.put("COL_A", i);
+            cv.put("COL_B", "NewValue");
+            cv.put("COL_C", 1.0);
+            assertEquals(i, mDatabase.insert("T1", null, cv));
+        }
+        // Make sure all changes are written to disk
+        mDatabase.close();
+        long bytes = endMeasuringWrites();
+        sendResults("testInsertsWithTransactions" , bytes);
+    }
+
+    private void startMeasuringWrites() {
+        Preconditions.checkState(mWriteBytes == null, "Measurement already started");
+        mWriteBytes = getIoStats().get("write_bytes");
+    }
+
+    private long endMeasuringWrites() {
+        Preconditions.checkState(mWriteBytes != null, "Measurement wasn't started");
+        Long newWriteBytes = getIoStats().get("write_bytes");
+        return newWriteBytes - mWriteBytes;
+    }
+
+    private void sendResults(String testName, long writeBytes) {
+        Log.i(TAG, testName + " write_bytes: " + writeBytes);
+        Bundle status = new Bundle();
+        status.putLong("write_bytes", writeBytes);
+        InstrumentationRegistry.getInstrumentation().sendStatus(Activity.RESULT_OK, status);
+    }
+
+    private static Map<String, Long> getIoStats() {
+        String ioStat = "/proc/self/io";
+        Map<String, Long> results = new ArrayMap<>();
+        try {
+            List<String> lines = Files.readAllLines(new File(ioStat).toPath());
+            for (String line : lines) {
+                line = line.trim();
+                String[] split = line.split(":");
+                if (split.length == 2) {
+                    try {
+                        String key = split[0].trim();
+                        Long value = Long.valueOf(split[1].trim());
+                        results.put(key, value);
+                    } catch (NumberFormatException e) {
+                        Log.e(TAG, "Cannot parse number from " + line);
+                    }
+                } else if (line.isEmpty()) {
+                    Log.e(TAG, "Cannot parse line " + line);
+                }
+            }
+        } catch (IOException e) {
+            Log.e(TAG, "Can't read: " + ioStat, e);
+        }
+        return results;
+    }
+
+}
diff --git a/android/database/SQLiteDatabasePerfTest.java b/android/database/SQLiteDatabasePerfTest.java
new file mode 100644
index 0000000..7a32c0c
--- /dev/null
+++ b/android/database/SQLiteDatabasePerfTest.java
@@ -0,0 +1,223 @@
+/*
+ * Copyright (C) 2017 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.database;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.sqlite.SQLiteDatabase;
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Random;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Performance tests for typical CRUD operations and loading rows into the Cursor
+ *
+ * <p>To run: bit CorePerfTests:android.database.SQLiteDatabasePerfTest
+ */
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class SQLiteDatabasePerfTest {
+    // TODO b/64262688 Add Concurrency tests to compare WAL vs DELETE read/write
+    private static final String DB_NAME = "dbperftest";
+    private static final int DEFAULT_DATASET_SIZE = 1000;
+
+    @Rule
+    public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+    private SQLiteDatabase mDatabase;
+    private Context mContext;
+
+    @Before
+    public void setUp() {
+        mContext = InstrumentationRegistry.getTargetContext();
+        mContext.deleteDatabase(DB_NAME);
+        mDatabase = mContext.openOrCreateDatabase(DB_NAME, Context.MODE_PRIVATE, null);
+        mDatabase.execSQL("CREATE TABLE T1 "
+                + "(_ID INTEGER PRIMARY KEY, COL_A INTEGER, COL_B VARCHAR(100), COL_C REAL)");
+        mDatabase.execSQL("CREATE TABLE T2 ("
+                + "_ID INTEGER PRIMARY KEY, COL_A VARCHAR(100), T1_ID INTEGER,"
+                + "FOREIGN KEY(T1_ID) REFERENCES T1 (_ID))");
+    }
+
+    @After
+    public void tearDown() {
+        mDatabase.close();
+        mContext.deleteDatabase(DB_NAME);
+    }
+
+    @Test
+    public void testSelect() {
+        insertT1TestDataSet();
+
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+        Random rnd = new Random(0);
+        while (state.keepRunning()) {
+            int index = rnd.nextInt(DEFAULT_DATASET_SIZE);
+            try (Cursor cursor = mDatabase.rawQuery("SELECT _ID, COL_A, COL_B, COL_C FROM T1 "
+                    + "WHERE _ID=?", new String[]{String.valueOf(index)})) {
+                assertTrue(cursor.moveToNext());
+                assertEquals(index, cursor.getInt(0));
+                assertEquals(index, cursor.getInt(1));
+                assertEquals("T1Value" + index, cursor.getString(2));
+                assertEquals(1.1 * index, cursor.getDouble(3), 0.0000001d);
+            }
+        }
+    }
+
+    @Test
+    public void testSelectMultipleRows() {
+        insertT1TestDataSet();
+
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        Random rnd = new Random(0);
+        final int querySize = 50;
+        while (state.keepRunning()) {
+            int index = rnd.nextInt(DEFAULT_DATASET_SIZE - querySize - 1);
+            try (Cursor cursor = mDatabase.rawQuery("SELECT _ID, COL_A, COL_B, COL_C FROM T1 "
+                            + "WHERE _ID BETWEEN ? and ? ORDER BY _ID",
+                    new String[]{String.valueOf(index), String.valueOf(index + querySize - 1)})) {
+                int i = 0;
+                while(cursor.moveToNext()) {
+                    assertEquals(index, cursor.getInt(0));
+                    assertEquals(index, cursor.getInt(1));
+                    assertEquals("T1Value" + index, cursor.getString(2));
+                    assertEquals(1.1 * index, cursor.getDouble(3), 0.0000001d);
+                    index++;
+                    i++;
+                }
+                assertEquals(querySize, i);
+            }
+        }
+    }
+
+    @Test
+    public void testInnerJoin() {
+        mDatabase.setForeignKeyConstraintsEnabled(true);
+        mDatabase.beginTransaction();
+        insertT1TestDataSet();
+        insertT2TestDataSet();
+        mDatabase.setTransactionSuccessful();
+        mDatabase.endTransaction();
+
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+        Random rnd = new Random(0);
+        while (state.keepRunning()) {
+            int index = rnd.nextInt(1000);
+            try (Cursor cursor = mDatabase.rawQuery(
+                    "SELECT T1._ID, T1.COL_A, T1.COL_B, T1.COL_C, T2.COL_A FROM T1 "
+                    + "INNER JOIN T2 on T2.T1_ID=T1._ID WHERE T1._ID = ?",
+                    new String[]{String.valueOf(index)})) {
+                assertTrue(cursor.moveToNext());
+                assertEquals(index, cursor.getInt(0));
+                assertEquals(index, cursor.getInt(1));
+                assertEquals("T1Value" + index, cursor.getString(2));
+                assertEquals(1.1 * index, cursor.getDouble(3), 0.0000001d);
+                assertEquals("T2Value" + index, cursor.getString(4));
+            }
+        }
+    }
+
+    @Test
+    public void testInsert() {
+        insertT1TestDataSet();
+
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+        ContentValues cv = new ContentValues();
+        cv.put("_ID", DEFAULT_DATASET_SIZE);
+        cv.put("COL_B", "NewValue");
+        cv.put("COL_C", 1.1);
+        String[] deleteArgs = new String[]{String.valueOf(DEFAULT_DATASET_SIZE)};
+        while (state.keepRunning()) {
+            assertEquals(DEFAULT_DATASET_SIZE, mDatabase.insert("T1", null, cv));
+            state.pauseTiming();
+            assertEquals(1, mDatabase.delete("T1", "_ID=?", deleteArgs));
+            state.resumeTiming();
+        }
+    }
+
+    @Test
+    public void testDelete() {
+        insertT1TestDataSet();
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        String[] deleteArgs = new String[]{String.valueOf(DEFAULT_DATASET_SIZE)};
+        Object[] insertsArgs = new Object[]{DEFAULT_DATASET_SIZE, DEFAULT_DATASET_SIZE,
+                "ValueToDelete", 1.1};
+
+        while (state.keepRunning()) {
+            state.pauseTiming();
+            mDatabase.execSQL("INSERT INTO T1 VALUES (?, ?, ?, ?)", insertsArgs);
+            state.resumeTiming();
+            assertEquals(1, mDatabase.delete("T1", "_ID=?", deleteArgs));
+        }
+    }
+
+    @Test
+    public void testUpdate() {
+        insertT1TestDataSet();
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+        Random rnd = new Random(0);
+        int i = 0;
+        ContentValues cv = new ContentValues();
+        String[] argArray = new String[1];
+        while (state.keepRunning()) {
+            int id = rnd.nextInt(DEFAULT_DATASET_SIZE);
+            cv.put("COL_A", i);
+            cv.put("COL_B", "UpdatedValue");
+            cv.put("COL_C", i);
+            argArray[0] = String.valueOf(id);
+            assertEquals(1, mDatabase.update("T1", cv, "_ID=?", argArray));
+            i++;
+        }
+    }
+
+    private void insertT1TestDataSet() {
+        mDatabase.beginTransaction();
+        for (int i = 0; i < DEFAULT_DATASET_SIZE; i++) {
+            mDatabase.execSQL("INSERT INTO T1 VALUES (?, ?, ?, ?)",
+                    new Object[]{i, i, "T1Value" + i, i * 1.1});
+        }
+        mDatabase.setTransactionSuccessful();
+        mDatabase.endTransaction();
+    }
+
+    private void insertT2TestDataSet() {
+        mDatabase.beginTransaction();
+        for (int i = 0; i < DEFAULT_DATASET_SIZE; i++) {
+            mDatabase.execSQL("INSERT INTO T2 VALUES (?, ?, ?)",
+                    new Object[]{i, "T2Value" + i, i});
+        }
+        mDatabase.setTransactionSuccessful();
+        mDatabase.endTransaction();
+    }
+}
+
diff --git a/android/graphics/Bitmap.java b/android/graphics/Bitmap.java
index 57c7549..0072012 100644
--- a/android/graphics/Bitmap.java
+++ b/android/graphics/Bitmap.java
@@ -21,6 +21,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.Size;
+import android.annotation.WorkerThread;
 import android.content.res.ResourcesImpl;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -1233,6 +1234,7 @@
      * @param stream   The outputstream to write the compressed data.
      * @return true if successfully compressed to the specified stream.
      */
+    @WorkerThread
     public boolean compress(CompressFormat format, int quality, OutputStream stream) {
         checkRecycled("Can't compress a recycled bitmap");
         // do explicit check before calling the native method
diff --git a/android/graphics/BitmapFactory.java b/android/graphics/BitmapFactory.java
index ffb39e3..f5bf754 100644
--- a/android/graphics/BitmapFactory.java
+++ b/android/graphics/BitmapFactory.java
@@ -354,6 +354,7 @@
          * decode, in the case of which a more accurate, but slightly slower,
          * IDCT method will be used instead.
          */
+        @Deprecated
         public boolean inPreferQualityOverSpeed;
 
         /**
@@ -412,6 +413,7 @@
          * can check, inbetween the bounds decode and the image decode, to see
          * if the operation is canceled.
          */
+        @Deprecated
         public boolean mCancel;
 
         /**
@@ -426,6 +428,7 @@
          *  or if inJustDecodeBounds is true, will set outWidth/outHeight
          *  to -1
          */
+        @Deprecated
         public void requestCancelDecode() {
             mCancel = true;
         }
diff --git a/android/graphics/Typeface.java b/android/graphics/Typeface.java
index cfc389f..9961ed6 100644
--- a/android/graphics/Typeface.java
+++ b/android/graphics/Typeface.java
@@ -250,9 +250,9 @@
 
         FontFamily fontFamily = new FontFamily();
         for (final FontFileResourceEntry fontFile : filesEntry.getEntries()) {
-            // TODO: Add ttc and variation font support. (b/37853920)
+            // TODO: Add variation font support. (b/37853920)
             if (!fontFamily.addFontFromAssetManager(mgr, fontFile.getFileName(),
-                    0 /* resourceCookie */, false /* isAsset */, 0 /* ttcIndex */,
+                    0 /* resourceCookie */, false /* isAsset */, fontFile.getTtcIndex(),
                     fontFile.getWeight(), fontFile.getItalic(), null /* axes */)) {
                 return null;
             }
diff --git a/android/graphics/pdf/PdfEditor.java b/android/graphics/pdf/PdfEditor.java
index 0c509b7..3821bc7 100644
--- a/android/graphics/pdf/PdfEditor.java
+++ b/android/graphics/pdf/PdfEditor.java
@@ -23,6 +23,7 @@
 import android.graphics.Rect;
 import android.os.ParcelFileDescriptor;
 import android.system.ErrnoException;
+import android.system.Os;
 import android.system.OsConstants;
 import dalvik.system.CloseGuard;
 import libcore.io.IoUtils;
@@ -72,8 +73,8 @@
 
         final long size;
         try {
-            Libcore.os.lseek(input.getFileDescriptor(), 0, OsConstants.SEEK_SET);
-            size = Libcore.os.fstat(input.getFileDescriptor()).st_size;
+            Os.lseek(input.getFileDescriptor(), 0, OsConstants.SEEK_SET);
+            size = Os.fstat(input.getFileDescriptor()).st_size;
         } catch (ErrnoException ee) {
             throw new IllegalArgumentException("file descriptor not seekable");
         }
diff --git a/android/graphics/pdf/PdfRenderer.java b/android/graphics/pdf/PdfRenderer.java
index c82ab0d..4a91705 100644
--- a/android/graphics/pdf/PdfRenderer.java
+++ b/android/graphics/pdf/PdfRenderer.java
@@ -26,12 +26,12 @@
 import android.graphics.Rect;
 import android.os.ParcelFileDescriptor;
 import android.system.ErrnoException;
+import android.system.Os;
 import android.system.OsConstants;
 import com.android.internal.util.Preconditions;
 import dalvik.system.CloseGuard;
 
 import libcore.io.IoUtils;
-import libcore.io.Libcore;
 
 import java.io.IOException;
 import java.lang.annotation.Retention;
@@ -156,8 +156,8 @@
 
         final long size;
         try {
-            Libcore.os.lseek(input.getFileDescriptor(), 0, OsConstants.SEEK_SET);
-            size = Libcore.os.fstat(input.getFileDescriptor()).st_size;
+            Os.lseek(input.getFileDescriptor(), 0, OsConstants.SEEK_SET);
+            size = Os.fstat(input.getFileDescriptor()).st_size;
         } catch (ErrnoException ee) {
             throw new IllegalArgumentException("file descriptor not seekable");
         }
diff --git a/android/inputmethodservice/AbstractInputMethodService.java b/android/inputmethodservice/AbstractInputMethodService.java
index 29177b6..185215a 100644
--- a/android/inputmethodservice/AbstractInputMethodService.java
+++ b/android/inputmethodservice/AbstractInputMethodService.java
@@ -16,6 +16,7 @@
 
 package android.inputmethodservice;
 
+import android.annotation.MainThread;
 import android.annotation.NonNull;
 import android.app.Service;
 import android.content.Intent;
@@ -62,6 +63,7 @@
          * back to {@link AbstractInputMethodService#onCreateInputMethodSessionInterface()
          * AbstractInputMethodService.onCreateInputMethodSessionInterface()}.
          */
+        @MainThread
         public void createSession(SessionCallback callback) {
             callback.sessionCreated(onCreateInputMethodSessionInterface());
         }
@@ -71,6 +73,7 @@
          * {@link AbstractInputMethodSessionImpl#revokeSelf()
          * AbstractInputMethodSessionImpl.setEnabled()} method.
          */
+        @MainThread
         public void setSessionEnabled(InputMethodSession session, boolean enabled) {
             ((AbstractInputMethodSessionImpl)session).setEnabled(enabled);
         }
@@ -80,6 +83,7 @@
          * {@link AbstractInputMethodSessionImpl#revokeSelf()
          * AbstractInputMethodSessionImpl.revokeSelf()} method.
          */
+        @MainThread
         public void revokeSession(InputMethodSession session) {
             ((AbstractInputMethodSessionImpl)session).revokeSelf();
         }
diff --git a/android/inputmethodservice/IInputMethodWrapper.java b/android/inputmethodservice/IInputMethodWrapper.java
index 765aff9..2c7e51a 100644
--- a/android/inputmethodservice/IInputMethodWrapper.java
+++ b/android/inputmethodservice/IInputMethodWrapper.java
@@ -16,14 +16,8 @@
 
 package android.inputmethodservice;
 
-import com.android.internal.os.HandlerCaller;
-import com.android.internal.os.SomeArgs;
-import com.android.internal.view.IInputContext;
-import com.android.internal.view.IInputMethod;
-import com.android.internal.view.IInputMethodSession;
-import com.android.internal.view.IInputSessionCallback;
-import com.android.internal.view.InputConnectionWrapper;
-
+import android.annotation.BinderThread;
+import android.annotation.MainThread;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.os.Binder;
@@ -41,11 +35,20 @@
 import android.view.inputmethod.InputMethodSession;
 import android.view.inputmethod.InputMethodSubtype;
 
+import com.android.internal.os.HandlerCaller;
+import com.android.internal.os.SomeArgs;
+import com.android.internal.view.IInputContext;
+import com.android.internal.view.IInputMethod;
+import com.android.internal.view.IInputMethodSession;
+import com.android.internal.view.IInputSessionCallback;
+import com.android.internal.view.InputConnectionWrapper;
+
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.lang.ref.WeakReference;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
  * Implements the internal IInputMethod interface to convert incoming calls
@@ -67,17 +70,27 @@
     private static final int DO_SHOW_SOFT_INPUT = 60;
     private static final int DO_HIDE_SOFT_INPUT = 70;
     private static final int DO_CHANGE_INPUTMETHOD_SUBTYPE = 80;
-   
+
     final WeakReference<AbstractInputMethodService> mTarget;
     final Context mContext;
     final HandlerCaller mCaller;
     final WeakReference<InputMethod> mInputMethod;
     final int mTargetSdkVersion;
-    
-    static class Notifier {
-        boolean notified;
-    }
-    
+
+    /**
+     * This is not {@null} only between {@link #bindInput(InputBinding)} and {@link #unbindInput()}
+     * so that {@link InputConnectionWrapper} can query if {@link #unbindInput()} has already been
+     * called or not, mainly to avoid unnecessary blocking operations.
+     *
+     * <p>This field must be set and cleared only from the binder thread(s), where the system
+     * guarantees that {@link #bindInput(InputBinding)},
+     * {@link #startInput(IBinder, IInputContext, int, EditorInfo, boolean)}, and
+     * {@link #unbindInput()} are called with the same order as the original calls
+     * in {@link com.android.server.InputMethodManagerService}.  See {@link IBinder#FLAG_ONEWAY}
+     * for detailed semantics.</p>
+     */
+    AtomicBoolean mIsUnbindIssued = null;
+
     // NOTE: we should have a cache of these.
     static final class InputMethodSessionCallbackWrapper implements InputMethod.SessionCallback {
         final Context mContext;
@@ -108,20 +121,16 @@
             }
         }
     }
-    
-    public IInputMethodWrapper(AbstractInputMethodService context,
-            InputMethod inputMethod) {
-        mTarget = new WeakReference<AbstractInputMethodService>(context);
+
+    public IInputMethodWrapper(AbstractInputMethodService context, InputMethod inputMethod) {
+        mTarget = new WeakReference<>(context);
         mContext = context.getApplicationContext();
         mCaller = new HandlerCaller(mContext, null, this, true /*asyncHandler*/);
-        mInputMethod = new WeakReference<InputMethod>(inputMethod);
+        mInputMethod = new WeakReference<>(inputMethod);
         mTargetSdkVersion = context.getApplicationInfo().targetSdkVersion;
     }
 
-    public InputMethod getInternalInputMethod() {
-        return mInputMethod.get();
-    }
-
+    @MainThread
     @Override
     public void executeMessage(Message msg) {
         InputMethod inputMethod = mInputMethod.get();
@@ -169,8 +178,10 @@
                 final IBinder startInputToken = (IBinder) args.arg1;
                 final IInputContext inputContext = (IInputContext) args.arg2;
                 final EditorInfo info = (EditorInfo) args.arg3;
+                final AtomicBoolean isUnbindIssued = (AtomicBoolean) args.arg4;
                 final InputConnection ic = inputContext != null
-                        ? new InputConnectionWrapper(mTarget, inputContext, missingMethods) : null;
+                        ? new InputConnectionWrapper(
+                                mTarget, inputContext, missingMethods, isUnbindIssued) : null;
                 info.makeCompatible(mTargetSdkVersion);
                 inputMethod.dispatchStartInputWithToken(ic, info, restarting /* restarting */,
                         startInputToken);
@@ -205,6 +216,7 @@
         Log.w(TAG, "Unhandled message code: " + msg.what);
     }
 
+    @BinderThread
     @Override
     protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
         AbstractInputMethodService target = mTarget.get();
@@ -232,40 +244,63 @@
         }
     }
 
+    @BinderThread
     @Override
     public void attachToken(IBinder token) {
         mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_ATTACH_TOKEN, token));
     }
 
+    @BinderThread
     @Override
     public void bindInput(InputBinding binding) {
+        if (mIsUnbindIssued != null) {
+            Log.e(TAG, "bindInput must be paired with unbindInput.");
+        }
+        mIsUnbindIssued = new AtomicBoolean();
         // This IInputContext is guaranteed to implement all the methods.
         final int missingMethodFlags = 0;
         InputConnection ic = new InputConnectionWrapper(mTarget,
-                IInputContext.Stub.asInterface(binding.getConnectionToken()), missingMethodFlags);
+                IInputContext.Stub.asInterface(binding.getConnectionToken()), missingMethodFlags,
+                mIsUnbindIssued);
         InputBinding nu = new InputBinding(ic, binding);
         mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_SET_INPUT_CONTEXT, nu));
     }
 
+    @BinderThread
     @Override
     public void unbindInput() {
+        if (mIsUnbindIssued != null) {
+            // Signal the flag then forget it.
+            mIsUnbindIssued.set(true);
+            mIsUnbindIssued = null;
+        } else {
+            Log.e(TAG, "unbindInput must be paired with bindInput.");
+        }
         mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_UNSET_INPUT_CONTEXT));
     }
 
+    @BinderThread
     @Override
     public void startInput(IBinder startInputToken, IInputContext inputContext,
             @InputConnectionInspector.MissingMethodFlags final int missingMethods,
             EditorInfo attribute, boolean restarting) {
-        mCaller.executeOrSendMessage(mCaller.obtainMessageIIOOO(DO_START_INPUT,
-                missingMethods, restarting ? 1 : 0, startInputToken, inputContext, attribute));
+        if (mIsUnbindIssued == null) {
+            Log.e(TAG, "startInput must be called after bindInput.");
+            mIsUnbindIssued = new AtomicBoolean();
+        }
+        mCaller.executeOrSendMessage(mCaller.obtainMessageIIOOOO(DO_START_INPUT,
+                missingMethods, restarting ? 1 : 0, startInputToken, inputContext, attribute,
+                mIsUnbindIssued));
     }
 
+    @BinderThread
     @Override
     public void createSession(InputChannel channel, IInputSessionCallback callback) {
         mCaller.executeOrSendMessage(mCaller.obtainMessageOO(DO_CREATE_SESSION,
                 channel, callback));
     }
 
+    @BinderThread
     @Override
     public void setSessionEnabled(IInputMethodSession session, boolean enabled) {
         try {
@@ -282,6 +317,7 @@
         }
     }
 
+    @BinderThread
     @Override
     public void revokeSession(IInputMethodSession session) {
         try {
@@ -297,18 +333,21 @@
         }
     }
 
+    @BinderThread
     @Override
     public void showSoftInput(int flags, ResultReceiver resultReceiver) {
         mCaller.executeOrSendMessage(mCaller.obtainMessageIO(DO_SHOW_SOFT_INPUT,
                 flags, resultReceiver));
     }
 
+    @BinderThread
     @Override
     public void hideSoftInput(int flags, ResultReceiver resultReceiver) {
         mCaller.executeOrSendMessage(mCaller.obtainMessageIO(DO_HIDE_SOFT_INPUT,
                 flags, resultReceiver));
     }
 
+    @BinderThread
     @Override
     public void changeInputMethodSubtype(InputMethodSubtype subtype) {
         mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_CHANGE_INPUTMETHOD_SUBTYPE,
diff --git a/android/inputmethodservice/InputMethodService.java b/android/inputmethodservice/InputMethodService.java
index 7a20943..223ed73 100644
--- a/android/inputmethodservice/InputMethodService.java
+++ b/android/inputmethodservice/InputMethodService.java
@@ -382,8 +382,10 @@
      */
     public class InputMethodImpl extends AbstractInputMethodImpl {
         /**
-         * Take care of attaching the given window token provided by the system.
+         * {@inheritDoc}
          */
+        @MainThread
+        @Override
         public void attachToken(IBinder token) {
             if (mToken == null) {
                 mToken = token;
@@ -392,10 +394,12 @@
         }
         
         /**
-         * Handle a new input binding, calling
-         * {@link InputMethodService#onBindInput InputMethodService.onBindInput()}
-         * when done.
+         * {@inheritDoc}
+         *
+         * <p>Calls {@link InputMethodService#onBindInput()} when done.</p>
          */
+        @MainThread
+        @Override
         public void bindInput(InputBinding binding) {
             mInputBinding = binding;
             mInputConnection = binding.getConnection();
@@ -409,8 +413,12 @@
         }
 
         /**
-         * Clear the current input binding.
+         * {@inheritDoc}
+         *
+         * <p>Calls {@link InputMethodService#onUnbindInput()} when done.</p>
          */
+        @MainThread
+        @Override
         public void unbindInput() {
             if (DEBUG) Log.v(TAG, "unbindInput(): binding=" + mInputBinding
                     + " ic=" + mInputConnection);
@@ -419,11 +427,21 @@
             mInputConnection = null;
         }
 
+        /**
+         * {@inheritDoc}
+         */
+        @MainThread
+        @Override
         public void startInput(InputConnection ic, EditorInfo attribute) {
             if (DEBUG) Log.v(TAG, "startInput(): editor=" + attribute);
             doStartInput(ic, attribute, false);
         }
 
+        /**
+         * {@inheritDoc}
+         */
+        @MainThread
+        @Override
         public void restartInput(InputConnection ic, EditorInfo attribute) {
             if (DEBUG) Log.v(TAG, "restartInput(): editor=" + attribute);
             doStartInput(ic, attribute, true);
@@ -433,6 +451,7 @@
          * {@inheritDoc}
          * @hide
          */
+        @MainThread
         @Override
         public void dispatchStartInputWithToken(@Nullable InputConnection inputConnection,
                 @NonNull EditorInfo editorInfo, boolean restarting,
@@ -447,8 +466,10 @@
         }
 
         /**
-         * Handle a request by the system to hide the soft input area.
+         * {@inheritDoc}
          */
+        @MainThread
+        @Override
         public void hideSoftInput(int flags, ResultReceiver resultReceiver) {
             if (DEBUG) Log.v(TAG, "hideSoftInput()");
             boolean wasVis = isInputViewShown();
@@ -465,8 +486,10 @@
         }
 
         /**
-         * Handle a request by the system to show the soft input area.
+         * {@inheritDoc}
          */
+        @MainThread
+        @Override
         public void showSoftInput(int flags, ResultReceiver resultReceiver) {
             if (DEBUG) Log.v(TAG, "showSoftInput()");
             boolean wasVis = isInputViewShown();
@@ -495,6 +518,11 @@
             }
         }
 
+        /**
+         * {@inheritDoc}
+         */
+        @MainThread
+        @Override
         public void changeInputMethodSubtype(InputMethodSubtype subtype) {
             onCurrentInputMethodSubtypeChanged(subtype);
         }
diff --git a/android/location/GnssMeasurement.java b/android/location/GnssMeasurement.java
index d24a477..412cc29 100644
--- a/android/location/GnssMeasurement.java
+++ b/android/location/GnssMeasurement.java
@@ -612,6 +612,16 @@
      *
      * <pre>
      *          accumulated delta range = -k * carrier phase    (where k is a constant)</pre>
+     *
+     * <p>Similar to the concept of an RTCM "Phaserange", when the accumulated delta range is
+     * initially chosen, and whenever it is reset, it will retain the integer nature
+     * of the relative carrier phase offset between satellites observed by this receiver, such that
+     * the double difference of this value between receivers and satellites may be used, together
+     * with integer ambiguity resolution, to determine highly precise relative location between
+     * receivers.
+     *
+     * <p>This includes ensuring that all half-cycle ambiguities are resolved before this value is
+     * reported as {@link #ADR_STATE_VALID}.
      */
     public double getAccumulatedDeltaRangeMeters() {
         return mAccumulatedDeltaRangeMeters;
@@ -861,7 +871,7 @@
     }
 
     /**
-     * Gets the Signal-to-Noise ratio (SNR) in dB.
+     * Gets the (post-correlation & integration) Signal-to-Noise ratio (SNR) in dB.
      *
      * <p>The value is only available if {@link #hasSnrInDb()} is {@code true}.
      */
diff --git a/android/location/Location.java b/android/location/Location.java
index e8eaa59..e7f903e 100644
--- a/android/location/Location.java
+++ b/android/location/Location.java
@@ -813,15 +813,16 @@
     /**
      * Get the estimated vertical accuracy of this location, in meters.
      *
-     * <p>We define vertical accuracy as the radius of 68% confidence. In other
-     * words, if you draw a circle centered at this location's altitude, and with a radius
-     * equal to the vertical accuracy, then there is a 68% probability that the true altitude is
-     * inside the circle.
+     * <p>We define vertical accuracy at 68% confidence.  Specifically, as 1-side of the
+     * 2-sided range above and below the estimated altitude reported by {@link #getAltitude()},
+     * within which there is a 68% probability of finding the true altitude.
      *
-     * <p>In statistical terms, it is assumed that location errors
-     * are random with a normal distribution, so the 68% confidence circle
-     * represents one standard deviation. Note that in practice, location
-     * errors do not always follow such a simple distribution.
+     * <p>In the case where the underlying distribution is assumed Gaussian normal, this would be
+     * considered 1 standard deviation.
+     *
+     * <p>For example, if {@link #getAltitude()} returns 150, and
+     * {@link #getVerticalAccuracyMeters()} ()} returns 20 then there is a 68% probability
+     * of the true altitude being between 130 and 170 meters.
      *
      * <p>If this location does not have a vertical accuracy, then 0.0 is returned.
      */
@@ -866,14 +867,16 @@
     /**
      * Get the estimated speed accuracy of this location, in meters per second.
      *
-     * <p>We define speed accuracy as a 1-standard-deviation value, i.e. as 1-side of the
-     * 2-sided range above and below the estimated
-     * speed reported by {@link #getSpeed()}, within which there is a 68% probability of
-     * finding the true speed.
+     * <p>We define speed accuracy at 68% confidence.  Specifically, as 1-side of the
+     * 2-sided range above and below the estimated speed reported by {@link #getSpeed()},
+     * within which there is a 68% probability of finding the true speed.
      *
-     * <p>For example, if {@link #getSpeed()} returns 5.0, and
-     * {@link #getSpeedAccuracyMetersPerSecond()} returns 1.0, then there is a 68% probably of the
-     * true speed being between 4.0 and 6.0 meters per second.
+     * <p>In the case where the underlying
+     * distribution is assumed Gaussian normal, this would be considered 1 standard deviation.
+     *
+     * <p>For example, if {@link #getSpeed()} returns 5, and
+     * {@link #getSpeedAccuracyMetersPerSecond()} returns 1, then there is a 68% probability of
+     * the true speed being between 4 and 6 meters per second.
      *
      * <p>Note that the speed and speed accuracy is often better than would be obtained simply from
      * differencing sequential positions, such as when the Doppler measurements from GNSS satellites
@@ -922,13 +925,16 @@
     /**
      * Get the estimated bearing accuracy of this location, in degrees.
      *
-     * <p>We define bearing accuracy as a 1-standard-deviation value, i.e. as 1-side of the
+     * <p>We define bearing accuracy at 68% confidence.  Specifically, as 1-side of the
      * 2-sided range on each side of the estimated bearing reported by {@link #getBearing()},
      * within which there is a 68% probability of finding the true bearing.
      *
-     * <p>For example, if {@link #getBearing()} returns 60., and
-     * {@link #getBearingAccuracyDegrees()} ()} returns 10., then there is a 68% probably of the
-     * true bearing being between 50. and 70. degrees.
+     * <p>In the case where the underlying distribution is assumed Gaussian normal, this would be
+     * considered 1 standard deviation.
+     *
+     * <p>For example, if {@link #getBearing()} returns 60, and
+     * {@link #getBearingAccuracyDegrees()} ()} returns 10, then there is a 68% probability of the
+     * true bearing being between 50 and 70 degrees.
      *
      * <p>If this location does not have a bearing accuracy, then 0.0 is returned.
      */
diff --git a/android/media/AudioAttributes.java b/android/media/AudioAttributes.java
index 26ead3d..20405d3 100644
--- a/android/media/AudioAttributes.java
+++ b/android/media/AudioAttributes.java
@@ -202,6 +202,22 @@
      * @see #SUPPRESSIBLE_USAGES
      */
     public final static int SUPPRESSIBLE_NEVER = 3;
+    /**
+     * @hide
+     * Denotes a usage for alarms,
+     * will be muted when the Zen mode doesn't allow alarms
+     * @see #SUPPRESSIBLE_USAGES
+     */
+    public final static int SUPPRESSIBLE_ALARM = 4;
+    /**
+     * @hide
+     * Denotes a usage for all other sounds not caught in SUPPRESSIBLE_NOTIFICATION,
+     * SUPPRESSIBLE_CALL,SUPPRESSIBLE_NEVER or SUPPRESSIBLE_ALARM.
+     * This includes media, system, game, navigation, the assistant, and more.
+     * These will be muted when the Zen mode doesn't allow media/system/other.
+     * @see #SUPPRESSIBLE_USAGES
+     */
+    public final static int SUPPRESSIBLE_MEDIA_SYSTEM_OTHER = 5;
 
     /**
      * @hide
@@ -221,6 +237,13 @@
         SUPPRESSIBLE_USAGES.put(USAGE_NOTIFICATION_EVENT,                SUPPRESSIBLE_NOTIFICATION);
         SUPPRESSIBLE_USAGES.put(USAGE_ASSISTANCE_ACCESSIBILITY,          SUPPRESSIBLE_NEVER);
         SUPPRESSIBLE_USAGES.put(USAGE_VOICE_COMMUNICATION,               SUPPRESSIBLE_NEVER);
+        SUPPRESSIBLE_USAGES.put(USAGE_ALARM,                             SUPPRESSIBLE_ALARM);
+        SUPPRESSIBLE_USAGES.put(USAGE_MEDIA,                             SUPPRESSIBLE_MEDIA_SYSTEM_OTHER);
+        SUPPRESSIBLE_USAGES.put(USAGE_ASSISTANCE_SONIFICATION,           SUPPRESSIBLE_MEDIA_SYSTEM_OTHER);
+        SUPPRESSIBLE_USAGES.put(USAGE_ASSISTANCE_NAVIGATION_GUIDANCE,    SUPPRESSIBLE_MEDIA_SYSTEM_OTHER);
+        SUPPRESSIBLE_USAGES.put(USAGE_GAME,                              SUPPRESSIBLE_MEDIA_SYSTEM_OTHER);
+        SUPPRESSIBLE_USAGES.put(USAGE_VOICE_COMMUNICATION_SIGNALLING,    SUPPRESSIBLE_MEDIA_SYSTEM_OTHER);
+        SUPPRESSIBLE_USAGES.put(USAGE_ASSISTANT,                         SUPPRESSIBLE_MEDIA_SYSTEM_OTHER);
     }
 
     /**
diff --git a/android/media/MediaMetadataRetriever.java b/android/media/MediaMetadataRetriever.java
index 4ea4e38..760cc49 100644
--- a/android/media/MediaMetadataRetriever.java
+++ b/android/media/MediaMetadataRetriever.java
@@ -395,7 +395,7 @@
      * @see #getFrameAtTime(long, int)
      */
     /* Do not change these option values without updating their counterparts
-     * in include/media/stagefright/MediaSource.h!
+     * in include/media/MediaSource.h!
      */
     /**
      * This option is used with {@link #getFrameAtTime(long, int)} to retrieve
diff --git a/android/media/MediaPlayer.java b/android/media/MediaPlayer.java
index 7787d4b..62757e2 100644
--- a/android/media/MediaPlayer.java
+++ b/android/media/MediaPlayer.java
@@ -39,6 +39,7 @@
 import android.os.SystemProperties;
 import android.provider.Settings;
 import android.system.ErrnoException;
+import android.system.Os;
 import android.system.OsConstants;
 import android.util.Log;
 import android.util.Pair;
@@ -60,7 +61,6 @@
 import com.android.internal.util.Preconditions;
 
 import libcore.io.IoBridge;
-import libcore.io.Libcore;
 import libcore.io.Streams;
 
 import java.io.ByteArrayOutputStream;
@@ -2843,7 +2843,7 @@
 
         final FileDescriptor dupedFd;
         try {
-            dupedFd = Libcore.os.dup(fd);
+            dupedFd = Os.dup(fd);
         } catch (ErrnoException ex) {
             Log.e(TAG, ex.getMessage(), ex);
             throw new RuntimeException(ex);
@@ -2881,7 +2881,7 @@
             private int addTrack() {
                 final ByteArrayOutputStream bos = new ByteArrayOutputStream();
                 try {
-                    Libcore.os.lseek(dupedFd, offset2, OsConstants.SEEK_SET);
+                    Os.lseek(dupedFd, offset2, OsConstants.SEEK_SET);
                     byte[] buffer = new byte[4096];
                     for (long total = 0; total < length2;) {
                         int bytesToRead = (int) Math.min(buffer.length, length2 - total);
@@ -2905,7 +2905,7 @@
                     return MEDIA_INFO_TIMED_TEXT_ERROR;
                 } finally {
                     try {
-                        Libcore.os.close(dupedFd);
+                        Os.close(dupedFd);
                     } catch (ErrnoException e) {
                         Log.e(TAG, e.getMessage(), e);
                     }
diff --git a/android/media/MediaRecorder.java b/android/media/MediaRecorder.java
index 59a124f..7678490 100644
--- a/android/media/MediaRecorder.java
+++ b/android/media/MediaRecorder.java
@@ -917,7 +917,7 @@
      */
     public void setNextOutputFile(File file) throws IOException
     {
-        RandomAccessFile f = new RandomAccessFile(file, "rws");
+        RandomAccessFile f = new RandomAccessFile(file, "rw");
         try {
             _setNextOutputFile(f.getFD());
         } finally {
@@ -942,7 +942,7 @@
     public void prepare() throws IllegalStateException, IOException
     {
         if (mPath != null) {
-            RandomAccessFile file = new RandomAccessFile(mPath, "rws");
+            RandomAccessFile file = new RandomAccessFile(mPath, "rw");
             try {
                 _setOutputFile(file.getFD());
             } finally {
@@ -951,7 +951,7 @@
         } else if (mFd != null) {
             _setOutputFile(mFd);
         } else if (mFile != null) {
-            RandomAccessFile file = new RandomAccessFile(mFile, "rws");
+            RandomAccessFile file = new RandomAccessFile(mFile, "rw");
             try {
                 _setOutputFile(file.getFD());
             } finally {
diff --git a/android/media/projection/MediaProjectionManager.java b/android/media/projection/MediaProjectionManager.java
index 9f2c08e..aa0d0cc 100644
--- a/android/media/projection/MediaProjectionManager.java
+++ b/android/media/projection/MediaProjectionManager.java
@@ -20,8 +20,10 @@
 import android.annotation.Nullable;
 import android.annotation.SystemService;
 import android.app.Activity;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.PackageManager;
 import android.media.projection.IMediaProjection;
 import android.os.Handler;
 import android.os.IBinder;
@@ -71,8 +73,11 @@
      */
     public Intent createScreenCaptureIntent() {
         Intent i = new Intent();
-        i.setClassName("com.android.systemui",
-                "com.android.systemui.media.MediaProjectionPermissionActivity");
+        final ComponentName mediaProjectionPermissionDialogComponent =
+                ComponentName.unflattenFromString(mContext.getResources().getString(
+                        com.android.internal.R.string
+                        .config_mediaProjectionPermissionDialogComponent));
+        i.setComponent(mediaProjectionPermissionDialogComponent);
         return i;
     }
 
diff --git a/android/media/tv/TvInputManager.java b/android/media/tv/TvInputManager.java
index d7a9ede..fd1f2cf 100644
--- a/android/media/tv/TvInputManager.java
+++ b/android/media/tv/TvInputManager.java
@@ -2590,12 +2590,9 @@
             }
         }
 
+        /** @removed */
         public boolean dispatchKeyEventToHdmi(KeyEvent event) {
-            try {
-                return mInterface.dispatchKeyEventToHdmi(event);
-            } catch (RemoteException e) {
-                throw new RuntimeException(e);
-            }
+            return false;
         }
 
         public void overrideAudioSink(int audioType, String audioAddress, int samplingRate,
diff --git a/android/mtp/MtpDatabase.java b/android/mtp/MtpDatabase.java
index 17b2326..aaf18e7 100644
--- a/android/mtp/MtpDatabase.java
+++ b/android/mtp/MtpDatabase.java
@@ -471,10 +471,14 @@
                     if (parent == 0xFFFFFFFF) {
                         // all objects in root of store
                         parent = 0;
+                        where = STORAGE_PARENT_WHERE;
+                        whereArgs = new String[]{Integer.toString(storageID),
+                                Integer.toString(parent)};
+                    }  else {
+                        // If a parent is specified, the storage is redundant
+                        where = PARENT_WHERE;
+                        whereArgs = new String[]{Integer.toString(parent)};
                     }
-                    where = STORAGE_PARENT_WHERE;
-                    whereArgs = new String[] { Integer.toString(storageID),
-                                               Integer.toString(parent) };
                 }
             } else {
                 // query specific format
@@ -487,11 +491,16 @@
                     if (parent == 0xFFFFFFFF) {
                         // all objects in root of store
                         parent = 0;
+                        where = STORAGE_FORMAT_PARENT_WHERE;
+                        whereArgs = new String[]{Integer.toString(storageID),
+                                Integer.toString(format),
+                                Integer.toString(parent)};
+                    } else {
+                        // If a parent is specified, the storage is redundant
+                        where = FORMAT_PARENT_WHERE;
+                        whereArgs = new String[]{Integer.toString(format),
+                                Integer.toString(parent)};
                     }
-                    where = STORAGE_FORMAT_PARENT_WHERE;
-                    whereArgs = new String[] { Integer.toString(storageID),
-                                               Integer.toString(format),
-                                               Integer.toString(parent) };
                 }
             }
         }
@@ -845,7 +854,7 @@
         return MtpConstants.RESPONSE_OK;
     }
 
-    private int moveObject(int handle, int newParent, String newPath) {
+    private int moveObject(int handle, int newParent, int newStorage, String newPath) {
         String[] whereArgs = new String[] {  Integer.toString(handle) };
 
         // do not allow renaming any of the special subdirectories
@@ -857,6 +866,7 @@
         ContentValues values = new ContentValues();
         values.put(Files.FileColumns.DATA, newPath);
         values.put(Files.FileColumns.PARENT, newParent);
+        values.put(Files.FileColumns.STORAGE_ID, newStorage);
         int updated = 0;
         try {
             // note - we are relying on a special case in MediaProvider.update() to update
diff --git a/android/net/IpSecAlgorithm.java b/android/net/IpSecAlgorithm.java
index 79310e2..16b1452 100644
--- a/android/net/IpSecAlgorithm.java
+++ b/android/net/IpSecAlgorithm.java
@@ -31,7 +31,6 @@
  * RFC 4301.
  */
 public final class IpSecAlgorithm implements Parcelable {
-
     /**
      * AES-CBC Encryption/Ciphering Algorithm.
      *
@@ -68,6 +67,7 @@
      * <p>Valid truncation lengths are multiples of 8 bits from 192 to (default) 384.
      */
     public static final String AUTH_HMAC_SHA384 = "hmac(sha384)";
+
     /**
      * SHA512 HMAC Authentication/Integrity Algorithm
      *
@@ -75,8 +75,24 @@
      */
     public static final String AUTH_HMAC_SHA512 = "hmac(sha512)";
 
+    /**
+     * AES-GCM Authentication/Integrity + Encryption/Ciphering Algorithm.
+     *
+     * <p>Valid lengths for this key are {128, 192, 256}.
+     *
+     * <p>Valid ICV (truncation) lengths are {64, 96, 128}.
+     */
+    public static final String AUTH_CRYPT_AES_GCM = "rfc4106(gcm(aes))";
+
     /** @hide */
-    @StringDef({CRYPT_AES_CBC, AUTH_HMAC_MD5, AUTH_HMAC_SHA1, AUTH_HMAC_SHA256, AUTH_HMAC_SHA512})
+    @StringDef({
+        CRYPT_AES_CBC,
+        AUTH_HMAC_MD5,
+        AUTH_HMAC_SHA1,
+        AUTH_HMAC_SHA256,
+        AUTH_HMAC_SHA512,
+        AUTH_CRYPT_AES_GCM
+    })
     @Retention(RetentionPolicy.SOURCE)
     public @interface AlgorithmName {}
 
@@ -102,7 +118,7 @@
      * @param algoName precise name of the algorithm to be used.
      * @param key non-null Key padded to a multiple of 8 bits.
      * @param truncLenBits the number of bits of output hash to use; only meaningful for
-     *     Authentication.
+     *     Authentication or Authenticated Encryption (equivalent to ICV length).
      */
     public IpSecAlgorithm(@AlgorithmName String algoName, byte[] key, int truncLenBits) {
         if (!isTruncationLengthValid(algoName, truncLenBits)) {
@@ -175,6 +191,8 @@
                 return (truncLenBits >= 192 && truncLenBits <= 384);
             case AUTH_HMAC_SHA512:
                 return (truncLenBits >= 256 && truncLenBits <= 512);
+            case AUTH_CRYPT_AES_GCM:
+                return (truncLenBits == 64 || truncLenBits == 96 || truncLenBits == 128);
             default:
                 return false;
         }
diff --git a/android/net/IpSecConfig.java b/android/net/IpSecConfig.java
index 632b7fc..61b13a9 100644
--- a/android/net/IpSecConfig.java
+++ b/android/net/IpSecConfig.java
@@ -50,6 +50,9 @@
         // Authentication Algorithm
         private IpSecAlgorithm mAuthentication;
 
+        // Authenticated Encryption Algorithm
+        private IpSecAlgorithm mAuthenticatedEncryption;
+
         @Override
         public String toString() {
             return new StringBuilder()
@@ -59,6 +62,8 @@
                     .append(mEncryption)
                     .append(", mAuthentication=")
                     .append(mAuthentication)
+                    .append(", mAuthenticatedEncryption=")
+                    .append(mAuthenticatedEncryption)
                     .append("}")
                     .toString();
         }
@@ -118,6 +123,11 @@
         mFlow[direction].mAuthentication = authentication;
     }
 
+    /** Set the authenticated encryption algorithm for a given direction */
+    public void setAuthenticatedEncryption(int direction, IpSecAlgorithm authenticatedEncryption) {
+        mFlow[direction].mAuthenticatedEncryption = authenticatedEncryption;
+    }
+
     public void setNetwork(Network network) {
         mNetwork = network;
     }
@@ -163,6 +173,10 @@
         return mFlow[direction].mAuthentication;
     }
 
+    public IpSecAlgorithm getAuthenticatedEncryption(int direction) {
+        return mFlow[direction].mAuthenticatedEncryption;
+    }
+
     public Network getNetwork() {
         return mNetwork;
     }
@@ -199,9 +213,11 @@
         out.writeInt(mFlow[IpSecTransform.DIRECTION_IN].mSpiResourceId);
         out.writeParcelable(mFlow[IpSecTransform.DIRECTION_IN].mEncryption, flags);
         out.writeParcelable(mFlow[IpSecTransform.DIRECTION_IN].mAuthentication, flags);
+        out.writeParcelable(mFlow[IpSecTransform.DIRECTION_IN].mAuthenticatedEncryption, flags);
         out.writeInt(mFlow[IpSecTransform.DIRECTION_OUT].mSpiResourceId);
         out.writeParcelable(mFlow[IpSecTransform.DIRECTION_OUT].mEncryption, flags);
         out.writeParcelable(mFlow[IpSecTransform.DIRECTION_OUT].mAuthentication, flags);
+        out.writeParcelable(mFlow[IpSecTransform.DIRECTION_OUT].mAuthenticatedEncryption, flags);
         out.writeInt(mEncapType);
         out.writeInt(mEncapSocketResourceId);
         out.writeInt(mEncapRemotePort);
@@ -221,11 +237,15 @@
                 (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
         mFlow[IpSecTransform.DIRECTION_IN].mAuthentication =
                 (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
+        mFlow[IpSecTransform.DIRECTION_IN].mAuthenticatedEncryption =
+                (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
         mFlow[IpSecTransform.DIRECTION_OUT].mSpiResourceId = in.readInt();
         mFlow[IpSecTransform.DIRECTION_OUT].mEncryption =
                 (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
         mFlow[IpSecTransform.DIRECTION_OUT].mAuthentication =
                 (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
+        mFlow[IpSecTransform.DIRECTION_OUT].mAuthenticatedEncryption =
+                (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
         mEncapType = in.readInt();
         mEncapSocketResourceId = in.readInt();
         mEncapRemotePort = in.readInt();
diff --git a/android/net/IpSecTransform.java b/android/net/IpSecTransform.java
index e15a2c6..48b5bd5 100644
--- a/android/net/IpSecTransform.java
+++ b/android/net/IpSecTransform.java
@@ -281,6 +281,8 @@
          * <p>If encryption is set for a given direction without also providing an SPI for that
          * direction, creation of an IpSecTransform will fail upon calling a build() method.
          *
+         * <p>Authenticated encryption is mutually exclusive with encryption and authentication.
+         *
          * @param direction either {@link #DIRECTION_IN or #DIRECTION_OUT}
          * @param algo {@link IpSecAlgorithm} specifying the encryption to be applied.
          */
@@ -296,6 +298,8 @@
          * <p>If authentication is set for a given direction without also providing an SPI for that
          * direction, creation of an IpSecTransform will fail upon calling a build() method.
          *
+         * <p>Authenticated encryption is mutually exclusive with encryption and authentication.
+         *
          * @param direction either {@link #DIRECTION_IN or #DIRECTION_OUT}
          * @param algo {@link IpSecAlgorithm} specifying the authentication to be applied.
          */
@@ -306,6 +310,29 @@
         }
 
         /**
+         * Add an authenticated encryption algorithm to the transform for the given direction.
+         *
+         * <p>If an authenticated encryption algorithm is set for a given direction without also
+         * providing an SPI for that direction, creation of an IpSecTransform will fail upon calling
+         * a build() method.
+         *
+         * <p>The Authenticated Encryption (AE) class of algorithms are also known as Authenticated
+         * Encryption with Associated Data (AEAD) algorithms, or Combined mode algorithms (as
+         * referred to in RFC 4301)
+         *
+         * <p>Authenticated encryption is mutually exclusive with encryption and authentication.
+         *
+         * @param direction either {@link #DIRECTION_IN or #DIRECTION_OUT}
+         * @param algo {@link IpSecAlgorithm} specifying the authenticated encryption algorithm to
+         *     be applied.
+         */
+        public IpSecTransform.Builder setAuthenticatedEncryption(
+                @TransformDirection int direction, IpSecAlgorithm algo) {
+            mConfig.setAuthenticatedEncryption(direction, algo);
+            return this;
+        }
+
+        /**
          * Set the SPI, which uniquely identifies a particular IPsec session from others. Because
          * IPsec operates at the IP layer, this 32-bit identifier uniquely identifies packets to a
          * given destination address.
diff --git a/android/net/LinkProperties.java b/android/net/LinkProperties.java
index 2c9fb23..4e474c8 100644
--- a/android/net/LinkProperties.java
+++ b/android/net/LinkProperties.java
@@ -683,9 +683,9 @@
      */
     public boolean hasIPv4Address() {
         for (LinkAddress address : mLinkAddresses) {
-          if (address.getAddress() instanceof Inet4Address) {
-            return true;
-          }
+            if (address.getAddress() instanceof Inet4Address) {
+                return true;
+            }
         }
         return false;
     }
@@ -725,9 +725,9 @@
      */
     public boolean hasIPv4DefaultRoute() {
         for (RouteInfo r : mRoutes) {
-          if (r.isIPv4Default()) {
-            return true;
-          }
+            if (r.isIPv4Default()) {
+                return true;
+            }
         }
         return false;
     }
@@ -740,9 +740,9 @@
      */
     public boolean hasIPv6DefaultRoute() {
         for (RouteInfo r : mRoutes) {
-          if (r.isIPv6Default()) {
-            return true;
-          }
+            if (r.isIPv6Default()) {
+                return true;
+            }
         }
         return false;
     }
@@ -755,9 +755,9 @@
      */
     public boolean hasIPv4DnsServer() {
         for (InetAddress ia : mDnses) {
-          if (ia instanceof Inet4Address) {
-            return true;
-          }
+            if (ia instanceof Inet4Address) {
+                return true;
+            }
         }
         return false;
     }
@@ -770,9 +770,9 @@
      */
     public boolean hasIPv6DnsServer() {
         for (InetAddress ia : mDnses) {
-          if (ia instanceof Inet6Address) {
-            return true;
-          }
+            if (ia instanceof Inet6Address) {
+                return true;
+            }
         }
         return false;
     }
diff --git a/android/net/apf/ApfFilter.java b/android/net/apf/ApfFilter.java
index 190b3a6..5c2b66f 100644
--- a/android/net/apf/ApfFilter.java
+++ b/android/net/apf/ApfFilter.java
@@ -33,7 +33,7 @@
 import android.net.apf.ApfGenerator;
 import android.net.apf.ApfGenerator.IllegalInstructionException;
 import android.net.apf.ApfGenerator.Register;
-import android.net.ip.IpManager;
+import android.net.ip.IpClient;
 import android.net.metrics.ApfProgramEvent;
 import android.net.metrics.ApfStats;
 import android.net.metrics.IpConnectivityLog;
@@ -238,7 +238,7 @@
     private static final int APF_MAX_ETH_TYPE_BLACK_LIST_LEN = 20;
 
     private final ApfCapabilities mApfCapabilities;
-    private final IpManager.Callback mIpManagerCallback;
+    private final IpClient.Callback mIpClientCallback;
     private final NetworkInterface mNetworkInterface;
     private final IpConnectivityLog mMetricsLog;
 
@@ -262,10 +262,10 @@
 
     @VisibleForTesting
     ApfFilter(ApfCapabilities apfCapabilities, NetworkInterface networkInterface,
-            IpManager.Callback ipManagerCallback, boolean multicastFilter,
+            IpClient.Callback ipClientCallback, boolean multicastFilter,
             boolean ieee802_3Filter, int[] ethTypeBlackList, IpConnectivityLog log) {
         mApfCapabilities = apfCapabilities;
-        mIpManagerCallback = ipManagerCallback;
+        mIpClientCallback = ipClientCallback;
         mNetworkInterface = networkInterface;
         mMulticastFilter = multicastFilter;
         mDrop802_3Frames = ieee802_3Filter;
@@ -275,7 +275,7 @@
 
         mMetricsLog = log;
 
-        // TODO: ApfFilter should not generate programs until IpManager sends provisioning success.
+        // TODO: ApfFilter should not generate programs until IpClient sends provisioning success.
         maybeStartFilter();
     }
 
@@ -1051,7 +1051,7 @@
         if (VDBG) {
             hexDump("Installing filter: ", program, program.length);
         }
-        mIpManagerCallback.installPacketFilter(program);
+        mIpClientCallback.installPacketFilter(program);
         logApfProgramEventLocked(now);
         mLastInstallEvent = new ApfProgramEvent();
         mLastInstallEvent.lifetime = programMinLifetime;
@@ -1161,7 +1161,7 @@
      * filtering using APF programs.
      */
     public static ApfFilter maybeCreate(ApfCapabilities apfCapabilities,
-            NetworkInterface networkInterface, IpManager.Callback ipManagerCallback,
+            NetworkInterface networkInterface, IpClient.Callback ipClientCallback,
             boolean multicastFilter, boolean ieee802_3Filter, int[] ethTypeBlackList) {
         if (apfCapabilities == null || networkInterface == null) return null;
         if (apfCapabilities.apfVersionSupported == 0) return null;
@@ -1178,7 +1178,7 @@
             Log.e(TAG, "Unsupported APF version: " + apfCapabilities.apfVersionSupported);
             return null;
         }
-        return new ApfFilter(apfCapabilities, networkInterface, ipManagerCallback,
+        return new ApfFilter(apfCapabilities, networkInterface, ipClientCallback,
                 multicastFilter, ieee802_3Filter, ethTypeBlackList, new IpConnectivityLog());
     }
 
diff --git a/android/net/ip/ConnectivityPacketTracker.java b/android/net/ip/ConnectivityPacketTracker.java
index 0230f36..1925c39 100644
--- a/android/net/ip/ConnectivityPacketTracker.java
+++ b/android/net/ip/ConnectivityPacketTracker.java
@@ -25,6 +25,7 @@
 import android.system.ErrnoException;
 import android.system.Os;
 import android.system.PacketSocketAddress;
+import android.text.TextUtils;
 import android.util.Log;
 import android.util.LocalLog;
 
@@ -59,11 +60,14 @@
     private static final boolean DBG = false;
     private static final String MARK_START = "--- START ---";
     private static final String MARK_STOP = "--- STOP ---";
+    private static final String MARK_NAMED_START = "--- START (%s) ---";
+    private static final String MARK_NAMED_STOP = "--- STOP (%s) ---";
 
     private final String mTag;
     private final LocalLog mLog;
     private final BlockingSocketReader mPacketListener;
     private boolean mRunning;
+    private String mDisplayName;
 
     public ConnectivityPacketTracker(Handler h, NetworkInterface netif, LocalLog log) {
         final String ifname;
@@ -85,14 +89,16 @@
         mPacketListener = new PacketListener(h, ifindex, hwaddr, mtu);
     }
 
-    public void start() {
+    public void start(String displayName) {
         mRunning = true;
+        mDisplayName = displayName;
         mPacketListener.start();
     }
 
     public void stop() {
         mPacketListener.stop();
         mRunning = false;
+        mDisplayName = null;
     }
 
     private final class PacketListener extends BlockingSocketReader {
@@ -133,16 +139,19 @@
 
         @Override
         protected void onStart() {
-            mLog.log(MARK_START);
+            final String msg = TextUtils.isEmpty(mDisplayName)
+                    ? MARK_START
+                    : String.format(MARK_NAMED_START, mDisplayName);
+            mLog.log(msg);
         }
 
         @Override
         protected void onStop() {
-            if (mRunning) {
-                mLog.log(MARK_STOP);
-            } else {
-                mLog.log(MARK_STOP + " (packet listener stopped unexpectedly)");
-            }
+            String msg = TextUtils.isEmpty(mDisplayName)
+                    ? MARK_STOP
+                    : String.format(MARK_NAMED_STOP, mDisplayName);
+            if (!mRunning) msg += " (packet listener stopped unexpectedly)";
+            mLog.log(msg);
         }
 
         @Override
diff --git a/android/net/ip/IpClient.java b/android/net/ip/IpClient.java
new file mode 100644
index 0000000..2359fab
--- /dev/null
+++ b/android/net/ip/IpClient.java
@@ -0,0 +1,1712 @@
+/*
+ * Copyright (C) 2017 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.net.ip;
+
+import com.android.internal.util.MessageUtils;
+import com.android.internal.util.WakeupMessage;
+
+import android.content.Context;
+import android.net.DhcpResults;
+import android.net.INetd;
+import android.net.IpPrefix;
+import android.net.LinkAddress;
+import android.net.LinkProperties.ProvisioningChange;
+import android.net.LinkProperties;
+import android.net.Network;
+import android.net.ProxyInfo;
+import android.net.RouteInfo;
+import android.net.StaticIpConfiguration;
+import android.net.apf.ApfCapabilities;
+import android.net.apf.ApfFilter;
+import android.net.dhcp.DhcpClient;
+import android.net.metrics.IpConnectivityLog;
+import android.net.metrics.IpManagerEvent;
+import android.net.util.MultinetworkPolicyTracker;
+import android.net.util.NetdService;
+import android.net.util.NetworkConstants;
+import android.net.util.SharedLog;
+import android.os.INetworkManagementService;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.text.TextUtils;
+import android.util.LocalLog;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.R;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.internal.util.IState;
+import com.android.internal.util.Preconditions;
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
+import com.android.server.net.NetlinkTracker;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.List;
+import java.util.Set;
+import java.util.StringJoiner;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+
+/**
+ * IpClient
+ *
+ * This class provides the interface to IP-layer provisioning and maintenance
+ * functionality that can be used by transport layers like Wi-Fi, Ethernet,
+ * et cetera.
+ *
+ * [ Lifetime ]
+ * IpClient is designed to be instantiated as soon as the interface name is
+ * known and can be as long-lived as the class containing it (i.e. declaring
+ * it "private final" is okay).
+ *
+ * @hide
+ */
+public class IpClient extends StateMachine {
+    private static final boolean DBG = false;
+
+    // For message logging.
+    private static final Class[] sMessageClasses = { IpClient.class, DhcpClient.class };
+    private static final SparseArray<String> sWhatToString =
+            MessageUtils.findMessageNames(sMessageClasses);
+
+    /**
+     * Callbacks for handling IpClient events.
+     */
+    public static class Callback {
+        // In order to receive onPreDhcpAction(), call #withPreDhcpAction()
+        // when constructing a ProvisioningConfiguration.
+        //
+        // Implementations of onPreDhcpAction() must call
+        // IpClient#completedPreDhcpAction() to indicate that DHCP is clear
+        // to proceed.
+        public void onPreDhcpAction() {}
+        public void onPostDhcpAction() {}
+
+        // This is purely advisory and not an indication of provisioning
+        // success or failure.  This is only here for callers that want to
+        // expose DHCPv4 results to other APIs (e.g., WifiInfo#setInetAddress).
+        // DHCPv4 or static IPv4 configuration failure or success can be
+        // determined by whether or not the passed-in DhcpResults object is
+        // null or not.
+        public void onNewDhcpResults(DhcpResults dhcpResults) {}
+
+        public void onProvisioningSuccess(LinkProperties newLp) {}
+        public void onProvisioningFailure(LinkProperties newLp) {}
+
+        // Invoked on LinkProperties changes.
+        public void onLinkPropertiesChange(LinkProperties newLp) {}
+
+        // Called when the internal IpReachabilityMonitor (if enabled) has
+        // detected the loss of a critical number of required neighbors.
+        public void onReachabilityLost(String logMsg) {}
+
+        // Called when the IpClient state machine terminates.
+        public void onQuit() {}
+
+        // Install an APF program to filter incoming packets.
+        public void installPacketFilter(byte[] filter) {}
+
+        // If multicast filtering cannot be accomplished with APF, this function will be called to
+        // actuate multicast filtering using another means.
+        public void setFallbackMulticastFilter(boolean enabled) {}
+
+        // Enabled/disable Neighbor Discover offload functionality. This is
+        // called, for example, whenever 464xlat is being started or stopped.
+        public void setNeighborDiscoveryOffload(boolean enable) {}
+    }
+
+    // Use a wrapper class to log in order to ensure complete and detailed
+    // logging. This method is lighter weight than annotations/reflection
+    // and has the following benefits:
+    //
+    //     - No invoked method can be forgotten.
+    //       Any new method added to IpClient.Callback must be overridden
+    //       here or it will never be called.
+    //
+    //     - No invoking call site can be forgotten.
+    //       Centralized logging in this way means call sites don't need to
+    //       remember to log, and therefore no call site can be forgotten.
+    //
+    //     - No variation in log format among call sites.
+    //       Encourages logging of any available arguments, and all call sites
+    //       are necessarily logged identically.
+    //
+    // TODO: Find an lighter weight approach.
+    private class LoggingCallbackWrapper extends Callback {
+        private static final String PREFIX = "INVOKE ";
+        private Callback mCallback;
+
+        public LoggingCallbackWrapper(Callback callback) {
+            mCallback = callback;
+        }
+
+        private void log(String msg) {
+            mLog.log(PREFIX + msg);
+        }
+
+        @Override
+        public void onPreDhcpAction() {
+            mCallback.onPreDhcpAction();
+            log("onPreDhcpAction()");
+        }
+        @Override
+        public void onPostDhcpAction() {
+            mCallback.onPostDhcpAction();
+            log("onPostDhcpAction()");
+        }
+        @Override
+        public void onNewDhcpResults(DhcpResults dhcpResults) {
+            mCallback.onNewDhcpResults(dhcpResults);
+            log("onNewDhcpResults({" + dhcpResults + "})");
+        }
+        @Override
+        public void onProvisioningSuccess(LinkProperties newLp) {
+            mCallback.onProvisioningSuccess(newLp);
+            log("onProvisioningSuccess({" + newLp + "})");
+        }
+        @Override
+        public void onProvisioningFailure(LinkProperties newLp) {
+            mCallback.onProvisioningFailure(newLp);
+            log("onProvisioningFailure({" + newLp + "})");
+        }
+        @Override
+        public void onLinkPropertiesChange(LinkProperties newLp) {
+            mCallback.onLinkPropertiesChange(newLp);
+            log("onLinkPropertiesChange({" + newLp + "})");
+        }
+        @Override
+        public void onReachabilityLost(String logMsg) {
+            mCallback.onReachabilityLost(logMsg);
+            log("onReachabilityLost(" + logMsg + ")");
+        }
+        @Override
+        public void onQuit() {
+            mCallback.onQuit();
+            log("onQuit()");
+        }
+        @Override
+        public void installPacketFilter(byte[] filter) {
+            mCallback.installPacketFilter(filter);
+            log("installPacketFilter(byte[" + filter.length + "])");
+        }
+        @Override
+        public void setFallbackMulticastFilter(boolean enabled) {
+            mCallback.setFallbackMulticastFilter(enabled);
+            log("setFallbackMulticastFilter(" + enabled + ")");
+        }
+        @Override
+        public void setNeighborDiscoveryOffload(boolean enable) {
+            mCallback.setNeighborDiscoveryOffload(enable);
+            log("setNeighborDiscoveryOffload(" + enable + ")");
+        }
+    }
+
+    /**
+     * This class encapsulates parameters to be passed to
+     * IpClient#startProvisioning(). A defensive copy is made by IpClient
+     * and the values specified herein are in force until IpClient#stop()
+     * is called.
+     *
+     * Example use:
+     *
+     *     final ProvisioningConfiguration config =
+     *             mIpClient.buildProvisioningConfiguration()
+     *                     .withPreDhcpAction()
+     *                     .withProvisioningTimeoutMs(36 * 1000)
+     *                     .build();
+     *     mIpClient.startProvisioning(config);
+     *     ...
+     *     mIpClient.stop();
+     *
+     * The specified provisioning configuration will only be active until
+     * IpClient#stop() is called. Future calls to IpClient#startProvisioning()
+     * must specify the configuration again.
+     */
+    public static class ProvisioningConfiguration {
+        // TODO: Delete this default timeout once those callers that care are
+        // fixed to pass in their preferred timeout.
+        //
+        // We pick 36 seconds so we can send DHCP requests at
+        //
+        //     t=0, t=2, t=6, t=14, t=30
+        //
+        // allowing for 10% jitter.
+        private static final int DEFAULT_TIMEOUT_MS = 36 * 1000;
+
+        public static class Builder {
+            private ProvisioningConfiguration mConfig = new ProvisioningConfiguration();
+
+            public Builder withoutIPv4() {
+                mConfig.mEnableIPv4 = false;
+                return this;
+            }
+
+            public Builder withoutIPv6() {
+                mConfig.mEnableIPv6 = false;
+                return this;
+            }
+
+            public Builder withoutIpReachabilityMonitor() {
+                mConfig.mUsingIpReachabilityMonitor = false;
+                return this;
+            }
+
+            public Builder withPreDhcpAction() {
+                mConfig.mRequestedPreDhcpActionMs = DEFAULT_TIMEOUT_MS;
+                return this;
+            }
+
+            public Builder withPreDhcpAction(int dhcpActionTimeoutMs) {
+                mConfig.mRequestedPreDhcpActionMs = dhcpActionTimeoutMs;
+                return this;
+            }
+
+            public Builder withInitialConfiguration(InitialConfiguration initialConfig) {
+                mConfig.mInitialConfig = initialConfig;
+                return this;
+            }
+
+            public Builder withStaticConfiguration(StaticIpConfiguration staticConfig) {
+                mConfig.mStaticIpConfig = staticConfig;
+                return this;
+            }
+
+            public Builder withApfCapabilities(ApfCapabilities apfCapabilities) {
+                mConfig.mApfCapabilities = apfCapabilities;
+                return this;
+            }
+
+            public Builder withProvisioningTimeoutMs(int timeoutMs) {
+                mConfig.mProvisioningTimeoutMs = timeoutMs;
+                return this;
+            }
+
+            public Builder withIPv6AddrGenModeEUI64() {
+                mConfig.mIPv6AddrGenMode = INetd.IPV6_ADDR_GEN_MODE_EUI64;
+                return this;
+            }
+
+            public Builder withIPv6AddrGenModeStablePrivacy() {
+                mConfig.mIPv6AddrGenMode = INetd.IPV6_ADDR_GEN_MODE_STABLE_PRIVACY;
+                return this;
+            }
+
+            public Builder withNetwork(Network network) {
+                mConfig.mNetwork = network;
+                return this;
+            }
+
+            public Builder withDisplayName(String displayName) {
+                mConfig.mDisplayName = displayName;
+                return this;
+            }
+
+            public ProvisioningConfiguration build() {
+                return new ProvisioningConfiguration(mConfig);
+            }
+        }
+
+        /* package */ boolean mEnableIPv4 = true;
+        /* package */ boolean mEnableIPv6 = true;
+        /* package */ boolean mUsingIpReachabilityMonitor = true;
+        /* package */ int mRequestedPreDhcpActionMs;
+        /* package */ InitialConfiguration mInitialConfig;
+        /* package */ StaticIpConfiguration mStaticIpConfig;
+        /* package */ ApfCapabilities mApfCapabilities;
+        /* package */ int mProvisioningTimeoutMs = DEFAULT_TIMEOUT_MS;
+        /* package */ int mIPv6AddrGenMode = INetd.IPV6_ADDR_GEN_MODE_STABLE_PRIVACY;
+        /* package */ Network mNetwork = null;
+        /* package */ String mDisplayName = null;
+
+        public ProvisioningConfiguration() {} // used by Builder
+
+        public ProvisioningConfiguration(ProvisioningConfiguration other) {
+            mEnableIPv4 = other.mEnableIPv4;
+            mEnableIPv6 = other.mEnableIPv6;
+            mUsingIpReachabilityMonitor = other.mUsingIpReachabilityMonitor;
+            mRequestedPreDhcpActionMs = other.mRequestedPreDhcpActionMs;
+            mInitialConfig = InitialConfiguration.copy(other.mInitialConfig);
+            mStaticIpConfig = other.mStaticIpConfig;
+            mApfCapabilities = other.mApfCapabilities;
+            mProvisioningTimeoutMs = other.mProvisioningTimeoutMs;
+            mIPv6AddrGenMode = other.mIPv6AddrGenMode;
+            mNetwork = other.mNetwork;
+            mDisplayName = other.mDisplayName;
+        }
+
+        @Override
+        public String toString() {
+            return new StringJoiner(", ", getClass().getSimpleName() + "{", "}")
+                    .add("mEnableIPv4: " + mEnableIPv4)
+                    .add("mEnableIPv6: " + mEnableIPv6)
+                    .add("mUsingIpReachabilityMonitor: " + mUsingIpReachabilityMonitor)
+                    .add("mRequestedPreDhcpActionMs: " + mRequestedPreDhcpActionMs)
+                    .add("mInitialConfig: " + mInitialConfig)
+                    .add("mStaticIpConfig: " + mStaticIpConfig)
+                    .add("mApfCapabilities: " + mApfCapabilities)
+                    .add("mProvisioningTimeoutMs: " + mProvisioningTimeoutMs)
+                    .add("mIPv6AddrGenMode: " + mIPv6AddrGenMode)
+                    .add("mNetwork: " + mNetwork)
+                    .add("mDisplayName: " + mDisplayName)
+                    .toString();
+        }
+
+        public boolean isValid() {
+            return (mInitialConfig == null) || mInitialConfig.isValid();
+        }
+    }
+
+    public static class InitialConfiguration {
+        public final Set<LinkAddress> ipAddresses = new HashSet<>();
+        public final Set<IpPrefix> directlyConnectedRoutes = new HashSet<>();
+        public final Set<InetAddress> dnsServers = new HashSet<>();
+        public Inet4Address gateway; // WiFi legacy behavior with static ipv4 config
+
+        public static InitialConfiguration copy(InitialConfiguration config) {
+            if (config == null) {
+                return null;
+            }
+            InitialConfiguration configCopy = new InitialConfiguration();
+            configCopy.ipAddresses.addAll(config.ipAddresses);
+            configCopy.directlyConnectedRoutes.addAll(config.directlyConnectedRoutes);
+            configCopy.dnsServers.addAll(config.dnsServers);
+            return configCopy;
+        }
+
+        @Override
+        public String toString() {
+            return String.format(
+                    "InitialConfiguration(IPs: {%s}, prefixes: {%s}, DNS: {%s}, v4 gateway: %s)",
+                    join(", ", ipAddresses), join(", ", directlyConnectedRoutes),
+                    join(", ", dnsServers), gateway);
+        }
+
+        public boolean isValid() {
+            if (ipAddresses.isEmpty()) {
+                return false;
+            }
+
+            // For every IP address, there must be at least one prefix containing that address.
+            for (LinkAddress addr : ipAddresses) {
+                if (!any(directlyConnectedRoutes, (p) -> p.contains(addr.getAddress()))) {
+                    return false;
+                }
+            }
+            // For every dns server, there must be at least one prefix containing that address.
+            for (InetAddress addr : dnsServers) {
+                if (!any(directlyConnectedRoutes, (p) -> p.contains(addr))) {
+                    return false;
+                }
+            }
+            // All IPv6 LinkAddresses have an RFC7421-suitable prefix length
+            // (read: compliant with RFC4291#section2.5.4).
+            if (any(ipAddresses, not(InitialConfiguration::isPrefixLengthCompliant))) {
+                return false;
+            }
+            // If directlyConnectedRoutes contains an IPv6 default route
+            // then ipAddresses MUST contain at least one non-ULA GUA.
+            if (any(directlyConnectedRoutes, InitialConfiguration::isIPv6DefaultRoute)
+                    && all(ipAddresses, not(InitialConfiguration::isIPv6GUA))) {
+                return false;
+            }
+            // The prefix length of routes in directlyConnectedRoutes be within reasonable
+            // bounds for IPv6: /48-/64 just as we’d accept in RIOs.
+            if (any(directlyConnectedRoutes, not(InitialConfiguration::isPrefixLengthCompliant))) {
+                return false;
+            }
+            // There no more than one IPv4 address
+            if (ipAddresses.stream().filter(Inet4Address.class::isInstance).count() > 1) {
+                return false;
+            }
+
+            return true;
+        }
+
+        /**
+         * @return true if the given list of addressess and routes satisfies provisioning for this
+         * InitialConfiguration. LinkAddresses and RouteInfo objects are not compared with equality
+         * because addresses and routes seen by Netlink will contain additional fields like flags,
+         * interfaces, and so on. If this InitialConfiguration has no IP address specified, the
+         * provisioning check always fails.
+         *
+         * If the given list of routes is null, only addresses are taken into considerations.
+         */
+        public boolean isProvisionedBy(List<LinkAddress> addresses, List<RouteInfo> routes) {
+            if (ipAddresses.isEmpty()) {
+                return false;
+            }
+
+            for (LinkAddress addr : ipAddresses) {
+                if (!any(addresses, (addrSeen) -> addr.isSameAddressAs(addrSeen))) {
+                    return false;
+                }
+            }
+
+            if (routes != null) {
+                for (IpPrefix prefix : directlyConnectedRoutes) {
+                    if (!any(routes, (routeSeen) -> isDirectlyConnectedRoute(routeSeen, prefix))) {
+                        return false;
+                    }
+                }
+            }
+
+            return true;
+        }
+
+        private static boolean isDirectlyConnectedRoute(RouteInfo route, IpPrefix prefix) {
+            return !route.hasGateway() && prefix.equals(route.getDestination());
+        }
+
+        private static boolean isPrefixLengthCompliant(LinkAddress addr) {
+            return addr.isIPv4() || isCompliantIPv6PrefixLength(addr.getPrefixLength());
+        }
+
+        private static boolean isPrefixLengthCompliant(IpPrefix prefix) {
+            return prefix.isIPv4() || isCompliantIPv6PrefixLength(prefix.getPrefixLength());
+        }
+
+        private static boolean isCompliantIPv6PrefixLength(int prefixLength) {
+            return (NetworkConstants.RFC6177_MIN_PREFIX_LENGTH <= prefixLength)
+                    && (prefixLength <= NetworkConstants.RFC7421_PREFIX_LENGTH);
+        }
+
+        private static boolean isIPv6DefaultRoute(IpPrefix prefix) {
+            return prefix.getAddress().equals(Inet6Address.ANY);
+        }
+
+        private static boolean isIPv6GUA(LinkAddress addr) {
+            return addr.isIPv6() && addr.isGlobalPreferred();
+        }
+    }
+
+    public static final String DUMP_ARG = "ipclient";
+    public static final String DUMP_ARG_CONFIRM = "confirm";
+
+    private static final int CMD_TERMINATE_AFTER_STOP             = 1;
+    private static final int CMD_STOP                             = 2;
+    private static final int CMD_START                            = 3;
+    private static final int CMD_CONFIRM                          = 4;
+    private static final int EVENT_PRE_DHCP_ACTION_COMPLETE       = 5;
+    // Sent by NetlinkTracker to communicate netlink events.
+    private static final int EVENT_NETLINK_LINKPROPERTIES_CHANGED = 6;
+    private static final int CMD_UPDATE_TCP_BUFFER_SIZES          = 7;
+    private static final int CMD_UPDATE_HTTP_PROXY                = 8;
+    private static final int CMD_SET_MULTICAST_FILTER             = 9;
+    private static final int EVENT_PROVISIONING_TIMEOUT           = 10;
+    private static final int EVENT_DHCPACTION_TIMEOUT             = 11;
+
+    private static final int MAX_LOG_RECORDS = 500;
+    private static final int MAX_PACKET_RECORDS = 100;
+
+    private static final boolean NO_CALLBACKS = false;
+    private static final boolean SEND_CALLBACKS = true;
+
+    // This must match the interface prefix in clatd.c.
+    // TODO: Revert this hack once IpClient and Nat464Xlat work in concert.
+    private static final String CLAT_PREFIX = "v4-";
+
+    private final State mStoppedState = new StoppedState();
+    private final State mStoppingState = new StoppingState();
+    private final State mStartedState = new StartedState();
+    private final State mRunningState = new RunningState();
+
+    private final String mTag;
+    private final Context mContext;
+    private final String mInterfaceName;
+    private final String mClatInterfaceName;
+    @VisibleForTesting
+    protected final Callback mCallback;
+    private final INetworkManagementService mNwService;
+    private final NetlinkTracker mNetlinkTracker;
+    private final WakeupMessage mProvisioningTimeoutAlarm;
+    private final WakeupMessage mDhcpActionTimeoutAlarm;
+    private final MultinetworkPolicyTracker mMultinetworkPolicyTracker;
+    private final SharedLog mLog;
+    private final LocalLog mConnectivityPacketLog;
+    private final MessageHandlingLogger mMsgStateLogger;
+    private final IpConnectivityLog mMetricsLog = new IpConnectivityLog();
+    private final InterfaceController mInterfaceCtrl;
+
+    private NetworkInterface mNetworkInterface;
+
+    /**
+     * Non-final member variables accessed only from within our StateMachine.
+     */
+    private LinkProperties mLinkProperties;
+    private ProvisioningConfiguration mConfiguration;
+    private IpReachabilityMonitor mIpReachabilityMonitor;
+    private DhcpClient mDhcpClient;
+    private DhcpResults mDhcpResults;
+    private String mTcpBufferSizes;
+    private ProxyInfo mHttpProxy;
+    private ApfFilter mApfFilter;
+    private boolean mMulticastFiltering;
+    private long mStartTimeMillis;
+
+    public IpClient(Context context, String ifName, Callback callback) {
+        this(context, ifName, callback, INetworkManagementService.Stub.asInterface(
+                ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE)),
+                NetdService.getInstance());
+    }
+
+    /**
+     * An expanded constructor, useful for dependency injection.
+     * TODO: migrate all test users to mock IpClient directly and remove this ctor.
+     */
+    public IpClient(Context context, String ifName, Callback callback,
+            INetworkManagementService nwService) {
+        this(context, ifName, callback, nwService, NetdService.getInstance());
+    }
+
+    @VisibleForTesting
+    IpClient(Context context, String ifName, Callback callback,
+            INetworkManagementService nwService, INetd netd) {
+        super(IpClient.class.getSimpleName() + "." + ifName);
+        mTag = getName();
+
+        mContext = context;
+        mInterfaceName = ifName;
+        mClatInterfaceName = CLAT_PREFIX + ifName;
+        mCallback = new LoggingCallbackWrapper(callback);
+        mNwService = nwService;
+
+        mLog = new SharedLog(MAX_LOG_RECORDS, mTag);
+        mConnectivityPacketLog = new LocalLog(MAX_PACKET_RECORDS);
+        mMsgStateLogger = new MessageHandlingLogger();
+
+        mInterfaceCtrl = new InterfaceController(mInterfaceName, mNwService, netd, mLog);
+
+        mNetlinkTracker = new NetlinkTracker(
+                mInterfaceName,
+                new NetlinkTracker.Callback() {
+                    @Override
+                    public void update() {
+                        sendMessage(EVENT_NETLINK_LINKPROPERTIES_CHANGED);
+                    }
+                }) {
+            @Override
+            public void interfaceAdded(String iface) {
+                super.interfaceAdded(iface);
+                if (mClatInterfaceName.equals(iface)) {
+                    mCallback.setNeighborDiscoveryOffload(false);
+                } else if (!mInterfaceName.equals(iface)) {
+                    return;
+                }
+
+                final String msg = "interfaceAdded(" + iface +")";
+                logMsg(msg);
+            }
+
+            @Override
+            public void interfaceRemoved(String iface) {
+                super.interfaceRemoved(iface);
+                // TODO: Also observe mInterfaceName going down and take some
+                // kind of appropriate action.
+                if (mClatInterfaceName.equals(iface)) {
+                    // TODO: consider sending a message to the IpClient main
+                    // StateMachine thread, in case "NDO enabled" state becomes
+                    // tied to more things that 464xlat operation.
+                    mCallback.setNeighborDiscoveryOffload(true);
+                } else if (!mInterfaceName.equals(iface)) {
+                    return;
+                }
+
+                final String msg = "interfaceRemoved(" + iface +")";
+                logMsg(msg);
+            }
+
+            private void logMsg(String msg) {
+                Log.d(mTag, msg);
+                getHandler().post(() -> { mLog.log("OBSERVED " + msg); });
+            }
+        };
+
+        mLinkProperties = new LinkProperties();
+        mLinkProperties.setInterfaceName(mInterfaceName);
+
+        mMultinetworkPolicyTracker = new MultinetworkPolicyTracker(mContext, getHandler(),
+                () -> { mLog.log("OBSERVED AvoidBadWifi changed"); });
+
+        mProvisioningTimeoutAlarm = new WakeupMessage(mContext, getHandler(),
+                mTag + ".EVENT_PROVISIONING_TIMEOUT", EVENT_PROVISIONING_TIMEOUT);
+        mDhcpActionTimeoutAlarm = new WakeupMessage(mContext, getHandler(),
+                mTag + ".EVENT_DHCPACTION_TIMEOUT", EVENT_DHCPACTION_TIMEOUT);
+
+        // Anything the StateMachine may access must have been instantiated
+        // before this point.
+        configureAndStartStateMachine();
+
+        // Anything that may send messages to the StateMachine must only be
+        // configured to do so after the StateMachine has started (above).
+        startStateMachineUpdaters();
+    }
+
+    private void configureAndStartStateMachine() {
+        addState(mStoppedState);
+        addState(mStartedState);
+            addState(mRunningState, mStartedState);
+        addState(mStoppingState);
+
+        setInitialState(mStoppedState);
+
+        super.start();
+    }
+
+    private void startStateMachineUpdaters() {
+        try {
+            mNwService.registerObserver(mNetlinkTracker);
+        } catch (RemoteException e) {
+            logError("Couldn't register NetlinkTracker: %s", e);
+        }
+
+        mMultinetworkPolicyTracker.start();
+    }
+
+    private void stopStateMachineUpdaters() {
+        try {
+            mNwService.unregisterObserver(mNetlinkTracker);
+        } catch (RemoteException e) {
+            logError("Couldn't unregister NetlinkTracker: %s", e);
+        }
+
+        mMultinetworkPolicyTracker.shutdown();
+    }
+
+    @Override
+    protected void onQuitting() {
+        mCallback.onQuit();
+    }
+
+    // Shut down this IpClient instance altogether.
+    public void shutdown() {
+        stop();
+        sendMessage(CMD_TERMINATE_AFTER_STOP);
+    }
+
+    public static ProvisioningConfiguration.Builder buildProvisioningConfiguration() {
+        return new ProvisioningConfiguration.Builder();
+    }
+
+    public void startProvisioning(ProvisioningConfiguration req) {
+        if (!req.isValid()) {
+            doImmediateProvisioningFailure(IpManagerEvent.ERROR_INVALID_PROVISIONING);
+            return;
+        }
+
+        getNetworkInterface();
+
+        mCallback.setNeighborDiscoveryOffload(true);
+        sendMessage(CMD_START, new ProvisioningConfiguration(req));
+    }
+
+    // TODO: Delete this.
+    public void startProvisioning(StaticIpConfiguration staticIpConfig) {
+        startProvisioning(buildProvisioningConfiguration()
+                .withStaticConfiguration(staticIpConfig)
+                .build());
+    }
+
+    public void startProvisioning() {
+        startProvisioning(new ProvisioningConfiguration());
+    }
+
+    public void stop() {
+        sendMessage(CMD_STOP);
+    }
+
+    public void confirmConfiguration() {
+        sendMessage(CMD_CONFIRM);
+    }
+
+    public void completedPreDhcpAction() {
+        sendMessage(EVENT_PRE_DHCP_ACTION_COMPLETE);
+    }
+
+    /**
+     * Set the TCP buffer sizes to use.
+     *
+     * This may be called, repeatedly, at any time before or after a call to
+     * #startProvisioning(). The setting is cleared upon calling #stop().
+     */
+    public void setTcpBufferSizes(String tcpBufferSizes) {
+        sendMessage(CMD_UPDATE_TCP_BUFFER_SIZES, tcpBufferSizes);
+    }
+
+    /**
+     * Set the HTTP Proxy configuration to use.
+     *
+     * This may be called, repeatedly, at any time before or after a call to
+     * #startProvisioning(). The setting is cleared upon calling #stop().
+     */
+    public void setHttpProxy(ProxyInfo proxyInfo) {
+        sendMessage(CMD_UPDATE_HTTP_PROXY, proxyInfo);
+    }
+
+    /**
+     * Enable or disable the multicast filter.  Attempts to use APF to accomplish the filtering,
+     * if not, Callback.setFallbackMulticastFilter() is called.
+     */
+    public void setMulticastFilter(boolean enabled) {
+        sendMessage(CMD_SET_MULTICAST_FILTER, enabled);
+    }
+
+    public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+        if (args != null && args.length > 0 && DUMP_ARG_CONFIRM.equals(args[0])) {
+            // Execute confirmConfiguration() and take no further action.
+            confirmConfiguration();
+            return;
+        }
+
+        // Thread-unsafe access to mApfFilter but just used for debugging.
+        final ApfFilter apfFilter = mApfFilter;
+        final ProvisioningConfiguration provisioningConfig = mConfiguration;
+        final ApfCapabilities apfCapabilities = (provisioningConfig != null)
+                ? provisioningConfig.mApfCapabilities : null;
+
+        IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
+        pw.println(mTag + " APF dump:");
+        pw.increaseIndent();
+        if (apfFilter != null) {
+            apfFilter.dump(pw);
+        } else {
+            pw.print("No active ApfFilter; ");
+            if (provisioningConfig == null) {
+                pw.println("IpClient not yet started.");
+            } else if (apfCapabilities == null || apfCapabilities.apfVersionSupported == 0) {
+                pw.println("Hardware does not support APF.");
+            } else {
+                pw.println("ApfFilter not yet started, APF capabilities: " + apfCapabilities);
+            }
+        }
+        pw.decreaseIndent();
+
+        pw.println();
+        pw.println(mTag + " current ProvisioningConfiguration:");
+        pw.increaseIndent();
+        pw.println(Objects.toString(provisioningConfig, "N/A"));
+        pw.decreaseIndent();
+
+        pw.println();
+        pw.println(mTag + " StateMachine dump:");
+        pw.increaseIndent();
+        mLog.dump(fd, pw, args);
+        pw.decreaseIndent();
+
+        pw.println();
+        pw.println(mTag + " connectivity packet log:");
+        pw.println();
+        pw.println("Debug with python and scapy via:");
+        pw.println("shell$ python");
+        pw.println(">>> from scapy import all as scapy");
+        pw.println(">>> scapy.Ether(\"<paste_hex_string>\".decode(\"hex\")).show2()");
+        pw.println();
+
+        pw.increaseIndent();
+        mConnectivityPacketLog.readOnlyLocalLog().dump(fd, pw, args);
+        pw.decreaseIndent();
+    }
+
+
+    /**
+     * Internals.
+     */
+
+    @Override
+    protected String getWhatToString(int what) {
+        return sWhatToString.get(what, "UNKNOWN: " + Integer.toString(what));
+    }
+
+    @Override
+    protected String getLogRecString(Message msg) {
+        final String logLine = String.format(
+                "%s/%d %d %d %s [%s]",
+                mInterfaceName, mNetworkInterface == null ? -1 : mNetworkInterface.getIndex(),
+                msg.arg1, msg.arg2, Objects.toString(msg.obj), mMsgStateLogger);
+
+        final String richerLogLine = getWhatToString(msg.what) + " " + logLine;
+        mLog.log(richerLogLine);
+        if (DBG) {
+            Log.d(mTag, richerLogLine);
+        }
+
+        mMsgStateLogger.reset();
+        return logLine;
+    }
+
+    @Override
+    protected boolean recordLogRec(Message msg) {
+        // Don't log EVENT_NETLINK_LINKPROPERTIES_CHANGED. They can be noisy,
+        // and we already log any LinkProperties change that results in an
+        // invocation of IpClient.Callback#onLinkPropertiesChange().
+        final boolean shouldLog = (msg.what != EVENT_NETLINK_LINKPROPERTIES_CHANGED);
+        if (!shouldLog) {
+            mMsgStateLogger.reset();
+        }
+        return shouldLog;
+    }
+
+    private void logError(String fmt, Object... args) {
+        final String msg = "ERROR " + String.format(fmt, args);
+        Log.e(mTag, msg);
+        mLog.log(msg);
+    }
+
+    private void getNetworkInterface() {
+        try {
+            mNetworkInterface = NetworkInterface.getByName(mInterfaceName);
+        } catch (SocketException | NullPointerException e) {
+            // TODO: throw new IllegalStateException.
+            logError("Failed to get interface object: %s", e);
+        }
+    }
+
+    // This needs to be called with care to ensure that our LinkProperties
+    // are in sync with the actual LinkProperties of the interface. For example,
+    // we should only call this if we know for sure that there are no IP addresses
+    // assigned to the interface, etc.
+    private void resetLinkProperties() {
+        mNetlinkTracker.clearLinkProperties();
+        mConfiguration = null;
+        mDhcpResults = null;
+        mTcpBufferSizes = "";
+        mHttpProxy = null;
+
+        mLinkProperties = new LinkProperties();
+        mLinkProperties.setInterfaceName(mInterfaceName);
+    }
+
+    private void recordMetric(final int type) {
+        if (mStartTimeMillis <= 0) { Log.wtf(mTag, "Start time undefined!"); }
+        final long duration = SystemClock.elapsedRealtime() - mStartTimeMillis;
+        mMetricsLog.log(mInterfaceName, new IpManagerEvent(type, duration));
+    }
+
+    // For now: use WifiStateMachine's historical notion of provisioned.
+    @VisibleForTesting
+    static boolean isProvisioned(LinkProperties lp, InitialConfiguration config) {
+        // For historical reasons, we should connect even if all we have is
+        // an IPv4 address and nothing else.
+        if (lp.hasIPv4Address() || lp.isProvisioned()) {
+            return true;
+        }
+        if (config == null) {
+            return false;
+        }
+
+        // When an InitialConfiguration is specified, ignore any difference with previous
+        // properties and instead check if properties observed match the desired properties.
+        return config.isProvisionedBy(lp.getLinkAddresses(), lp.getRoutes());
+    }
+
+    // TODO: Investigate folding all this into the existing static function
+    // LinkProperties.compareProvisioning() or some other single function that
+    // takes two LinkProperties objects and returns a ProvisioningChange
+    // object that is a correct and complete assessment of what changed, taking
+    // account of the asymmetries described in the comments in this function.
+    // Then switch to using it everywhere (IpReachabilityMonitor, etc.).
+    private ProvisioningChange compareProvisioning(LinkProperties oldLp, LinkProperties newLp) {
+        ProvisioningChange delta;
+        InitialConfiguration config = mConfiguration != null ? mConfiguration.mInitialConfig : null;
+        final boolean wasProvisioned = isProvisioned(oldLp, config);
+        final boolean isProvisioned = isProvisioned(newLp, config);
+
+        if (!wasProvisioned && isProvisioned) {
+            delta = ProvisioningChange.GAINED_PROVISIONING;
+        } else if (wasProvisioned && isProvisioned) {
+            delta = ProvisioningChange.STILL_PROVISIONED;
+        } else if (!wasProvisioned && !isProvisioned) {
+            delta = ProvisioningChange.STILL_NOT_PROVISIONED;
+        } else {
+            // (wasProvisioned && !isProvisioned)
+            //
+            // Note that this is true even if we lose a configuration element
+            // (e.g., a default gateway) that would not be required to advance
+            // into provisioned state. This is intended: if we have a default
+            // router and we lose it, that's a sure sign of a problem, but if
+            // we connect to a network with no IPv4 DNS servers, we consider
+            // that to be a network without DNS servers and connect anyway.
+            //
+            // See the comment below.
+            delta = ProvisioningChange.LOST_PROVISIONING;
+        }
+
+        final boolean lostIPv6 = oldLp.isIPv6Provisioned() && !newLp.isIPv6Provisioned();
+        final boolean lostIPv4Address = oldLp.hasIPv4Address() && !newLp.hasIPv4Address();
+        final boolean lostIPv6Router = oldLp.hasIPv6DefaultRoute() && !newLp.hasIPv6DefaultRoute();
+
+        // If bad wifi avoidance is disabled, then ignore IPv6 loss of
+        // provisioning. Otherwise, when a hotspot that loses Internet
+        // access sends out a 0-lifetime RA to its clients, the clients
+        // will disconnect and then reconnect, avoiding the bad hotspot,
+        // instead of getting stuck on the bad hotspot. http://b/31827713 .
+        //
+        // This is incorrect because if the hotspot then regains Internet
+        // access with a different prefix, TCP connections on the
+        // deprecated addresses will remain stuck.
+        //
+        // Note that we can still be disconnected by IpReachabilityMonitor
+        // if the IPv6 default gateway (but not the IPv6 DNS servers; see
+        // accompanying code in IpReachabilityMonitor) is unreachable.
+        final boolean ignoreIPv6ProvisioningLoss = !mMultinetworkPolicyTracker.getAvoidBadWifi();
+
+        // Additionally:
+        //
+        // Partial configurations (e.g., only an IPv4 address with no DNS
+        // servers and no default route) are accepted as long as DHCPv4
+        // succeeds. On such a network, isProvisioned() will always return
+        // false, because the configuration is not complete, but we want to
+        // connect anyway. It might be a disconnected network such as a
+        // Chromecast or a wireless printer, for example.
+        //
+        // Because on such a network isProvisioned() will always return false,
+        // delta will never be LOST_PROVISIONING. So check for loss of
+        // provisioning here too.
+        if (lostIPv4Address || (lostIPv6 && !ignoreIPv6ProvisioningLoss)) {
+            delta = ProvisioningChange.LOST_PROVISIONING;
+        }
+
+        // Additionally:
+        //
+        // If the previous link properties had a global IPv6 address and an
+        // IPv6 default route then also consider the loss of that default route
+        // to be a loss of provisioning. See b/27962810.
+        if (oldLp.hasGlobalIPv6Address() && (lostIPv6Router && !ignoreIPv6ProvisioningLoss)) {
+            delta = ProvisioningChange.LOST_PROVISIONING;
+        }
+
+        return delta;
+    }
+
+    private void dispatchCallback(ProvisioningChange delta, LinkProperties newLp) {
+        switch (delta) {
+            case GAINED_PROVISIONING:
+                if (DBG) { Log.d(mTag, "onProvisioningSuccess()"); }
+                recordMetric(IpManagerEvent.PROVISIONING_OK);
+                mCallback.onProvisioningSuccess(newLp);
+                break;
+
+            case LOST_PROVISIONING:
+                if (DBG) { Log.d(mTag, "onProvisioningFailure()"); }
+                recordMetric(IpManagerEvent.PROVISIONING_FAIL);
+                mCallback.onProvisioningFailure(newLp);
+                break;
+
+            default:
+                if (DBG) { Log.d(mTag, "onLinkPropertiesChange()"); }
+                mCallback.onLinkPropertiesChange(newLp);
+                break;
+        }
+    }
+
+    // Updates all IpClient-related state concerned with LinkProperties.
+    // Returns a ProvisioningChange for possibly notifying other interested
+    // parties that are not fronted by IpClient.
+    private ProvisioningChange setLinkProperties(LinkProperties newLp) {
+        if (mApfFilter != null) {
+            mApfFilter.setLinkProperties(newLp);
+        }
+        if (mIpReachabilityMonitor != null) {
+            mIpReachabilityMonitor.updateLinkProperties(newLp);
+        }
+
+        ProvisioningChange delta = compareProvisioning(mLinkProperties, newLp);
+        mLinkProperties = new LinkProperties(newLp);
+
+        if (delta == ProvisioningChange.GAINED_PROVISIONING) {
+            // TODO: Add a proper ProvisionedState and cancel the alarm in
+            // its enter() method.
+            mProvisioningTimeoutAlarm.cancel();
+        }
+
+        return delta;
+    }
+
+    private LinkProperties assembleLinkProperties() {
+        // [1] Create a new LinkProperties object to populate.
+        LinkProperties newLp = new LinkProperties();
+        newLp.setInterfaceName(mInterfaceName);
+
+        // [2] Pull in data from netlink:
+        //         - IPv4 addresses
+        //         - IPv6 addresses
+        //         - IPv6 routes
+        //         - IPv6 DNS servers
+        //
+        // N.B.: this is fundamentally race-prone and should be fixed by
+        // changing NetlinkTracker from a hybrid edge/level model to an
+        // edge-only model, or by giving IpClient its own netlink socket(s)
+        // so as to track all required information directly.
+        LinkProperties netlinkLinkProperties = mNetlinkTracker.getLinkProperties();
+        newLp.setLinkAddresses(netlinkLinkProperties.getLinkAddresses());
+        for (RouteInfo route : netlinkLinkProperties.getRoutes()) {
+            newLp.addRoute(route);
+        }
+        addAllReachableDnsServers(newLp, netlinkLinkProperties.getDnsServers());
+
+        // [3] Add in data from DHCPv4, if available.
+        //
+        // mDhcpResults is never shared with any other owner so we don't have
+        // to worry about concurrent modification.
+        if (mDhcpResults != null) {
+            for (RouteInfo route : mDhcpResults.getRoutes(mInterfaceName)) {
+                newLp.addRoute(route);
+            }
+            addAllReachableDnsServers(newLp, mDhcpResults.dnsServers);
+            newLp.setDomains(mDhcpResults.domains);
+
+            if (mDhcpResults.mtu != 0) {
+                newLp.setMtu(mDhcpResults.mtu);
+            }
+        }
+
+        // [4] Add in TCP buffer sizes and HTTP Proxy config, if available.
+        if (!TextUtils.isEmpty(mTcpBufferSizes)) {
+            newLp.setTcpBufferSizes(mTcpBufferSizes);
+        }
+        if (mHttpProxy != null) {
+            newLp.setHttpProxy(mHttpProxy);
+        }
+
+        // [5] Add data from InitialConfiguration
+        if (mConfiguration != null && mConfiguration.mInitialConfig != null) {
+            InitialConfiguration config = mConfiguration.mInitialConfig;
+            // Add InitialConfiguration routes and dns server addresses once all addresses
+            // specified in the InitialConfiguration have been observed with Netlink.
+            if (config.isProvisionedBy(newLp.getLinkAddresses(), null)) {
+                for (IpPrefix prefix : config.directlyConnectedRoutes) {
+                    newLp.addRoute(new RouteInfo(prefix, null, mInterfaceName));
+                }
+            }
+            addAllReachableDnsServers(newLp, config.dnsServers);
+        }
+        final LinkProperties oldLp = mLinkProperties;
+        if (DBG) {
+            Log.d(mTag, String.format("Netlink-seen LPs: %s, new LPs: %s; old LPs: %s",
+                    netlinkLinkProperties, newLp, oldLp));
+        }
+
+        // TODO: also learn via netlink routes specified by an InitialConfiguration and specified
+        // from a static IP v4 config instead of manually patching them in in steps [3] and [5].
+        return newLp;
+    }
+
+    private static void addAllReachableDnsServers(
+            LinkProperties lp, Iterable<InetAddress> dnses) {
+        // TODO: Investigate deleting this reachability check.  We should be
+        // able to pass everything down to netd and let netd do evaluation
+        // and RFC6724-style sorting.
+        for (InetAddress dns : dnses) {
+            if (!dns.isAnyLocalAddress() && lp.isReachable(dns)) {
+                lp.addDnsServer(dns);
+            }
+        }
+    }
+
+    // Returns false if we have lost provisioning, true otherwise.
+    private boolean handleLinkPropertiesUpdate(boolean sendCallbacks) {
+        final LinkProperties newLp = assembleLinkProperties();
+        if (Objects.equals(newLp, mLinkProperties)) {
+            return true;
+        }
+        final ProvisioningChange delta = setLinkProperties(newLp);
+        if (sendCallbacks) {
+            dispatchCallback(delta, newLp);
+        }
+        return (delta != ProvisioningChange.LOST_PROVISIONING);
+    }
+
+    private void handleIPv4Success(DhcpResults dhcpResults) {
+        mDhcpResults = new DhcpResults(dhcpResults);
+        final LinkProperties newLp = assembleLinkProperties();
+        final ProvisioningChange delta = setLinkProperties(newLp);
+
+        if (DBG) {
+            Log.d(mTag, "onNewDhcpResults(" + Objects.toString(dhcpResults) + ")");
+        }
+        mCallback.onNewDhcpResults(dhcpResults);
+        dispatchCallback(delta, newLp);
+    }
+
+    private void handleIPv4Failure() {
+        // TODO: Investigate deleting this clearIPv4Address() call.
+        //
+        // DhcpClient will send us CMD_CLEAR_LINKADDRESS in all circumstances
+        // that could trigger a call to this function. If we missed handling
+        // that message in StartedState for some reason we would still clear
+        // any addresses upon entry to StoppedState.
+        mInterfaceCtrl.clearIPv4Address();
+        mDhcpResults = null;
+        if (DBG) { Log.d(mTag, "onNewDhcpResults(null)"); }
+        mCallback.onNewDhcpResults(null);
+
+        handleProvisioningFailure();
+    }
+
+    private void handleProvisioningFailure() {
+        final LinkProperties newLp = assembleLinkProperties();
+        ProvisioningChange delta = setLinkProperties(newLp);
+        // If we've gotten here and we're still not provisioned treat that as
+        // a total loss of provisioning.
+        //
+        // Either (a) static IP configuration failed or (b) DHCPv4 failed AND
+        // there was no usable IPv6 obtained before a non-zero provisioning
+        // timeout expired.
+        //
+        // Regardless: GAME OVER.
+        if (delta == ProvisioningChange.STILL_NOT_PROVISIONED) {
+            delta = ProvisioningChange.LOST_PROVISIONING;
+        }
+
+        dispatchCallback(delta, newLp);
+        if (delta == ProvisioningChange.LOST_PROVISIONING) {
+            transitionTo(mStoppingState);
+        }
+    }
+
+    private void doImmediateProvisioningFailure(int failureType) {
+        logError("onProvisioningFailure(): %s", failureType);
+        recordMetric(failureType);
+        mCallback.onProvisioningFailure(new LinkProperties(mLinkProperties));
+    }
+
+    private boolean startIPv4() {
+        // If we have a StaticIpConfiguration attempt to apply it and
+        // handle the result accordingly.
+        if (mConfiguration.mStaticIpConfig != null) {
+            if (mInterfaceCtrl.setIPv4Address(mConfiguration.mStaticIpConfig.ipAddress)) {
+                handleIPv4Success(new DhcpResults(mConfiguration.mStaticIpConfig));
+            } else {
+                return false;
+            }
+        } else {
+            // Start DHCPv4.
+            mDhcpClient = DhcpClient.makeDhcpClient(mContext, IpClient.this, mInterfaceName);
+            mDhcpClient.registerForPreDhcpNotification();
+            mDhcpClient.sendMessage(DhcpClient.CMD_START_DHCP);
+        }
+
+        return true;
+    }
+
+    private boolean startIPv6() {
+        return mInterfaceCtrl.setIPv6PrivacyExtensions(true) &&
+               mInterfaceCtrl.setIPv6AddrGenModeIfSupported(mConfiguration.mIPv6AddrGenMode) &&
+               mInterfaceCtrl.enableIPv6();
+    }
+
+    private boolean applyInitialConfig(InitialConfiguration config) {
+        // TODO: also support specifying a static IPv4 configuration in InitialConfiguration.
+        for (LinkAddress addr : findAll(config.ipAddresses, LinkAddress::isIPv6)) {
+            if (!mInterfaceCtrl.addAddress(addr)) return false;
+        }
+
+        return true;
+    }
+
+    private boolean startIpReachabilityMonitor() {
+        try {
+            mIpReachabilityMonitor = new IpReachabilityMonitor(
+                    mContext,
+                    mInterfaceName,
+                    mLog,
+                    new IpReachabilityMonitor.Callback() {
+                        @Override
+                        public void notifyLost(InetAddress ip, String logMsg) {
+                            mCallback.onReachabilityLost(logMsg);
+                        }
+                    },
+                    mMultinetworkPolicyTracker);
+        } catch (IllegalArgumentException iae) {
+            // Failed to start IpReachabilityMonitor. Log it and call
+            // onProvisioningFailure() immediately.
+            //
+            // See http://b/31038971.
+            logError("IpReachabilityMonitor failure: %s", iae);
+            mIpReachabilityMonitor = null;
+        }
+
+        return (mIpReachabilityMonitor != null);
+    }
+
+    private void stopAllIP() {
+        // We don't need to worry about routes, just addresses, because:
+        //     - disableIpv6() will clear autoconf IPv6 routes as well, and
+        //     - we don't get IPv4 routes from netlink
+        // so we neither react to nor need to wait for changes in either.
+
+        mInterfaceCtrl.disableIPv6();
+        mInterfaceCtrl.clearAllAddresses();
+    }
+
+    class StoppedState extends State {
+        @Override
+        public void enter() {
+            stopAllIP();
+
+            resetLinkProperties();
+            if (mStartTimeMillis > 0) {
+                recordMetric(IpManagerEvent.COMPLETE_LIFECYCLE);
+                mStartTimeMillis = 0;
+            }
+        }
+
+        @Override
+        public boolean processMessage(Message msg) {
+            switch (msg.what) {
+                case CMD_TERMINATE_AFTER_STOP:
+                    stopStateMachineUpdaters();
+                    quit();
+                    break;
+
+                case CMD_STOP:
+                    break;
+
+                case CMD_START:
+                    mConfiguration = (ProvisioningConfiguration) msg.obj;
+                    transitionTo(mStartedState);
+                    break;
+
+                case EVENT_NETLINK_LINKPROPERTIES_CHANGED:
+                    handleLinkPropertiesUpdate(NO_CALLBACKS);
+                    break;
+
+                case CMD_UPDATE_TCP_BUFFER_SIZES:
+                    mTcpBufferSizes = (String) msg.obj;
+                    handleLinkPropertiesUpdate(NO_CALLBACKS);
+                    break;
+
+                case CMD_UPDATE_HTTP_PROXY:
+                    mHttpProxy = (ProxyInfo) msg.obj;
+                    handleLinkPropertiesUpdate(NO_CALLBACKS);
+                    break;
+
+                case CMD_SET_MULTICAST_FILTER:
+                    mMulticastFiltering = (boolean) msg.obj;
+                    break;
+
+                case DhcpClient.CMD_ON_QUIT:
+                    // Everything is already stopped.
+                    logError("Unexpected CMD_ON_QUIT (already stopped).");
+                    break;
+
+                default:
+                    return NOT_HANDLED;
+            }
+
+            mMsgStateLogger.handled(this, getCurrentState());
+            return HANDLED;
+        }
+    }
+
+    class StoppingState extends State {
+        @Override
+        public void enter() {
+            if (mDhcpClient == null) {
+                // There's no DHCPv4 for which to wait; proceed to stopped.
+                transitionTo(mStoppedState);
+            }
+        }
+
+        @Override
+        public boolean processMessage(Message msg) {
+            switch (msg.what) {
+                case CMD_STOP:
+                    break;
+
+                case DhcpClient.CMD_CLEAR_LINKADDRESS:
+                    mInterfaceCtrl.clearIPv4Address();
+                    break;
+
+                case DhcpClient.CMD_ON_QUIT:
+                    mDhcpClient = null;
+                    transitionTo(mStoppedState);
+                    break;
+
+                default:
+                    deferMessage(msg);
+            }
+
+            mMsgStateLogger.handled(this, getCurrentState());
+            return HANDLED;
+        }
+    }
+
+    class StartedState extends State {
+        @Override
+        public void enter() {
+            mStartTimeMillis = SystemClock.elapsedRealtime();
+
+            if (mConfiguration.mProvisioningTimeoutMs > 0) {
+                final long alarmTime = SystemClock.elapsedRealtime() +
+                        mConfiguration.mProvisioningTimeoutMs;
+                mProvisioningTimeoutAlarm.schedule(alarmTime);
+            }
+
+            if (readyToProceed()) {
+                transitionTo(mRunningState);
+            } else {
+                // Clear all IPv4 and IPv6 before proceeding to RunningState.
+                // Clean up any leftover state from an abnormal exit from
+                // tethering or during an IpClient restart.
+                stopAllIP();
+            }
+        }
+
+        @Override
+        public void exit() {
+            mProvisioningTimeoutAlarm.cancel();
+        }
+
+        @Override
+        public boolean processMessage(Message msg) {
+            switch (msg.what) {
+                case CMD_STOP:
+                    transitionTo(mStoppingState);
+                    break;
+
+                case EVENT_NETLINK_LINKPROPERTIES_CHANGED:
+                    handleLinkPropertiesUpdate(NO_CALLBACKS);
+                    if (readyToProceed()) {
+                        transitionTo(mRunningState);
+                    }
+                    break;
+
+                case EVENT_PROVISIONING_TIMEOUT:
+                    handleProvisioningFailure();
+                    break;
+
+                default:
+                    // It's safe to process messages out of order because the
+                    // only message that can both
+                    //     a) be received at this time and
+                    //     b) affect provisioning state
+                    // is EVENT_NETLINK_LINKPROPERTIES_CHANGED (handled above).
+                    deferMessage(msg);
+            }
+
+            mMsgStateLogger.handled(this, getCurrentState());
+            return HANDLED;
+        }
+
+        boolean readyToProceed() {
+            return (!mLinkProperties.hasIPv4Address() &&
+                    !mLinkProperties.hasGlobalIPv6Address());
+        }
+    }
+
+    class RunningState extends State {
+        private ConnectivityPacketTracker mPacketTracker;
+        private boolean mDhcpActionInFlight;
+
+        @Override
+        public void enter() {
+            // Get the Configuration for ApfFilter from Context
+            final boolean filter802_3Frames =
+                    mContext.getResources().getBoolean(R.bool.config_apfDrop802_3Frames);
+
+            final int[] ethTypeBlackList = mContext.getResources().getIntArray(
+                    R.array.config_apfEthTypeBlackList);
+
+            mApfFilter = ApfFilter.maybeCreate(mConfiguration.mApfCapabilities, mNetworkInterface,
+                    mCallback, mMulticastFiltering, filter802_3Frames, ethTypeBlackList);
+            // TODO: investigate the effects of any multicast filtering racing/interfering with the
+            // rest of this IP configuration startup.
+            if (mApfFilter == null) {
+                mCallback.setFallbackMulticastFilter(mMulticastFiltering);
+            }
+
+            mPacketTracker = createPacketTracker();
+            if (mPacketTracker != null) mPacketTracker.start(mConfiguration.mDisplayName);
+
+            if (mConfiguration.mEnableIPv6 && !startIPv6()) {
+                doImmediateProvisioningFailure(IpManagerEvent.ERROR_STARTING_IPV6);
+                transitionTo(mStoppingState);
+                return;
+            }
+
+            if (mConfiguration.mEnableIPv4 && !startIPv4()) {
+                doImmediateProvisioningFailure(IpManagerEvent.ERROR_STARTING_IPV4);
+                transitionTo(mStoppingState);
+                return;
+            }
+
+            final InitialConfiguration config = mConfiguration.mInitialConfig;
+            if ((config != null) && !applyInitialConfig(config)) {
+                // TODO introduce a new IpManagerEvent constant to distinguish this error case.
+                doImmediateProvisioningFailure(IpManagerEvent.ERROR_INVALID_PROVISIONING);
+                transitionTo(mStoppingState);
+                return;
+            }
+
+            if (mConfiguration.mUsingIpReachabilityMonitor && !startIpReachabilityMonitor()) {
+                doImmediateProvisioningFailure(
+                        IpManagerEvent.ERROR_STARTING_IPREACHABILITYMONITOR);
+                transitionTo(mStoppingState);
+                return;
+            }
+        }
+
+        @Override
+        public void exit() {
+            stopDhcpAction();
+
+            if (mIpReachabilityMonitor != null) {
+                mIpReachabilityMonitor.stop();
+                mIpReachabilityMonitor = null;
+            }
+
+            if (mDhcpClient != null) {
+                mDhcpClient.sendMessage(DhcpClient.CMD_STOP_DHCP);
+                mDhcpClient.doQuit();
+            }
+
+            if (mPacketTracker != null) {
+                mPacketTracker.stop();
+                mPacketTracker = null;
+            }
+
+            if (mApfFilter != null) {
+                mApfFilter.shutdown();
+                mApfFilter = null;
+            }
+
+            resetLinkProperties();
+        }
+
+        private ConnectivityPacketTracker createPacketTracker() {
+            try {
+                return new ConnectivityPacketTracker(
+                        getHandler(), mNetworkInterface, mConnectivityPacketLog);
+            } catch (IllegalArgumentException e) {
+                return null;
+            }
+        }
+
+        private void ensureDhcpAction() {
+            if (!mDhcpActionInFlight) {
+                mCallback.onPreDhcpAction();
+                mDhcpActionInFlight = true;
+                final long alarmTime = SystemClock.elapsedRealtime() +
+                        mConfiguration.mRequestedPreDhcpActionMs;
+                mDhcpActionTimeoutAlarm.schedule(alarmTime);
+            }
+        }
+
+        private void stopDhcpAction() {
+            mDhcpActionTimeoutAlarm.cancel();
+            if (mDhcpActionInFlight) {
+                mCallback.onPostDhcpAction();
+                mDhcpActionInFlight = false;
+            }
+        }
+
+        @Override
+        public boolean processMessage(Message msg) {
+            switch (msg.what) {
+                case CMD_STOP:
+                    transitionTo(mStoppingState);
+                    break;
+
+                case CMD_START:
+                    logError("ALERT: START received in StartedState. Please fix caller.");
+                    break;
+
+                case CMD_CONFIRM:
+                    // TODO: Possibly introduce a second type of confirmation
+                    // that both probes (a) on-link neighbors and (b) does
+                    // a DHCPv4 RENEW.  We used to do this on Wi-Fi framework
+                    // roams.
+                    if (mIpReachabilityMonitor != null) {
+                        mIpReachabilityMonitor.probeAll();
+                    }
+                    break;
+
+                case EVENT_PRE_DHCP_ACTION_COMPLETE:
+                    // It's possible to reach here if, for example, someone
+                    // calls completedPreDhcpAction() after provisioning with
+                    // a static IP configuration.
+                    if (mDhcpClient != null) {
+                        mDhcpClient.sendMessage(DhcpClient.CMD_PRE_DHCP_ACTION_COMPLETE);
+                    }
+                    break;
+
+                case EVENT_NETLINK_LINKPROPERTIES_CHANGED:
+                    if (!handleLinkPropertiesUpdate(SEND_CALLBACKS)) {
+                        transitionTo(mStoppingState);
+                    }
+                    break;
+
+                case CMD_UPDATE_TCP_BUFFER_SIZES:
+                    mTcpBufferSizes = (String) msg.obj;
+                    // This cannot possibly change provisioning state.
+                    handleLinkPropertiesUpdate(SEND_CALLBACKS);
+                    break;
+
+                case CMD_UPDATE_HTTP_PROXY:
+                    mHttpProxy = (ProxyInfo) msg.obj;
+                    // This cannot possibly change provisioning state.
+                    handleLinkPropertiesUpdate(SEND_CALLBACKS);
+                    break;
+
+                case CMD_SET_MULTICAST_FILTER: {
+                    mMulticastFiltering = (boolean) msg.obj;
+                    if (mApfFilter != null) {
+                        mApfFilter.setMulticastFilter(mMulticastFiltering);
+                    } else {
+                        mCallback.setFallbackMulticastFilter(mMulticastFiltering);
+                    }
+                    break;
+                }
+
+                case EVENT_DHCPACTION_TIMEOUT:
+                    stopDhcpAction();
+                    break;
+
+                case DhcpClient.CMD_PRE_DHCP_ACTION:
+                    if (mConfiguration.mRequestedPreDhcpActionMs > 0) {
+                        ensureDhcpAction();
+                    } else {
+                        sendMessage(EVENT_PRE_DHCP_ACTION_COMPLETE);
+                    }
+                    break;
+
+                case DhcpClient.CMD_CLEAR_LINKADDRESS:
+                    mInterfaceCtrl.clearIPv4Address();
+                    break;
+
+                case DhcpClient.CMD_CONFIGURE_LINKADDRESS: {
+                    final LinkAddress ipAddress = (LinkAddress) msg.obj;
+                    if (mInterfaceCtrl.setIPv4Address(ipAddress)) {
+                        mDhcpClient.sendMessage(DhcpClient.EVENT_LINKADDRESS_CONFIGURED);
+                    } else {
+                        logError("Failed to set IPv4 address.");
+                        dispatchCallback(ProvisioningChange.LOST_PROVISIONING,
+                                new LinkProperties(mLinkProperties));
+                        transitionTo(mStoppingState);
+                    }
+                    break;
+                }
+
+                // This message is only received when:
+                //
+                //     a) initial address acquisition succeeds,
+                //     b) renew succeeds or is NAK'd,
+                //     c) rebind succeeds or is NAK'd, or
+                //     c) the lease expires,
+                //
+                // but never when initial address acquisition fails. The latter
+                // condition is now governed by the provisioning timeout.
+                case DhcpClient.CMD_POST_DHCP_ACTION:
+                    stopDhcpAction();
+
+                    switch (msg.arg1) {
+                        case DhcpClient.DHCP_SUCCESS:
+                            handleIPv4Success((DhcpResults) msg.obj);
+                            break;
+                        case DhcpClient.DHCP_FAILURE:
+                            handleIPv4Failure();
+                            break;
+                        default:
+                            logError("Unknown CMD_POST_DHCP_ACTION status: %s", msg.arg1);
+                    }
+                    break;
+
+                case DhcpClient.CMD_ON_QUIT:
+                    // DHCPv4 quit early for some reason.
+                    logError("Unexpected CMD_ON_QUIT.");
+                    mDhcpClient = null;
+                    break;
+
+                default:
+                    return NOT_HANDLED;
+            }
+
+            mMsgStateLogger.handled(this, getCurrentState());
+            return HANDLED;
+        }
+    }
+
+    private static class MessageHandlingLogger {
+        public String processedInState;
+        public String receivedInState;
+
+        public void reset() {
+            processedInState = null;
+            receivedInState = null;
+        }
+
+        public void handled(State processedIn, IState receivedIn) {
+            processedInState = processedIn.getClass().getSimpleName();
+            receivedInState = receivedIn.getName();
+        }
+
+        public String toString() {
+            return String.format("rcvd_in=%s, proc_in=%s",
+                                 receivedInState, processedInState);
+        }
+    }
+
+    // TODO: extract out into CollectionUtils.
+    static <T> boolean any(Iterable<T> coll, Predicate<T> fn) {
+        for (T t : coll) {
+            if (fn.test(t)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    static <T> boolean all(Iterable<T> coll, Predicate<T> fn) {
+        return !any(coll, not(fn));
+    }
+
+    static <T> Predicate<T> not(Predicate<T> fn) {
+        return (t) -> !fn.test(t);
+    }
+
+    static <T> String join(String delimiter, Collection<T> coll) {
+        return coll.stream().map(Object::toString).collect(Collectors.joining(delimiter));
+    }
+
+    static <T> T find(Iterable<T> coll, Predicate<T> fn) {
+        for (T t: coll) {
+            if (fn.test(t)) {
+              return t;
+            }
+        }
+        return null;
+    }
+
+    static <T> List<T> findAll(Collection<T> coll, Predicate<T> fn) {
+        return coll.stream().filter(fn).collect(Collectors.toList());
+    }
+}
diff --git a/android/net/ip/IpManager.java b/android/net/ip/IpManager.java
index bc07b81..b12cb32 100644
--- a/android/net/ip/IpManager.java
+++ b/android/net/ip/IpManager.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016 The Android Open Source Project
+ * Copyright (C) 2017 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.
@@ -16,131 +16,112 @@
 
 package android.net.ip;
 
-import com.android.internal.util.MessageUtils;
-import com.android.internal.util.WakeupMessage;
-
 import android.content.Context;
-import android.net.DhcpResults;
 import android.net.INetd;
-import android.net.IpPrefix;
-import android.net.LinkAddress;
-import android.net.LinkProperties.ProvisioningChange;
 import android.net.LinkProperties;
-import android.net.ProxyInfo;
-import android.net.RouteInfo;
+import android.net.Network;
 import android.net.StaticIpConfiguration;
 import android.net.apf.ApfCapabilities;
-import android.net.apf.ApfFilter;
-import android.net.dhcp.DhcpClient;
-import android.net.metrics.IpConnectivityLog;
-import android.net.metrics.IpManagerEvent;
-import android.net.util.MultinetworkPolicyTracker;
 import android.net.util.NetdService;
-import android.net.util.NetworkConstants;
-import android.net.util.SharedLog;
 import android.os.INetworkManagementService;
-import android.os.Message;
-import android.os.RemoteException;
 import android.os.ServiceManager;
-import android.os.SystemClock;
-import android.text.TextUtils;
-import android.util.LocalLog;
-import android.util.Log;
-import android.util.SparseArray;
+import android.net.apf.ApfCapabilities;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.R;
-import com.android.internal.util.IndentingPrintWriter;
-import com.android.internal.util.IState;
-import com.android.internal.util.Preconditions;
-import com.android.internal.util.State;
-import com.android.internal.util.StateMachine;
-import com.android.server.net.NetlinkTracker;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.net.Inet4Address;
-import java.net.Inet6Address;
-import java.net.InetAddress;
-import java.net.NetworkInterface;
-import java.net.SocketException;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.Objects;
-import java.util.List;
-import java.util.Set;
-import java.util.StringJoiner;
-import java.util.function.Predicate;
-import java.util.stream.Collectors;
 
 
-/**
- * IpManager
- *
- * This class provides the interface to IP-layer provisioning and maintenance
- * functionality that can be used by transport layers like Wi-Fi, Ethernet,
- * et cetera.
- *
- * [ Lifetime ]
- * IpManager is designed to be instantiated as soon as the interface name is
- * known and can be as long-lived as the class containing it (i.e. declaring
- * it "private final" is okay).
+/*
+ * TODO: Delete this altogether in favor of its renamed successor: IpClient.
  *
  * @hide
  */
-public class IpManager extends StateMachine {
-    private static final boolean DBG = false;
+public class IpManager extends IpClient {
+    public static class ProvisioningConfiguration extends IpClient.ProvisioningConfiguration {
+        public ProvisioningConfiguration(IpClient.ProvisioningConfiguration ipcConfig) {
+            super(ipcConfig);
+        }
 
-    // For message logging.
-    private static final Class[] sMessageClasses = { IpManager.class, DhcpClient.class };
-    private static final SparseArray<String> sWhatToString =
-            MessageUtils.findMessageNames(sMessageClasses);
+        public static class Builder extends IpClient.ProvisioningConfiguration.Builder {
+            @Override
+            public Builder withoutIPv4() {
+                super.withoutIPv4();
+                return this;
+            }
+            @Override
+            public Builder withoutIPv6() {
+                super.withoutIPv6();
+                return this;
+            }
+            @Override
+            public Builder withoutIpReachabilityMonitor() {
+                super.withoutIpReachabilityMonitor();
+                return this;
+            }
+            @Override
+            public Builder withPreDhcpAction() {
+                super.withPreDhcpAction();
+                return this;
+            }
+            @Override
+            public Builder withPreDhcpAction(int dhcpActionTimeoutMs) {
+                super.withPreDhcpAction(dhcpActionTimeoutMs);
+                return this;
+            }
+            // No Override; locally defined type.
+            public Builder withInitialConfiguration(InitialConfiguration initialConfig) {
+                super.withInitialConfiguration((IpClient.InitialConfiguration) initialConfig);
+                return this;
+            }
+            @Override
+            public Builder withStaticConfiguration(StaticIpConfiguration staticConfig) {
+                super.withStaticConfiguration(staticConfig);
+                return this;
+            }
+            @Override
+            public Builder withApfCapabilities(ApfCapabilities apfCapabilities) {
+                super.withApfCapabilities(apfCapabilities);
+                return this;
+            }
+            @Override
+            public Builder withProvisioningTimeoutMs(int timeoutMs) {
+                super.withProvisioningTimeoutMs(timeoutMs);
+                return this;
+            }
+            @Override
+            public Builder withIPv6AddrGenModeEUI64() {
+                super.withIPv6AddrGenModeEUI64();
+                return this;
+            }
+            @Override
+            public Builder withIPv6AddrGenModeStablePrivacy() {
+                super.withIPv6AddrGenModeStablePrivacy();
+                return this;
+            }
+            @Override
+            public Builder withNetwork(Network network) {
+                super.withNetwork(network);
+                return this;
+            }
+            @Override
+            public Builder withDisplayName(String displayName) {
+                super.withDisplayName(displayName);
+                return this;
+            }
+            @Override
+            public ProvisioningConfiguration build() {
+                return new ProvisioningConfiguration(super.build());
+            }
+        }
+    }
 
-    /**
-     * Callbacks for handling IpManager events.
-     */
-    public static class Callback {
-        // In order to receive onPreDhcpAction(), call #withPreDhcpAction()
-        // when constructing a ProvisioningConfiguration.
-        //
-        // Implementations of onPreDhcpAction() must call
-        // IpManager#completedPreDhcpAction() to indicate that DHCP is clear
-        // to proceed.
-        public void onPreDhcpAction() {}
-        public void onPostDhcpAction() {}
+    public static ProvisioningConfiguration.Builder buildProvisioningConfiguration() {
+        return new ProvisioningConfiguration.Builder();
+    }
 
-        // This is purely advisory and not an indication of provisioning
-        // success or failure.  This is only here for callers that want to
-        // expose DHCPv4 results to other APIs (e.g., WifiInfo#setInetAddress).
-        // DHCPv4 or static IPv4 configuration failure or success can be
-        // determined by whether or not the passed-in DhcpResults object is
-        // null or not.
-        public void onNewDhcpResults(DhcpResults dhcpResults) {}
+    public static class InitialConfiguration extends IpClient.InitialConfiguration {
+    }
 
-        public void onProvisioningSuccess(LinkProperties newLp) {}
-        public void onProvisioningFailure(LinkProperties newLp) {}
-
-        // Invoked on LinkProperties changes.
-        public void onLinkPropertiesChange(LinkProperties newLp) {}
-
-        // Called when the internal IpReachabilityMonitor (if enabled) has
-        // detected the loss of a critical number of required neighbors.
-        public void onReachabilityLost(String logMsg) {}
-
-        // Called when the IpManager state machine terminates.
-        public void onQuit() {}
-
-        // Install an APF program to filter incoming packets.
-        public void installPacketFilter(byte[] filter) {}
-
-        // If multicast filtering cannot be accomplished with APF, this function will be called to
-        // actuate multicast filtering using another means.
-        public void setFallbackMulticastFilter(boolean enabled) {}
-
-        // Enabled/disable Neighbor Discover offload functionality. This is
-        // called, for example, whenever 464xlat is being started or stopped.
-        public void setNeighborDiscoveryOffload(boolean enable) {}
+    public static class Callback extends IpClient.Callback {
     }
 
     public static class WaitForProvisioningCallback extends Callback {
@@ -172,1552 +153,24 @@
         }
     }
 
-    // Use a wrapper class to log in order to ensure complete and detailed
-    // logging. This method is lighter weight than annotations/reflection
-    // and has the following benefits:
-    //
-    //     - No invoked method can be forgotten.
-    //       Any new method added to IpManager.Callback must be overridden
-    //       here or it will never be called.
-    //
-    //     - No invoking call site can be forgotten.
-    //       Centralized logging in this way means call sites don't need to
-    //       remember to log, and therefore no call site can be forgotten.
-    //
-    //     - No variation in log format among call sites.
-    //       Encourages logging of any available arguments, and all call sites
-    //       are necessarily logged identically.
-    //
-    // TODO: Find an lighter weight approach.
-    private class LoggingCallbackWrapper extends Callback {
-        private static final String PREFIX = "INVOKE ";
-        private Callback mCallback;
-
-        public LoggingCallbackWrapper(Callback callback) {
-            mCallback = callback;
-        }
-
-        private void log(String msg) {
-            mLog.log(PREFIX + msg);
-        }
-
-        @Override
-        public void onPreDhcpAction() {
-            mCallback.onPreDhcpAction();
-            log("onPreDhcpAction()");
-        }
-        @Override
-        public void onPostDhcpAction() {
-            mCallback.onPostDhcpAction();
-            log("onPostDhcpAction()");
-        }
-        @Override
-        public void onNewDhcpResults(DhcpResults dhcpResults) {
-            mCallback.onNewDhcpResults(dhcpResults);
-            log("onNewDhcpResults({" + dhcpResults + "})");
-        }
-        @Override
-        public void onProvisioningSuccess(LinkProperties newLp) {
-            mCallback.onProvisioningSuccess(newLp);
-            log("onProvisioningSuccess({" + newLp + "})");
-        }
-        @Override
-        public void onProvisioningFailure(LinkProperties newLp) {
-            mCallback.onProvisioningFailure(newLp);
-            log("onProvisioningFailure({" + newLp + "})");
-        }
-        @Override
-        public void onLinkPropertiesChange(LinkProperties newLp) {
-            mCallback.onLinkPropertiesChange(newLp);
-            log("onLinkPropertiesChange({" + newLp + "})");
-        }
-        @Override
-        public void onReachabilityLost(String logMsg) {
-            mCallback.onReachabilityLost(logMsg);
-            log("onReachabilityLost(" + logMsg + ")");
-        }
-        @Override
-        public void onQuit() {
-            mCallback.onQuit();
-            log("onQuit()");
-        }
-        @Override
-        public void installPacketFilter(byte[] filter) {
-            mCallback.installPacketFilter(filter);
-            log("installPacketFilter(byte[" + filter.length + "])");
-        }
-        @Override
-        public void setFallbackMulticastFilter(boolean enabled) {
-            mCallback.setFallbackMulticastFilter(enabled);
-            log("setFallbackMulticastFilter(" + enabled + ")");
-        }
-        @Override
-        public void setNeighborDiscoveryOffload(boolean enable) {
-            mCallback.setNeighborDiscoveryOffload(enable);
-            log("setNeighborDiscoveryOffload(" + enable + ")");
-        }
-    }
-
-    /**
-     * This class encapsulates parameters to be passed to
-     * IpManager#startProvisioning(). A defensive copy is made by IpManager
-     * and the values specified herein are in force until IpManager#stop()
-     * is called.
-     *
-     * Example use:
-     *
-     *     final ProvisioningConfiguration config =
-     *             mIpManager.buildProvisioningConfiguration()
-     *                     .withPreDhcpAction()
-     *                     .withProvisioningTimeoutMs(36 * 1000)
-     *                     .build();
-     *     mIpManager.startProvisioning(config);
-     *     ...
-     *     mIpManager.stop();
-     *
-     * The specified provisioning configuration will only be active until
-     * IpManager#stop() is called. Future calls to IpManager#startProvisioning()
-     * must specify the configuration again.
-     */
-    public static class ProvisioningConfiguration {
-        // TODO: Delete this default timeout once those callers that care are
-        // fixed to pass in their preferred timeout.
-        //
-        // We pick 36 seconds so we can send DHCP requests at
-        //
-        //     t=0, t=2, t=6, t=14, t=30
-        //
-        // allowing for 10% jitter.
-        private static final int DEFAULT_TIMEOUT_MS = 36 * 1000;
-
-        public static class Builder {
-            private ProvisioningConfiguration mConfig = new ProvisioningConfiguration();
-
-            public Builder withoutIPv4() {
-                mConfig.mEnableIPv4 = false;
-                return this;
-            }
-
-            public Builder withoutIPv6() {
-                mConfig.mEnableIPv6 = false;
-                return this;
-            }
-
-            public Builder withoutIpReachabilityMonitor() {
-                mConfig.mUsingIpReachabilityMonitor = false;
-                return this;
-            }
-
-            public Builder withPreDhcpAction() {
-                mConfig.mRequestedPreDhcpActionMs = DEFAULT_TIMEOUT_MS;
-                return this;
-            }
-
-            public Builder withPreDhcpAction(int dhcpActionTimeoutMs) {
-                mConfig.mRequestedPreDhcpActionMs = dhcpActionTimeoutMs;
-                return this;
-            }
-
-            public Builder withInitialConfiguration(InitialConfiguration initialConfig) {
-                mConfig.mInitialConfig = initialConfig;
-                return this;
-            }
-
-            public Builder withStaticConfiguration(StaticIpConfiguration staticConfig) {
-                mConfig.mStaticIpConfig = staticConfig;
-                return this;
-            }
-
-            public Builder withApfCapabilities(ApfCapabilities apfCapabilities) {
-                mConfig.mApfCapabilities = apfCapabilities;
-                return this;
-            }
-
-            public Builder withProvisioningTimeoutMs(int timeoutMs) {
-                mConfig.mProvisioningTimeoutMs = timeoutMs;
-                return this;
-            }
-
-            public Builder withIPv6AddrGenModeEUI64() {
-                mConfig.mIPv6AddrGenMode = INetd.IPV6_ADDR_GEN_MODE_EUI64;
-                return this;
-            }
-
-            public Builder withIPv6AddrGenModeStablePrivacy() {
-                mConfig.mIPv6AddrGenMode = INetd.IPV6_ADDR_GEN_MODE_STABLE_PRIVACY;
-                return this;
-            }
-
-            public ProvisioningConfiguration build() {
-                return new ProvisioningConfiguration(mConfig);
-            }
-        }
-
-        /* package */ boolean mEnableIPv4 = true;
-        /* package */ boolean mEnableIPv6 = true;
-        /* package */ boolean mUsingIpReachabilityMonitor = true;
-        /* package */ int mRequestedPreDhcpActionMs;
-        /* package */ InitialConfiguration mInitialConfig;
-        /* package */ StaticIpConfiguration mStaticIpConfig;
-        /* package */ ApfCapabilities mApfCapabilities;
-        /* package */ int mProvisioningTimeoutMs = DEFAULT_TIMEOUT_MS;
-        /* package */ int mIPv6AddrGenMode = INetd.IPV6_ADDR_GEN_MODE_STABLE_PRIVACY;
-
-        public ProvisioningConfiguration() {} // used by Builder
-
-        public ProvisioningConfiguration(ProvisioningConfiguration other) {
-            mEnableIPv4 = other.mEnableIPv4;
-            mEnableIPv6 = other.mEnableIPv6;
-            mUsingIpReachabilityMonitor = other.mUsingIpReachabilityMonitor;
-            mRequestedPreDhcpActionMs = other.mRequestedPreDhcpActionMs;
-            mInitialConfig = InitialConfiguration.copy(other.mInitialConfig);
-            mStaticIpConfig = other.mStaticIpConfig;
-            mApfCapabilities = other.mApfCapabilities;
-            mProvisioningTimeoutMs = other.mProvisioningTimeoutMs;
-        }
-
-        @Override
-        public String toString() {
-            return new StringJoiner(", ", getClass().getSimpleName() + "{", "}")
-                    .add("mEnableIPv4: " + mEnableIPv4)
-                    .add("mEnableIPv6: " + mEnableIPv6)
-                    .add("mUsingIpReachabilityMonitor: " + mUsingIpReachabilityMonitor)
-                    .add("mRequestedPreDhcpActionMs: " + mRequestedPreDhcpActionMs)
-                    .add("mInitialConfig: " + mInitialConfig)
-                    .add("mStaticIpConfig: " + mStaticIpConfig)
-                    .add("mApfCapabilities: " + mApfCapabilities)
-                    .add("mProvisioningTimeoutMs: " + mProvisioningTimeoutMs)
-                    .add("mIPv6AddrGenMode: " + mIPv6AddrGenMode)
-                    .toString();
-        }
-
-        public boolean isValid() {
-            return (mInitialConfig == null) || mInitialConfig.isValid();
-        }
-    }
-
-    public static class InitialConfiguration {
-        public final Set<LinkAddress> ipAddresses = new HashSet<>();
-        public final Set<IpPrefix> directlyConnectedRoutes = new HashSet<>();
-        public final Set<InetAddress> dnsServers = new HashSet<>();
-        public Inet4Address gateway; // WiFi legacy behavior with static ipv4 config
-
-        public static InitialConfiguration copy(InitialConfiguration config) {
-            if (config == null) {
-                return null;
-            }
-            InitialConfiguration configCopy = new InitialConfiguration();
-            configCopy.ipAddresses.addAll(config.ipAddresses);
-            configCopy.directlyConnectedRoutes.addAll(config.directlyConnectedRoutes);
-            configCopy.dnsServers.addAll(config.dnsServers);
-            return configCopy;
-        }
-
-        @Override
-        public String toString() {
-            return String.format(
-                    "InitialConfiguration(IPs: {%s}, prefixes: {%s}, DNS: {%s}, v4 gateway: %s)",
-                    join(", ", ipAddresses), join(", ", directlyConnectedRoutes),
-                    join(", ", dnsServers), gateway);
-        }
-
-        public boolean isValid() {
-            if (ipAddresses.isEmpty()) {
-                return false;
-            }
-
-            // For every IP address, there must be at least one prefix containing that address.
-            for (LinkAddress addr : ipAddresses) {
-                if (!any(directlyConnectedRoutes, (p) -> p.contains(addr.getAddress()))) {
-                    return false;
-                }
-            }
-            // For every dns server, there must be at least one prefix containing that address.
-            for (InetAddress addr : dnsServers) {
-                if (!any(directlyConnectedRoutes, (p) -> p.contains(addr))) {
-                    return false;
-                }
-            }
-            // All IPv6 LinkAddresses have an RFC7421-suitable prefix length
-            // (read: compliant with RFC4291#section2.5.4).
-            if (any(ipAddresses, not(InitialConfiguration::isPrefixLengthCompliant))) {
-                return false;
-            }
-            // If directlyConnectedRoutes contains an IPv6 default route
-            // then ipAddresses MUST contain at least one non-ULA GUA.
-            if (any(directlyConnectedRoutes, InitialConfiguration::isIPv6DefaultRoute)
-                    && all(ipAddresses, not(InitialConfiguration::isIPv6GUA))) {
-                return false;
-            }
-            // The prefix length of routes in directlyConnectedRoutes be within reasonable
-            // bounds for IPv6: /48-/64 just as we’d accept in RIOs.
-            if (any(directlyConnectedRoutes, not(InitialConfiguration::isPrefixLengthCompliant))) {
-                return false;
-            }
-            // There no more than one IPv4 address
-            if (ipAddresses.stream().filter(Inet4Address.class::isInstance).count() > 1) {
-                return false;
-            }
-
-            return true;
-        }
-
-        /**
-         * @return true if the given list of addressess and routes satisfies provisioning for this
-         * InitialConfiguration. LinkAddresses and RouteInfo objects are not compared with equality
-         * because addresses and routes seen by Netlink will contain additional fields like flags,
-         * interfaces, and so on. If this InitialConfiguration has no IP address specified, the
-         * provisioning check always fails.
-         *
-         * If the given list of routes is null, only addresses are taken into considerations.
-         */
-        public boolean isProvisionedBy(List<LinkAddress> addresses, List<RouteInfo> routes) {
-            if (ipAddresses.isEmpty()) {
-                return false;
-            }
-
-            for (LinkAddress addr : ipAddresses) {
-                if (!any(addresses, (addrSeen) -> addr.isSameAddressAs(addrSeen))) {
-                    return false;
-                }
-            }
-
-            if (routes != null) {
-                for (IpPrefix prefix : directlyConnectedRoutes) {
-                    if (!any(routes, (routeSeen) -> isDirectlyConnectedRoute(routeSeen, prefix))) {
-                        return false;
-                    }
-                }
-            }
-
-            return true;
-        }
-
-        private static boolean isDirectlyConnectedRoute(RouteInfo route, IpPrefix prefix) {
-            return !route.hasGateway() && prefix.equals(route.getDestination());
-        }
-
-        private static boolean isPrefixLengthCompliant(LinkAddress addr) {
-            return addr.isIPv4() || isCompliantIPv6PrefixLength(addr.getPrefixLength());
-        }
-
-        private static boolean isPrefixLengthCompliant(IpPrefix prefix) {
-            return prefix.isIPv4() || isCompliantIPv6PrefixLength(prefix.getPrefixLength());
-        }
-
-        private static boolean isCompliantIPv6PrefixLength(int prefixLength) {
-            return (NetworkConstants.RFC6177_MIN_PREFIX_LENGTH <= prefixLength)
-                    && (prefixLength <= NetworkConstants.RFC7421_PREFIX_LENGTH);
-        }
-
-        private static boolean isIPv6DefaultRoute(IpPrefix prefix) {
-            return prefix.getAddress().equals(Inet6Address.ANY);
-        }
-
-        private static boolean isIPv6GUA(LinkAddress addr) {
-            return addr.isIPv6() && addr.isGlobalPreferred();
-        }
-    }
-
-    public static final String DUMP_ARG = "ipmanager";
-    public static final String DUMP_ARG_CONFIRM = "confirm";
-
-    private static final int CMD_TERMINATE_AFTER_STOP             = 1;
-    private static final int CMD_STOP                             = 2;
-    private static final int CMD_START                            = 3;
-    private static final int CMD_CONFIRM                          = 4;
-    private static final int EVENT_PRE_DHCP_ACTION_COMPLETE       = 5;
-    // Sent by NetlinkTracker to communicate netlink events.
-    private static final int EVENT_NETLINK_LINKPROPERTIES_CHANGED = 6;
-    private static final int CMD_UPDATE_TCP_BUFFER_SIZES          = 7;
-    private static final int CMD_UPDATE_HTTP_PROXY                = 8;
-    private static final int CMD_SET_MULTICAST_FILTER             = 9;
-    private static final int EVENT_PROVISIONING_TIMEOUT           = 10;
-    private static final int EVENT_DHCPACTION_TIMEOUT             = 11;
-
-    private static final int MAX_LOG_RECORDS = 500;
-    private static final int MAX_PACKET_RECORDS = 100;
-
-    private static final boolean NO_CALLBACKS = false;
-    private static final boolean SEND_CALLBACKS = true;
-
-    // This must match the interface prefix in clatd.c.
-    // TODO: Revert this hack once IpManager and Nat464Xlat work in concert.
-    private static final String CLAT_PREFIX = "v4-";
-
-    private final State mStoppedState = new StoppedState();
-    private final State mStoppingState = new StoppingState();
-    private final State mStartedState = new StartedState();
-    private final State mRunningState = new RunningState();
-
-    private final String mTag;
-    private final Context mContext;
-    private final String mInterfaceName;
-    private final String mClatInterfaceName;
-    @VisibleForTesting
-    protected final Callback mCallback;
-    private final INetworkManagementService mNwService;
-    private final NetlinkTracker mNetlinkTracker;
-    private final WakeupMessage mProvisioningTimeoutAlarm;
-    private final WakeupMessage mDhcpActionTimeoutAlarm;
-    private final MultinetworkPolicyTracker mMultinetworkPolicyTracker;
-    private final SharedLog mLog;
-    private final LocalLog mConnectivityPacketLog;
-    private final MessageHandlingLogger mMsgStateLogger;
-    private final IpConnectivityLog mMetricsLog = new IpConnectivityLog();
-    private final InterfaceController mInterfaceCtrl;
-
-    private NetworkInterface mNetworkInterface;
-
-    /**
-     * Non-final member variables accessed only from within our StateMachine.
-     */
-    private LinkProperties mLinkProperties;
-    private ProvisioningConfiguration mConfiguration;
-    private IpReachabilityMonitor mIpReachabilityMonitor;
-    private DhcpClient mDhcpClient;
-    private DhcpResults mDhcpResults;
-    private String mTcpBufferSizes;
-    private ProxyInfo mHttpProxy;
-    private ApfFilter mApfFilter;
-    private boolean mMulticastFiltering;
-    private long mStartTimeMillis;
-
     public IpManager(Context context, String ifName, Callback callback) {
         this(context, ifName, callback, INetworkManagementService.Stub.asInterface(
                 ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE)),
                 NetdService.getInstance());
     }
 
-    /**
-     * An expanded constructor, useful for dependency injection.
-     * TODO: migrate all test users to mock IpManager directly and remove this ctor.
-     */
     public IpManager(Context context, String ifName, Callback callback,
             INetworkManagementService nwService) {
         this(context, ifName, callback, nwService, NetdService.getInstance());
     }
 
     @VisibleForTesting
-    IpManager(Context context, String ifName, Callback callback,
+    public IpManager(Context context, String ifName, Callback callback,
             INetworkManagementService nwService, INetd netd) {
-        super(IpManager.class.getSimpleName() + "." + ifName);
-        mTag = getName();
-
-        mContext = context;
-        mInterfaceName = ifName;
-        mClatInterfaceName = CLAT_PREFIX + ifName;
-        mCallback = new LoggingCallbackWrapper(callback);
-        mNwService = nwService;
-
-        mLog = new SharedLog(MAX_LOG_RECORDS, mTag);
-        mConnectivityPacketLog = new LocalLog(MAX_PACKET_RECORDS);
-        mMsgStateLogger = new MessageHandlingLogger();
-
-        mInterfaceCtrl = new InterfaceController(mInterfaceName, mNwService, netd, mLog);
-
-        mNetlinkTracker = new NetlinkTracker(
-                mInterfaceName,
-                new NetlinkTracker.Callback() {
-                    @Override
-                    public void update() {
-                        sendMessage(EVENT_NETLINK_LINKPROPERTIES_CHANGED);
-                    }
-                }) {
-            @Override
-            public void interfaceAdded(String iface) {
-                super.interfaceAdded(iface);
-                if (mClatInterfaceName.equals(iface)) {
-                    mCallback.setNeighborDiscoveryOffload(false);
-                } else if (!mInterfaceName.equals(iface)) {
-                    return;
-                }
-
-                final String msg = "interfaceAdded(" + iface +")";
-                logMsg(msg);
-            }
-
-            @Override
-            public void interfaceRemoved(String iface) {
-                super.interfaceRemoved(iface);
-                // TODO: Also observe mInterfaceName going down and take some
-                // kind of appropriate action.
-                if (mClatInterfaceName.equals(iface)) {
-                    // TODO: consider sending a message to the IpManager main
-                    // StateMachine thread, in case "NDO enabled" state becomes
-                    // tied to more things that 464xlat operation.
-                    mCallback.setNeighborDiscoveryOffload(true);
-                } else if (!mInterfaceName.equals(iface)) {
-                    return;
-                }
-
-                final String msg = "interfaceRemoved(" + iface +")";
-                logMsg(msg);
-            }
-
-            private void logMsg(String msg) {
-                Log.d(mTag, msg);
-                getHandler().post(() -> { mLog.log("OBSERVED " + msg); });
-            }
-        };
-
-        mLinkProperties = new LinkProperties();
-        mLinkProperties.setInterfaceName(mInterfaceName);
-
-        mMultinetworkPolicyTracker = new MultinetworkPolicyTracker(mContext, getHandler(),
-                () -> { mLog.log("OBSERVED AvoidBadWifi changed"); });
-
-        mProvisioningTimeoutAlarm = new WakeupMessage(mContext, getHandler(),
-                mTag + ".EVENT_PROVISIONING_TIMEOUT", EVENT_PROVISIONING_TIMEOUT);
-        mDhcpActionTimeoutAlarm = new WakeupMessage(mContext, getHandler(),
-                mTag + ".EVENT_DHCPACTION_TIMEOUT", EVENT_DHCPACTION_TIMEOUT);
-
-        // Anything the StateMachine may access must have been instantiated
-        // before this point.
-        configureAndStartStateMachine();
-
-        // Anything that may send messages to the StateMachine must only be
-        // configured to do so after the StateMachine has started (above).
-        startStateMachineUpdaters();
-    }
-
-    private void configureAndStartStateMachine() {
-        addState(mStoppedState);
-        addState(mStartedState);
-            addState(mRunningState, mStartedState);
-        addState(mStoppingState);
-
-        setInitialState(mStoppedState);
-
-        super.start();
-    }
-
-    private void startStateMachineUpdaters() {
-        try {
-            mNwService.registerObserver(mNetlinkTracker);
-        } catch (RemoteException e) {
-            logError("Couldn't register NetlinkTracker: %s", e);
-        }
-
-        mMultinetworkPolicyTracker.start();
-    }
-
-    private void stopStateMachineUpdaters() {
-        try {
-            mNwService.unregisterObserver(mNetlinkTracker);
-        } catch (RemoteException e) {
-            logError("Couldn't unregister NetlinkTracker: %s", e);
-        }
-
-        mMultinetworkPolicyTracker.shutdown();
-    }
-
-    @Override
-    protected void onQuitting() {
-        mCallback.onQuit();
-    }
-
-    // Shut down this IpManager instance altogether.
-    public void shutdown() {
-        stop();
-        sendMessage(CMD_TERMINATE_AFTER_STOP);
-    }
-
-    public static ProvisioningConfiguration.Builder buildProvisioningConfiguration() {
-        return new ProvisioningConfiguration.Builder();
+        super(context, ifName, callback, nwService, netd);
     }
 
     public void startProvisioning(ProvisioningConfiguration req) {
-        if (!req.isValid()) {
-            doImmediateProvisioningFailure(IpManagerEvent.ERROR_INVALID_PROVISIONING);
-            return;
-        }
-
-        getNetworkInterface();
-
-        mCallback.setNeighborDiscoveryOffload(true);
-        sendMessage(CMD_START, new ProvisioningConfiguration(req));
-    }
-
-    // TODO: Delete this.
-    public void startProvisioning(StaticIpConfiguration staticIpConfig) {
-        startProvisioning(buildProvisioningConfiguration()
-                .withStaticConfiguration(staticIpConfig)
-                .build());
-    }
-
-    public void startProvisioning() {
-        startProvisioning(new ProvisioningConfiguration());
-    }
-
-    public void stop() {
-        sendMessage(CMD_STOP);
-    }
-
-    public void confirmConfiguration() {
-        sendMessage(CMD_CONFIRM);
-    }
-
-    public void completedPreDhcpAction() {
-        sendMessage(EVENT_PRE_DHCP_ACTION_COMPLETE);
-    }
-
-    /**
-     * Set the TCP buffer sizes to use.
-     *
-     * This may be called, repeatedly, at any time before or after a call to
-     * #startProvisioning(). The setting is cleared upon calling #stop().
-     */
-    public void setTcpBufferSizes(String tcpBufferSizes) {
-        sendMessage(CMD_UPDATE_TCP_BUFFER_SIZES, tcpBufferSizes);
-    }
-
-    /**
-     * Set the HTTP Proxy configuration to use.
-     *
-     * This may be called, repeatedly, at any time before or after a call to
-     * #startProvisioning(). The setting is cleared upon calling #stop().
-     */
-    public void setHttpProxy(ProxyInfo proxyInfo) {
-        sendMessage(CMD_UPDATE_HTTP_PROXY, proxyInfo);
-    }
-
-    /**
-     * Enable or disable the multicast filter.  Attempts to use APF to accomplish the filtering,
-     * if not, Callback.setFallbackMulticastFilter() is called.
-     */
-    public void setMulticastFilter(boolean enabled) {
-        sendMessage(CMD_SET_MULTICAST_FILTER, enabled);
-    }
-
-    public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
-        if (args != null && args.length > 0 && DUMP_ARG_CONFIRM.equals(args[0])) {
-            // Execute confirmConfiguration() and take no further action.
-            confirmConfiguration();
-            return;
-        }
-
-        // Thread-unsafe access to mApfFilter but just used for debugging.
-        final ApfFilter apfFilter = mApfFilter;
-        final ProvisioningConfiguration provisioningConfig = mConfiguration;
-        final ApfCapabilities apfCapabilities = (provisioningConfig != null)
-                ? provisioningConfig.mApfCapabilities : null;
-
-        IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
-        pw.println(mTag + " APF dump:");
-        pw.increaseIndent();
-        if (apfFilter != null) {
-            apfFilter.dump(pw);
-        } else {
-            pw.print("No active ApfFilter; ");
-            if (provisioningConfig == null) {
-                pw.println("IpManager not yet started.");
-            } else if (apfCapabilities == null || apfCapabilities.apfVersionSupported == 0) {
-                pw.println("Hardware does not support APF.");
-            } else {
-                pw.println("ApfFilter not yet started, APF capabilities: " + apfCapabilities);
-            }
-        }
-        pw.decreaseIndent();
-
-        pw.println();
-        pw.println(mTag + " current ProvisioningConfiguration:");
-        pw.increaseIndent();
-        pw.println(Objects.toString(provisioningConfig, "N/A"));
-        pw.decreaseIndent();
-
-        pw.println();
-        pw.println(mTag + " StateMachine dump:");
-        pw.increaseIndent();
-        mLog.dump(fd, pw, args);
-        pw.decreaseIndent();
-
-        pw.println();
-        pw.println(mTag + " connectivity packet log:");
-        pw.println();
-        pw.println("Debug with python and scapy via:");
-        pw.println("shell$ python");
-        pw.println(">>> from scapy import all as scapy");
-        pw.println(">>> scapy.Ether(\"<paste_hex_string>\".decode(\"hex\")).show2()");
-        pw.println();
-
-        pw.increaseIndent();
-        mConnectivityPacketLog.readOnlyLocalLog().dump(fd, pw, args);
-        pw.decreaseIndent();
-    }
-
-
-    /**
-     * Internals.
-     */
-
-    @Override
-    protected String getWhatToString(int what) {
-        return sWhatToString.get(what, "UNKNOWN: " + Integer.toString(what));
-    }
-
-    @Override
-    protected String getLogRecString(Message msg) {
-        final String logLine = String.format(
-                "%s/%d %d %d %s [%s]",
-                mInterfaceName, mNetworkInterface == null ? -1 : mNetworkInterface.getIndex(),
-                msg.arg1, msg.arg2, Objects.toString(msg.obj), mMsgStateLogger);
-
-        final String richerLogLine = getWhatToString(msg.what) + " " + logLine;
-        mLog.log(richerLogLine);
-        if (DBG) {
-            Log.d(mTag, richerLogLine);
-        }
-
-        mMsgStateLogger.reset();
-        return logLine;
-    }
-
-    @Override
-    protected boolean recordLogRec(Message msg) {
-        // Don't log EVENT_NETLINK_LINKPROPERTIES_CHANGED. They can be noisy,
-        // and we already log any LinkProperties change that results in an
-        // invocation of IpManager.Callback#onLinkPropertiesChange().
-        final boolean shouldLog = (msg.what != EVENT_NETLINK_LINKPROPERTIES_CHANGED);
-        if (!shouldLog) {
-            mMsgStateLogger.reset();
-        }
-        return shouldLog;
-    }
-
-    private void logError(String fmt, Object... args) {
-        final String msg = "ERROR " + String.format(fmt, args);
-        Log.e(mTag, msg);
-        mLog.log(msg);
-    }
-
-    private void getNetworkInterface() {
-        try {
-            mNetworkInterface = NetworkInterface.getByName(mInterfaceName);
-        } catch (SocketException | NullPointerException e) {
-            // TODO: throw new IllegalStateException.
-            logError("Failed to get interface object: %s", e);
-        }
-    }
-
-    // This needs to be called with care to ensure that our LinkProperties
-    // are in sync with the actual LinkProperties of the interface. For example,
-    // we should only call this if we know for sure that there are no IP addresses
-    // assigned to the interface, etc.
-    private void resetLinkProperties() {
-        mNetlinkTracker.clearLinkProperties();
-        mConfiguration = null;
-        mDhcpResults = null;
-        mTcpBufferSizes = "";
-        mHttpProxy = null;
-
-        mLinkProperties = new LinkProperties();
-        mLinkProperties.setInterfaceName(mInterfaceName);
-    }
-
-    private void recordMetric(final int type) {
-        if (mStartTimeMillis <= 0) { Log.wtf(mTag, "Start time undefined!"); }
-        final long duration = SystemClock.elapsedRealtime() - mStartTimeMillis;
-        mMetricsLog.log(mInterfaceName, new IpManagerEvent(type, duration));
-    }
-
-    // For now: use WifiStateMachine's historical notion of provisioned.
-    @VisibleForTesting
-    static boolean isProvisioned(LinkProperties lp, InitialConfiguration config) {
-        // For historical reasons, we should connect even if all we have is
-        // an IPv4 address and nothing else.
-        if (lp.hasIPv4Address() || lp.isProvisioned()) {
-            return true;
-        }
-        if (config == null) {
-            return false;
-        }
-
-        // When an InitialConfiguration is specified, ignore any difference with previous
-        // properties and instead check if properties observed match the desired properties.
-        return config.isProvisionedBy(lp.getLinkAddresses(), lp.getRoutes());
-    }
-
-    // TODO: Investigate folding all this into the existing static function
-    // LinkProperties.compareProvisioning() or some other single function that
-    // takes two LinkProperties objects and returns a ProvisioningChange
-    // object that is a correct and complete assessment of what changed, taking
-    // account of the asymmetries described in the comments in this function.
-    // Then switch to using it everywhere (IpReachabilityMonitor, etc.).
-    private ProvisioningChange compareProvisioning(LinkProperties oldLp, LinkProperties newLp) {
-        ProvisioningChange delta;
-        InitialConfiguration config = mConfiguration != null ? mConfiguration.mInitialConfig : null;
-        final boolean wasProvisioned = isProvisioned(oldLp, config);
-        final boolean isProvisioned = isProvisioned(newLp, config);
-
-        if (!wasProvisioned && isProvisioned) {
-            delta = ProvisioningChange.GAINED_PROVISIONING;
-        } else if (wasProvisioned && isProvisioned) {
-            delta = ProvisioningChange.STILL_PROVISIONED;
-        } else if (!wasProvisioned && !isProvisioned) {
-            delta = ProvisioningChange.STILL_NOT_PROVISIONED;
-        } else {
-            // (wasProvisioned && !isProvisioned)
-            //
-            // Note that this is true even if we lose a configuration element
-            // (e.g., a default gateway) that would not be required to advance
-            // into provisioned state. This is intended: if we have a default
-            // router and we lose it, that's a sure sign of a problem, but if
-            // we connect to a network with no IPv4 DNS servers, we consider
-            // that to be a network without DNS servers and connect anyway.
-            //
-            // See the comment below.
-            delta = ProvisioningChange.LOST_PROVISIONING;
-        }
-
-        final boolean lostIPv6 = oldLp.isIPv6Provisioned() && !newLp.isIPv6Provisioned();
-        final boolean lostIPv4Address = oldLp.hasIPv4Address() && !newLp.hasIPv4Address();
-        final boolean lostIPv6Router = oldLp.hasIPv6DefaultRoute() && !newLp.hasIPv6DefaultRoute();
-
-        // If bad wifi avoidance is disabled, then ignore IPv6 loss of
-        // provisioning. Otherwise, when a hotspot that loses Internet
-        // access sends out a 0-lifetime RA to its clients, the clients
-        // will disconnect and then reconnect, avoiding the bad hotspot,
-        // instead of getting stuck on the bad hotspot. http://b/31827713 .
-        //
-        // This is incorrect because if the hotspot then regains Internet
-        // access with a different prefix, TCP connections on the
-        // deprecated addresses will remain stuck.
-        //
-        // Note that we can still be disconnected by IpReachabilityMonitor
-        // if the IPv6 default gateway (but not the IPv6 DNS servers; see
-        // accompanying code in IpReachabilityMonitor) is unreachable.
-        final boolean ignoreIPv6ProvisioningLoss = !mMultinetworkPolicyTracker.getAvoidBadWifi();
-
-        // Additionally:
-        //
-        // Partial configurations (e.g., only an IPv4 address with no DNS
-        // servers and no default route) are accepted as long as DHCPv4
-        // succeeds. On such a network, isProvisioned() will always return
-        // false, because the configuration is not complete, but we want to
-        // connect anyway. It might be a disconnected network such as a
-        // Chromecast or a wireless printer, for example.
-        //
-        // Because on such a network isProvisioned() will always return false,
-        // delta will never be LOST_PROVISIONING. So check for loss of
-        // provisioning here too.
-        if (lostIPv4Address || (lostIPv6 && !ignoreIPv6ProvisioningLoss)) {
-            delta = ProvisioningChange.LOST_PROVISIONING;
-        }
-
-        // Additionally:
-        //
-        // If the previous link properties had a global IPv6 address and an
-        // IPv6 default route then also consider the loss of that default route
-        // to be a loss of provisioning. See b/27962810.
-        if (oldLp.hasGlobalIPv6Address() && (lostIPv6Router && !ignoreIPv6ProvisioningLoss)) {
-            delta = ProvisioningChange.LOST_PROVISIONING;
-        }
-
-        return delta;
-    }
-
-    private void dispatchCallback(ProvisioningChange delta, LinkProperties newLp) {
-        switch (delta) {
-            case GAINED_PROVISIONING:
-                if (DBG) { Log.d(mTag, "onProvisioningSuccess()"); }
-                recordMetric(IpManagerEvent.PROVISIONING_OK);
-                mCallback.onProvisioningSuccess(newLp);
-                break;
-
-            case LOST_PROVISIONING:
-                if (DBG) { Log.d(mTag, "onProvisioningFailure()"); }
-                recordMetric(IpManagerEvent.PROVISIONING_FAIL);
-                mCallback.onProvisioningFailure(newLp);
-                break;
-
-            default:
-                if (DBG) { Log.d(mTag, "onLinkPropertiesChange()"); }
-                mCallback.onLinkPropertiesChange(newLp);
-                break;
-        }
-    }
-
-    // Updates all IpManager-related state concerned with LinkProperties.
-    // Returns a ProvisioningChange for possibly notifying other interested
-    // parties that are not fronted by IpManager.
-    private ProvisioningChange setLinkProperties(LinkProperties newLp) {
-        if (mApfFilter != null) {
-            mApfFilter.setLinkProperties(newLp);
-        }
-        if (mIpReachabilityMonitor != null) {
-            mIpReachabilityMonitor.updateLinkProperties(newLp);
-        }
-
-        ProvisioningChange delta = compareProvisioning(mLinkProperties, newLp);
-        mLinkProperties = new LinkProperties(newLp);
-
-        if (delta == ProvisioningChange.GAINED_PROVISIONING) {
-            // TODO: Add a proper ProvisionedState and cancel the alarm in
-            // its enter() method.
-            mProvisioningTimeoutAlarm.cancel();
-        }
-
-        return delta;
-    }
-
-    private LinkProperties assembleLinkProperties() {
-        // [1] Create a new LinkProperties object to populate.
-        LinkProperties newLp = new LinkProperties();
-        newLp.setInterfaceName(mInterfaceName);
-
-        // [2] Pull in data from netlink:
-        //         - IPv4 addresses
-        //         - IPv6 addresses
-        //         - IPv6 routes
-        //         - IPv6 DNS servers
-        //
-        // N.B.: this is fundamentally race-prone and should be fixed by
-        // changing NetlinkTracker from a hybrid edge/level model to an
-        // edge-only model, or by giving IpManager its own netlink socket(s)
-        // so as to track all required information directly.
-        LinkProperties netlinkLinkProperties = mNetlinkTracker.getLinkProperties();
-        newLp.setLinkAddresses(netlinkLinkProperties.getLinkAddresses());
-        for (RouteInfo route : netlinkLinkProperties.getRoutes()) {
-            newLp.addRoute(route);
-        }
-        addAllReachableDnsServers(newLp, netlinkLinkProperties.getDnsServers());
-
-        // [3] Add in data from DHCPv4, if available.
-        //
-        // mDhcpResults is never shared with any other owner so we don't have
-        // to worry about concurrent modification.
-        if (mDhcpResults != null) {
-            for (RouteInfo route : mDhcpResults.getRoutes(mInterfaceName)) {
-                newLp.addRoute(route);
-            }
-            addAllReachableDnsServers(newLp, mDhcpResults.dnsServers);
-            newLp.setDomains(mDhcpResults.domains);
-
-            if (mDhcpResults.mtu != 0) {
-                newLp.setMtu(mDhcpResults.mtu);
-            }
-        }
-
-        // [4] Add in TCP buffer sizes and HTTP Proxy config, if available.
-        if (!TextUtils.isEmpty(mTcpBufferSizes)) {
-            newLp.setTcpBufferSizes(mTcpBufferSizes);
-        }
-        if (mHttpProxy != null) {
-            newLp.setHttpProxy(mHttpProxy);
-        }
-
-        // [5] Add data from InitialConfiguration
-        if (mConfiguration != null && mConfiguration.mInitialConfig != null) {
-            InitialConfiguration config = mConfiguration.mInitialConfig;
-            // Add InitialConfiguration routes and dns server addresses once all addresses
-            // specified in the InitialConfiguration have been observed with Netlink.
-            if (config.isProvisionedBy(newLp.getLinkAddresses(), null)) {
-                for (IpPrefix prefix : config.directlyConnectedRoutes) {
-                    newLp.addRoute(new RouteInfo(prefix, null, mInterfaceName));
-                }
-            }
-            addAllReachableDnsServers(newLp, config.dnsServers);
-        }
-        final LinkProperties oldLp = mLinkProperties;
-        if (DBG) {
-            Log.d(mTag, String.format("Netlink-seen LPs: %s, new LPs: %s; old LPs: %s",
-                    netlinkLinkProperties, newLp, oldLp));
-        }
-
-        // TODO: also learn via netlink routes specified by an InitialConfiguration and specified
-        // from a static IP v4 config instead of manually patching them in in steps [3] and [5].
-        return newLp;
-    }
-
-    private static void addAllReachableDnsServers(
-            LinkProperties lp, Iterable<InetAddress> dnses) {
-        // TODO: Investigate deleting this reachability check.  We should be
-        // able to pass everything down to netd and let netd do evaluation
-        // and RFC6724-style sorting.
-        for (InetAddress dns : dnses) {
-            if (!dns.isAnyLocalAddress() && lp.isReachable(dns)) {
-                lp.addDnsServer(dns);
-            }
-        }
-    }
-
-    // Returns false if we have lost provisioning, true otherwise.
-    private boolean handleLinkPropertiesUpdate(boolean sendCallbacks) {
-        final LinkProperties newLp = assembleLinkProperties();
-        if (Objects.equals(newLp, mLinkProperties)) {
-            return true;
-        }
-        final ProvisioningChange delta = setLinkProperties(newLp);
-        if (sendCallbacks) {
-            dispatchCallback(delta, newLp);
-        }
-        return (delta != ProvisioningChange.LOST_PROVISIONING);
-    }
-
-    private void handleIPv4Success(DhcpResults dhcpResults) {
-        mDhcpResults = new DhcpResults(dhcpResults);
-        final LinkProperties newLp = assembleLinkProperties();
-        final ProvisioningChange delta = setLinkProperties(newLp);
-
-        if (DBG) {
-            Log.d(mTag, "onNewDhcpResults(" + Objects.toString(dhcpResults) + ")");
-        }
-        mCallback.onNewDhcpResults(dhcpResults);
-        dispatchCallback(delta, newLp);
-    }
-
-    private void handleIPv4Failure() {
-        // TODO: Investigate deleting this clearIPv4Address() call.
-        //
-        // DhcpClient will send us CMD_CLEAR_LINKADDRESS in all circumstances
-        // that could trigger a call to this function. If we missed handling
-        // that message in StartedState for some reason we would still clear
-        // any addresses upon entry to StoppedState.
-        mInterfaceCtrl.clearIPv4Address();
-        mDhcpResults = null;
-        if (DBG) { Log.d(mTag, "onNewDhcpResults(null)"); }
-        mCallback.onNewDhcpResults(null);
-
-        handleProvisioningFailure();
-    }
-
-    private void handleProvisioningFailure() {
-        final LinkProperties newLp = assembleLinkProperties();
-        ProvisioningChange delta = setLinkProperties(newLp);
-        // If we've gotten here and we're still not provisioned treat that as
-        // a total loss of provisioning.
-        //
-        // Either (a) static IP configuration failed or (b) DHCPv4 failed AND
-        // there was no usable IPv6 obtained before a non-zero provisioning
-        // timeout expired.
-        //
-        // Regardless: GAME OVER.
-        if (delta == ProvisioningChange.STILL_NOT_PROVISIONED) {
-            delta = ProvisioningChange.LOST_PROVISIONING;
-        }
-
-        dispatchCallback(delta, newLp);
-        if (delta == ProvisioningChange.LOST_PROVISIONING) {
-            transitionTo(mStoppingState);
-        }
-    }
-
-    private void doImmediateProvisioningFailure(int failureType) {
-        logError("onProvisioningFailure(): %s", failureType);
-        recordMetric(failureType);
-        mCallback.onProvisioningFailure(new LinkProperties(mLinkProperties));
-    }
-
-    private boolean startIPv4() {
-        // If we have a StaticIpConfiguration attempt to apply it and
-        // handle the result accordingly.
-        if (mConfiguration.mStaticIpConfig != null) {
-            if (mInterfaceCtrl.setIPv4Address(mConfiguration.mStaticIpConfig.ipAddress)) {
-                handleIPv4Success(new DhcpResults(mConfiguration.mStaticIpConfig));
-            } else {
-                return false;
-            }
-        } else {
-            // Start DHCPv4.
-            mDhcpClient = DhcpClient.makeDhcpClient(mContext, IpManager.this, mInterfaceName);
-            mDhcpClient.registerForPreDhcpNotification();
-            mDhcpClient.sendMessage(DhcpClient.CMD_START_DHCP);
-        }
-
-        return true;
-    }
-
-    private boolean startIPv6() {
-        return mInterfaceCtrl.setIPv6PrivacyExtensions(true) &&
-               mInterfaceCtrl.setIPv6AddrGenModeIfSupported(mConfiguration.mIPv6AddrGenMode) &&
-               mInterfaceCtrl.enableIPv6();
-    }
-
-    private boolean applyInitialConfig(InitialConfiguration config) {
-        // TODO: also support specifying a static IPv4 configuration in InitialConfiguration.
-        for (LinkAddress addr : findAll(config.ipAddresses, LinkAddress::isIPv6)) {
-            if (!mInterfaceCtrl.addAddress(addr)) return false;
-        }
-
-        return true;
-    }
-
-    private boolean startIpReachabilityMonitor() {
-        try {
-            mIpReachabilityMonitor = new IpReachabilityMonitor(
-                    mContext,
-                    mInterfaceName,
-                    mLog,
-                    new IpReachabilityMonitor.Callback() {
-                        @Override
-                        public void notifyLost(InetAddress ip, String logMsg) {
-                            mCallback.onReachabilityLost(logMsg);
-                        }
-                    },
-                    mMultinetworkPolicyTracker);
-        } catch (IllegalArgumentException iae) {
-            // Failed to start IpReachabilityMonitor. Log it and call
-            // onProvisioningFailure() immediately.
-            //
-            // See http://b/31038971.
-            logError("IpReachabilityMonitor failure: %s", iae);
-            mIpReachabilityMonitor = null;
-        }
-
-        return (mIpReachabilityMonitor != null);
-    }
-
-    private void stopAllIP() {
-        // We don't need to worry about routes, just addresses, because:
-        //     - disableIpv6() will clear autoconf IPv6 routes as well, and
-        //     - we don't get IPv4 routes from netlink
-        // so we neither react to nor need to wait for changes in either.
-
-        mInterfaceCtrl.disableIPv6();
-        mInterfaceCtrl.clearAllAddresses();
-    }
-
-    class StoppedState extends State {
-        @Override
-        public void enter() {
-            stopAllIP();
-
-            resetLinkProperties();
-            if (mStartTimeMillis > 0) {
-                recordMetric(IpManagerEvent.COMPLETE_LIFECYCLE);
-                mStartTimeMillis = 0;
-            }
-        }
-
-        @Override
-        public boolean processMessage(Message msg) {
-            switch (msg.what) {
-                case CMD_TERMINATE_AFTER_STOP:
-                    stopStateMachineUpdaters();
-                    quit();
-                    break;
-
-                case CMD_STOP:
-                    break;
-
-                case CMD_START:
-                    mConfiguration = (ProvisioningConfiguration) msg.obj;
-                    transitionTo(mStartedState);
-                    break;
-
-                case EVENT_NETLINK_LINKPROPERTIES_CHANGED:
-                    handleLinkPropertiesUpdate(NO_CALLBACKS);
-                    break;
-
-                case CMD_UPDATE_TCP_BUFFER_SIZES:
-                    mTcpBufferSizes = (String) msg.obj;
-                    handleLinkPropertiesUpdate(NO_CALLBACKS);
-                    break;
-
-                case CMD_UPDATE_HTTP_PROXY:
-                    mHttpProxy = (ProxyInfo) msg.obj;
-                    handleLinkPropertiesUpdate(NO_CALLBACKS);
-                    break;
-
-                case CMD_SET_MULTICAST_FILTER:
-                    mMulticastFiltering = (boolean) msg.obj;
-                    break;
-
-                case DhcpClient.CMD_ON_QUIT:
-                    // Everything is already stopped.
-                    logError("Unexpected CMD_ON_QUIT (already stopped).");
-                    break;
-
-                default:
-                    return NOT_HANDLED;
-            }
-
-            mMsgStateLogger.handled(this, getCurrentState());
-            return HANDLED;
-        }
-    }
-
-    class StoppingState extends State {
-        @Override
-        public void enter() {
-            if (mDhcpClient == null) {
-                // There's no DHCPv4 for which to wait; proceed to stopped.
-                transitionTo(mStoppedState);
-            }
-        }
-
-        @Override
-        public boolean processMessage(Message msg) {
-            switch (msg.what) {
-                case CMD_STOP:
-                    break;
-
-                case DhcpClient.CMD_CLEAR_LINKADDRESS:
-                    mInterfaceCtrl.clearIPv4Address();
-                    break;
-
-                case DhcpClient.CMD_ON_QUIT:
-                    mDhcpClient = null;
-                    transitionTo(mStoppedState);
-                    break;
-
-                default:
-                    deferMessage(msg);
-            }
-
-            mMsgStateLogger.handled(this, getCurrentState());
-            return HANDLED;
-        }
-    }
-
-    class StartedState extends State {
-        @Override
-        public void enter() {
-            mStartTimeMillis = SystemClock.elapsedRealtime();
-
-            if (mConfiguration.mProvisioningTimeoutMs > 0) {
-                final long alarmTime = SystemClock.elapsedRealtime() +
-                        mConfiguration.mProvisioningTimeoutMs;
-                mProvisioningTimeoutAlarm.schedule(alarmTime);
-            }
-
-            if (readyToProceed()) {
-                transitionTo(mRunningState);
-            } else {
-                // Clear all IPv4 and IPv6 before proceeding to RunningState.
-                // Clean up any leftover state from an abnormal exit from
-                // tethering or during an IpManager restart.
-                stopAllIP();
-            }
-        }
-
-        @Override
-        public void exit() {
-            mProvisioningTimeoutAlarm.cancel();
-        }
-
-        @Override
-        public boolean processMessage(Message msg) {
-            switch (msg.what) {
-                case CMD_STOP:
-                    transitionTo(mStoppingState);
-                    break;
-
-                case EVENT_NETLINK_LINKPROPERTIES_CHANGED:
-                    handleLinkPropertiesUpdate(NO_CALLBACKS);
-                    if (readyToProceed()) {
-                        transitionTo(mRunningState);
-                    }
-                    break;
-
-                case EVENT_PROVISIONING_TIMEOUT:
-                    handleProvisioningFailure();
-                    break;
-
-                default:
-                    // It's safe to process messages out of order because the
-                    // only message that can both
-                    //     a) be received at this time and
-                    //     b) affect provisioning state
-                    // is EVENT_NETLINK_LINKPROPERTIES_CHANGED (handled above).
-                    deferMessage(msg);
-            }
-
-            mMsgStateLogger.handled(this, getCurrentState());
-            return HANDLED;
-        }
-
-        boolean readyToProceed() {
-            return (!mLinkProperties.hasIPv4Address() &&
-                    !mLinkProperties.hasGlobalIPv6Address());
-        }
-    }
-
-    class RunningState extends State {
-        private ConnectivityPacketTracker mPacketTracker;
-        private boolean mDhcpActionInFlight;
-
-        @Override
-        public void enter() {
-            // Get the Configuration for ApfFilter from Context
-            boolean filter802_3Frames =
-                    mContext.getResources().getBoolean(R.bool.config_apfDrop802_3Frames);
-
-            int[] ethTypeBlackList = mContext.getResources().getIntArray(
-                    R.array.config_apfEthTypeBlackList);
-
-            mApfFilter = ApfFilter.maybeCreate(mConfiguration.mApfCapabilities, mNetworkInterface,
-                    mCallback, mMulticastFiltering, filter802_3Frames, ethTypeBlackList);
-            // TODO: investigate the effects of any multicast filtering racing/interfering with the
-            // rest of this IP configuration startup.
-            if (mApfFilter == null) {
-                mCallback.setFallbackMulticastFilter(mMulticastFiltering);
-            }
-
-            mPacketTracker = createPacketTracker();
-            if (mPacketTracker != null) mPacketTracker.start();
-
-            if (mConfiguration.mEnableIPv6 && !startIPv6()) {
-                doImmediateProvisioningFailure(IpManagerEvent.ERROR_STARTING_IPV6);
-                transitionTo(mStoppingState);
-                return;
-            }
-
-            if (mConfiguration.mEnableIPv4 && !startIPv4()) {
-                doImmediateProvisioningFailure(IpManagerEvent.ERROR_STARTING_IPV4);
-                transitionTo(mStoppingState);
-                return;
-            }
-
-            InitialConfiguration config = mConfiguration.mInitialConfig;
-            if ((config != null) && !applyInitialConfig(config)) {
-                // TODO introduce a new IpManagerEvent constant to distinguish this error case.
-                doImmediateProvisioningFailure(IpManagerEvent.ERROR_INVALID_PROVISIONING);
-                transitionTo(mStoppingState);
-                return;
-            }
-
-            if (mConfiguration.mUsingIpReachabilityMonitor && !startIpReachabilityMonitor()) {
-                doImmediateProvisioningFailure(
-                        IpManagerEvent.ERROR_STARTING_IPREACHABILITYMONITOR);
-                transitionTo(mStoppingState);
-                return;
-            }
-        }
-
-        @Override
-        public void exit() {
-            stopDhcpAction();
-
-            if (mIpReachabilityMonitor != null) {
-                mIpReachabilityMonitor.stop();
-                mIpReachabilityMonitor = null;
-            }
-
-            if (mDhcpClient != null) {
-                mDhcpClient.sendMessage(DhcpClient.CMD_STOP_DHCP);
-                mDhcpClient.doQuit();
-            }
-
-            if (mPacketTracker != null) {
-                mPacketTracker.stop();
-                mPacketTracker = null;
-            }
-
-            if (mApfFilter != null) {
-                mApfFilter.shutdown();
-                mApfFilter = null;
-            }
-
-            resetLinkProperties();
-        }
-
-        private ConnectivityPacketTracker createPacketTracker() {
-            try {
-                return new ConnectivityPacketTracker(
-                        getHandler(), mNetworkInterface, mConnectivityPacketLog);
-            } catch (IllegalArgumentException e) {
-                return null;
-            }
-        }
-
-        private void ensureDhcpAction() {
-            if (!mDhcpActionInFlight) {
-                mCallback.onPreDhcpAction();
-                mDhcpActionInFlight = true;
-                final long alarmTime = SystemClock.elapsedRealtime() +
-                        mConfiguration.mRequestedPreDhcpActionMs;
-                mDhcpActionTimeoutAlarm.schedule(alarmTime);
-            }
-        }
-
-        private void stopDhcpAction() {
-            mDhcpActionTimeoutAlarm.cancel();
-            if (mDhcpActionInFlight) {
-                mCallback.onPostDhcpAction();
-                mDhcpActionInFlight = false;
-            }
-        }
-
-        @Override
-        public boolean processMessage(Message msg) {
-            switch (msg.what) {
-                case CMD_STOP:
-                    transitionTo(mStoppingState);
-                    break;
-
-                case CMD_START:
-                    logError("ALERT: START received in StartedState. Please fix caller.");
-                    break;
-
-                case CMD_CONFIRM:
-                    // TODO: Possibly introduce a second type of confirmation
-                    // that both probes (a) on-link neighbors and (b) does
-                    // a DHCPv4 RENEW.  We used to do this on Wi-Fi framework
-                    // roams.
-                    if (mIpReachabilityMonitor != null) {
-                        mIpReachabilityMonitor.probeAll();
-                    }
-                    break;
-
-                case EVENT_PRE_DHCP_ACTION_COMPLETE:
-                    // It's possible to reach here if, for example, someone
-                    // calls completedPreDhcpAction() after provisioning with
-                    // a static IP configuration.
-                    if (mDhcpClient != null) {
-                        mDhcpClient.sendMessage(DhcpClient.CMD_PRE_DHCP_ACTION_COMPLETE);
-                    }
-                    break;
-
-                case EVENT_NETLINK_LINKPROPERTIES_CHANGED:
-                    if (!handleLinkPropertiesUpdate(SEND_CALLBACKS)) {
-                        transitionTo(mStoppingState);
-                    }
-                    break;
-
-                case CMD_UPDATE_TCP_BUFFER_SIZES:
-                    mTcpBufferSizes = (String) msg.obj;
-                    // This cannot possibly change provisioning state.
-                    handleLinkPropertiesUpdate(SEND_CALLBACKS);
-                    break;
-
-                case CMD_UPDATE_HTTP_PROXY:
-                    mHttpProxy = (ProxyInfo) msg.obj;
-                    // This cannot possibly change provisioning state.
-                    handleLinkPropertiesUpdate(SEND_CALLBACKS);
-                    break;
-
-                case CMD_SET_MULTICAST_FILTER: {
-                    mMulticastFiltering = (boolean) msg.obj;
-                    if (mApfFilter != null) {
-                        mApfFilter.setMulticastFilter(mMulticastFiltering);
-                    } else {
-                        mCallback.setFallbackMulticastFilter(mMulticastFiltering);
-                    }
-                    break;
-                }
-
-                case EVENT_DHCPACTION_TIMEOUT:
-                    stopDhcpAction();
-                    break;
-
-                case DhcpClient.CMD_PRE_DHCP_ACTION:
-                    if (mConfiguration.mRequestedPreDhcpActionMs > 0) {
-                        ensureDhcpAction();
-                    } else {
-                        sendMessage(EVENT_PRE_DHCP_ACTION_COMPLETE);
-                    }
-                    break;
-
-                case DhcpClient.CMD_CLEAR_LINKADDRESS:
-                    mInterfaceCtrl.clearIPv4Address();
-                    break;
-
-                case DhcpClient.CMD_CONFIGURE_LINKADDRESS: {
-                    final LinkAddress ipAddress = (LinkAddress) msg.obj;
-                    if (mInterfaceCtrl.setIPv4Address(ipAddress)) {
-                        mDhcpClient.sendMessage(DhcpClient.EVENT_LINKADDRESS_CONFIGURED);
-                    } else {
-                        logError("Failed to set IPv4 address.");
-                        dispatchCallback(ProvisioningChange.LOST_PROVISIONING,
-                                new LinkProperties(mLinkProperties));
-                        transitionTo(mStoppingState);
-                    }
-                    break;
-                }
-
-                // This message is only received when:
-                //
-                //     a) initial address acquisition succeeds,
-                //     b) renew succeeds or is NAK'd,
-                //     c) rebind succeeds or is NAK'd, or
-                //     c) the lease expires,
-                //
-                // but never when initial address acquisition fails. The latter
-                // condition is now governed by the provisioning timeout.
-                case DhcpClient.CMD_POST_DHCP_ACTION:
-                    stopDhcpAction();
-
-                    switch (msg.arg1) {
-                        case DhcpClient.DHCP_SUCCESS:
-                            handleIPv4Success((DhcpResults) msg.obj);
-                            break;
-                        case DhcpClient.DHCP_FAILURE:
-                            handleIPv4Failure();
-                            break;
-                        default:
-                            logError("Unknown CMD_POST_DHCP_ACTION status: %s", msg.arg1);
-                    }
-                    break;
-
-                case DhcpClient.CMD_ON_QUIT:
-                    // DHCPv4 quit early for some reason.
-                    logError("Unexpected CMD_ON_QUIT.");
-                    mDhcpClient = null;
-                    break;
-
-                default:
-                    return NOT_HANDLED;
-            }
-
-            mMsgStateLogger.handled(this, getCurrentState());
-            return HANDLED;
-        }
-    }
-
-    private static class MessageHandlingLogger {
-        public String processedInState;
-        public String receivedInState;
-
-        public void reset() {
-            processedInState = null;
-            receivedInState = null;
-        }
-
-        public void handled(State processedIn, IState receivedIn) {
-            processedInState = processedIn.getClass().getSimpleName();
-            receivedInState = receivedIn.getName();
-        }
-
-        public String toString() {
-            return String.format("rcvd_in=%s, proc_in=%s",
-                                 receivedInState, processedInState);
-        }
-    }
-
-    // TODO: extract out into CollectionUtils.
-    static <T> boolean any(Iterable<T> coll, Predicate<T> fn) {
-        for (T t : coll) {
-            if (fn.test(t)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    static <T> boolean all(Iterable<T> coll, Predicate<T> fn) {
-        return !any(coll, not(fn));
-    }
-
-    static <T> Predicate<T> not(Predicate<T> fn) {
-        return (t) -> !fn.test(t);
-    }
-
-    static <T> String join(String delimiter, Collection<T> coll) {
-        return coll.stream().map(Object::toString).collect(Collectors.joining(delimiter));
-    }
-
-    static <T> T find(Iterable<T> coll, Predicate<T> fn) {
-        for (T t: coll) {
-            if (fn.test(t)) {
-              return t;
-            }
-        }
-        return null;
-    }
-
-    static <T> List<T> findAll(Collection<T> coll, Predicate<T> fn) {
-        return coll.stream().filter(fn).collect(Collectors.toList());
+        super.startProvisioning((IpClient.ProvisioningConfiguration) req);
     }
 }
diff --git a/android/net/metrics/ConnectStats.java b/android/net/metrics/ConnectStats.java
index 30b2656..2495cab 100644
--- a/android/net/metrics/ConnectStats.java
+++ b/android/net/metrics/ConnectStats.java
@@ -20,6 +20,7 @@
 import android.system.OsConstants;
 import android.util.IntArray;
 import android.util.SparseIntArray;
+
 import com.android.internal.util.BitUtils;
 import com.android.internal.util.TokenBucket;
 
@@ -43,6 +44,8 @@
     public final TokenBucket mLatencyTb;
     /** Maximum number of latency values recorded. */
     public final int mMaxLatencyRecords;
+    /** Total count of events */
+    public int eventCount = 0;
     /** Total count of successful connects. */
     public int connectCount = 0;
     /** Total count of successful connects done in blocking mode. */
@@ -57,12 +60,15 @@
         mMaxLatencyRecords = maxLatencyRecords;
     }
 
-    public void addEvent(int errno, int latencyMs, String ipAddr) {
+    boolean addEvent(int errno, int latencyMs, String ipAddr) {
+        eventCount++;
         if (isSuccess(errno)) {
             countConnect(errno, ipAddr);
             countLatency(errno, latencyMs);
+            return true;
         } else {
             countError(errno);
+            return false;
         }
     }
 
@@ -101,7 +107,7 @@
         return (errno == 0) || isNonBlocking(errno);
     }
 
-    private static boolean isNonBlocking(int errno) {
+    static boolean isNonBlocking(int errno) {
         // On non-blocking TCP sockets, connect() immediately returns EINPROGRESS.
         // On non-blocking TCP sockets that are connecting, connect() immediately returns EALREADY.
         return (errno == EINPROGRESS) || (errno == EALREADY);
@@ -117,6 +123,7 @@
         for (int t : BitUtils.unpackBits(transports)) {
             builder.append(NetworkCapabilities.transportNameOf(t)).append(", ");
         }
+        builder.append(String.format("%d events, ", eventCount));
         builder.append(String.format("%d success, ", connectCount));
         builder.append(String.format("%d blocking, ", connectBlockingCount));
         builder.append(String.format("%d IPv6 dst", ipv6ConnectCount));
diff --git a/android/net/metrics/DnsEvent.java b/android/net/metrics/DnsEvent.java
index a4970e4..81b098b 100644
--- a/android/net/metrics/DnsEvent.java
+++ b/android/net/metrics/DnsEvent.java
@@ -17,11 +17,13 @@
 package android.net.metrics;
 
 import android.net.NetworkCapabilities;
-import java.util.Arrays;
+
 import com.android.internal.util.BitUtils;
 
+import java.util.Arrays;
+
 /**
- * A DNS event recorded by NetdEventListenerService.
+ * A batch of DNS events recorded by NetdEventListenerService for a specific network.
  * {@hide}
  */
 final public class DnsEvent {
@@ -38,6 +40,8 @@
     // the eventTypes, returnCodes, and latenciesMs arrays have the same length and the i-th event
     // is spread across the three array at position i.
     public int eventCount;
+    // The number of successful DNS queries recorded.
+    public int successCount;
     // The types of DNS queries as defined in INetdEventListener.
     public byte[] eventTypes;
     // Current getaddrinfo codes go from 1 to EAI_MAX = 15. gethostbyname returns errno, but there
@@ -54,10 +58,11 @@
         latenciesMs = new int[initialCapacity];
     }
 
-    public void addResult(byte eventType, byte returnCode, int latencyMs) {
+    boolean addResult(byte eventType, byte returnCode, int latencyMs) {
+        boolean isSuccess = (returnCode == 0);
         if (eventCount >= SIZE_LIMIT) {
             // TODO: implement better rate limiting that does not biases metrics.
-            return;
+            return isSuccess;
         }
         if (eventCount == eventTypes.length) {
             resize((int) (1.4 * eventCount));
@@ -66,6 +71,10 @@
         returnCodes[eventCount] = returnCode;
         latenciesMs[eventCount] = latencyMs;
         eventCount++;
+        if (isSuccess) {
+            successCount++;
+        }
+        return isSuccess;
     }
 
     public void resize(int newLength) {
@@ -80,6 +89,8 @@
         for (int t : BitUtils.unpackBits(transports)) {
             builder.append(NetworkCapabilities.transportNameOf(t)).append(", ");
         }
-        return builder.append(eventCount).append(" events)").toString();
+        builder.append(String.format("%d events, ", eventCount));
+        builder.append(String.format("%d success)", successCount));
+        return builder.toString();
     }
 }
diff --git a/android/net/metrics/NetworkMetrics.java b/android/net/metrics/NetworkMetrics.java
new file mode 100644
index 0000000..2b662a0
--- /dev/null
+++ b/android/net/metrics/NetworkMetrics.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2017 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.net.metrics;
+
+import android.net.NetworkCapabilities;
+
+import com.android.internal.util.BitUtils;
+import com.android.internal.util.TokenBucket;
+
+import java.util.StringJoiner;
+
+/**
+ * A class accumulating network metrics received from Netd regarding dns queries and
+ * connect() calls on a given network.
+ *
+ * This class also accumulates running sums of dns and connect latency stats and
+ * error counts for bug report logging.
+ *
+ * @hide
+ */
+public class NetworkMetrics {
+
+    private static final int INITIAL_DNS_BATCH_SIZE = 100;
+    private static final int CONNECT_LATENCY_MAXIMUM_RECORDS = 20000;
+
+    // The network id of the Android Network.
+    public final int netId;
+    // The transport types bitmap of the Android Network, as defined in NetworkCapabilities.java.
+    public final long transports;
+    // Accumulated metrics for connect events.
+    public final ConnectStats connectMetrics;
+    // Accumulated metrics for dns events.
+    public final DnsEvent dnsMetrics;
+    // Running sums of latencies and error counts for connect and dns events.
+    public final Summary summary;
+    // Running sums of the most recent latencies and error counts for connect and dns events.
+    // Starts null until some events are accumulated.
+    // Allows to collect periodic snapshot of the running summaries for a given network.
+    public Summary pendingSummary;
+
+    public NetworkMetrics(int netId, long transports, TokenBucket tb) {
+        this.netId = netId;
+        this.transports = transports;
+        this.connectMetrics =
+                new ConnectStats(netId, transports, tb, CONNECT_LATENCY_MAXIMUM_RECORDS);
+        this.dnsMetrics = new DnsEvent(netId, transports, INITIAL_DNS_BATCH_SIZE);
+        this.summary = new Summary(netId, transports);
+    }
+
+    /**
+     * Get currently pending Summary statistics, if any, for this NetworkMetrics, merge them
+     * into the long running Summary statistics of this NetworkMetrics, and also clear them.
+     */
+    public Summary getPendingStats() {
+        Summary s = pendingSummary;
+        pendingSummary = null;
+        if (s != null) {
+            summary.merge(s);
+        }
+        return s;
+    }
+
+    /** Accumulate a dns query result reported by netd. */
+    public void addDnsResult(int eventType, int returnCode, int latencyMs) {
+        if (pendingSummary == null) {
+            pendingSummary = new Summary(netId, transports);
+        }
+        boolean isSuccess = dnsMetrics.addResult((byte) eventType, (byte) returnCode, latencyMs);
+        pendingSummary.dnsLatencies.count(latencyMs);
+        pendingSummary.dnsErrorRate.count(isSuccess ? 0 : 1);
+    }
+
+    /** Accumulate a connect query result reported by netd. */
+    public void addConnectResult(int error, int latencyMs, String ipAddr) {
+        if (pendingSummary == null) {
+            pendingSummary = new Summary(netId, transports);
+        }
+        boolean isSuccess = connectMetrics.addEvent(error, latencyMs, ipAddr);
+        pendingSummary.connectErrorRate.count(isSuccess ? 0 : 1);
+        if (ConnectStats.isNonBlocking(error)) {
+            pendingSummary.connectLatencies.count(latencyMs);
+        }
+    }
+
+    /** Represents running sums for dns and connect average error counts and average latencies. */
+    public static class Summary {
+
+        public final int netId;
+        public final long transports;
+        // DNS latencies measured in milliseconds.
+        public final Metrics dnsLatencies = new Metrics();
+        // DNS error rate measured in percentage points.
+        public final Metrics dnsErrorRate = new Metrics();
+        // Blocking connect latencies measured in milliseconds.
+        public final Metrics connectLatencies = new Metrics();
+        // Blocking and non blocking connect error rate measured in percentage points.
+        public final Metrics connectErrorRate = new Metrics();
+
+        public Summary(int netId, long transports) {
+            this.netId = netId;
+            this.transports = transports;
+        }
+
+        void merge(Summary that) {
+            dnsLatencies.merge(that.dnsLatencies);
+            dnsErrorRate.merge(that.dnsErrorRate);
+            connectLatencies.merge(that.connectLatencies);
+            connectErrorRate.merge(that.connectErrorRate);
+        }
+
+        @Override
+        public String toString() {
+            StringJoiner j = new StringJoiner(", ", "{", "}");
+            j.add("netId=" + netId);
+            for (int t : BitUtils.unpackBits(transports)) {
+                j.add(NetworkCapabilities.transportNameOf(t));
+            }
+            j.add(String.format("dns avg=%dms max=%dms err=%.1f%% tot=%d",
+                    (int) dnsLatencies.average(), (int) dnsLatencies.max,
+                    100 * dnsErrorRate.average(), dnsErrorRate.count));
+            j.add(String.format("connect avg=%dms max=%dms err=%.1f%% tot=%d",
+                    (int) connectLatencies.average(), (int) connectLatencies.max,
+                    100 * connectErrorRate.average(), connectErrorRate.count));
+            return j.toString();
+        }
+    }
+
+    /** Tracks a running sum and returns the average of a metric. */
+    static class Metrics {
+        public double sum;
+        public double max = Double.MIN_VALUE;
+        public int count;
+
+        void merge(Metrics that) {
+            this.count += that.count;
+            this.sum += that.sum;
+            this.max = Math.max(this.max, that.max);
+        }
+
+        void count(double value) {
+            count++;
+            sum += value;
+            max = Math.max(max, value);
+        }
+
+        double average() {
+            double a = sum / (double) count;
+            if (Double.isNaN(a)) {
+                a = 0;
+            }
+            return a;
+        }
+    }
+}
diff --git a/android/net/netlink/NetlinkSocket.java b/android/net/netlink/NetlinkSocket.java
index a9e0cd9..f5f211d 100644
--- a/android/net/netlink/NetlinkSocket.java
+++ b/android/net/netlink/NetlinkSocket.java
@@ -96,7 +96,7 @@
         mDescriptor = Os.socket(
                 OsConstants.AF_NETLINK, OsConstants.SOCK_DGRAM, nlProto);
 
-        Libcore.os.setsockoptInt(
+        Os.setsockoptInt(
                 mDescriptor, OsConstants.SOL_SOCKET,
                 OsConstants.SO_RCVBUF, SOCKET_RECV_BUFSIZE);
     }
diff --git a/android/net/util/SharedLog.java b/android/net/util/SharedLog.java
index 343d237..bbd3d13 100644
--- a/android/net/util/SharedLog.java
+++ b/android/net/util/SharedLog.java
@@ -106,6 +106,10 @@
         record(Category.NONE, msg);
     }
 
+    public void logf(String fmt, Object... args) {
+        log(String.format(fmt, args));
+    }
+
     public void mark(String msg) {
         record(Category.MARK, msg);
     }
diff --git a/android/net/wifi/WifiManager.java b/android/net/wifi/WifiManager.java
index b08b4b7..649b0ce 100644
--- a/android/net/wifi/WifiManager.java
+++ b/android/net/wifi/WifiManager.java
@@ -1029,6 +1029,26 @@
     }
 
     /**
+     * Return all matching WifiConfigurations for this ScanResult.
+     *
+     * An empty list will be returned when no configurations are installed or if no configurations
+     * match the ScanResult.
+     *
+     * @param scanResult scanResult that represents the BSSID
+     * @return A list of {@link WifiConfiguration}
+     * @throws UnsupportedOperationException if Passpoint is not enabled on the device.
+     * @hide
+     */
+    public List<WifiConfiguration> getAllMatchingWifiConfigs(ScanResult scanResult) {
+        try {
+            return mService.getAllMatchingWifiConfigs(scanResult);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+
+    /**
      * Returns a list of Hotspot 2.0 OSU (Online Sign-Up) providers associated with the given AP.
      *
      * An empty list will be returned if no match is found.
diff --git a/android/net/wifi/rtt/RangingRequest.java b/android/net/wifi/rtt/RangingRequest.java
index 997b680..a396281 100644
--- a/android/net/wifi/rtt/RangingRequest.java
+++ b/android/net/wifi/rtt/RangingRequest.java
@@ -17,13 +17,22 @@
 package android.net.wifi.rtt;
 
 import android.net.wifi.ScanResult;
+import android.net.wifi.aware.AttachCallback;
+import android.net.wifi.aware.DiscoverySessionCallback;
+import android.net.wifi.aware.IdentityChangedListener;
+import android.net.wifi.aware.PeerHandle;
+import android.net.wifi.aware.WifiAwareManager;
 import android.os.Handler;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
 
+import libcore.util.HexEncoding;
+
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
+import java.util.Objects;
 import java.util.StringJoiner;
 
 /**
@@ -33,8 +42,8 @@
  * {@link WifiRttManager#startRanging(RangingRequest, RangingResultCallback, Handler)}.
  * <p>
  * The ranging request is a batch request - specifying a set of devices (specified using
- * {@link RangingRequest.Builder#addAp(ScanResult)} and
- * {@link RangingRequest.Builder#addAps(List)}).
+ * {@link RangingRequest.Builder#addAccessPoint(ScanResult)} and
+ * {@link RangingRequest.Builder#addAccessPoints(List)}).
  *
  * @hide RTT_API
  */
@@ -44,8 +53,8 @@
     /**
      * Returns the maximum number of peers to range which can be specified in a single {@code
      * RangingRequest}. The limit applies no matter how the peers are added to the request, e.g.
-     * through {@link RangingRequest.Builder#addAp(ScanResult)} or
-     * {@link RangingRequest.Builder#addAps(List)}.
+     * through {@link RangingRequest.Builder#addAccessPoint(ScanResult)} or
+     * {@link RangingRequest.Builder#addAccessPoints(List)}.
      *
      * @return Maximum number of peers.
      */
@@ -94,11 +103,34 @@
     }
 
     /** @hide */
-    public void enforceValidity() {
+    public void enforceValidity(boolean awareSupported) {
         if (mRttPeers.size() > MAX_PEERS) {
             throw new IllegalArgumentException(
                     "Ranging to too many peers requested. Use getMaxPeers() API to get limit.");
         }
+
+        for (RttPeer peer: mRttPeers) {
+            if (peer instanceof RttPeerAp) {
+                RttPeerAp apPeer = (RttPeerAp) peer;
+                if (apPeer.scanResult == null || apPeer.scanResult.BSSID == null) {
+                    throw new IllegalArgumentException("Invalid AP peer specification");
+                }
+            } else if (peer instanceof RttPeerAware) {
+                if (!awareSupported) {
+                    throw new IllegalArgumentException(
+                            "Request contains Aware peers - but Aware isn't supported on this "
+                                    + "device");
+                }
+
+                RttPeerAware awarePeer = (RttPeerAware) peer;
+                if (awarePeer.peerMacAddress == null && awarePeer.peerHandle == null) {
+                    throw new IllegalArgumentException("Invalid Aware peer specification");
+                }
+            } else {
+                throw new IllegalArgumentException(
+                        "Request contains unknown peer specification types");
+            }
+        }
     }
 
     /**
@@ -116,7 +148,7 @@
          * @return The builder to facilitate chaining
          *         {@code builder.setXXX(..).setXXX(..)}.
          */
-        public Builder addAp(ScanResult apInfo) {
+        public Builder addAccessPoint(ScanResult apInfo) {
             if (apInfo == null) {
                 throw new IllegalArgumentException("Null ScanResult!");
             }
@@ -133,17 +165,55 @@
          * @return The builder to facilitate chaining
          *         {@code builder.setXXX(..).setXXX(..)}.
          */
-        public Builder addAps(List<ScanResult> apInfos) {
+        public Builder addAccessPoints(List<ScanResult> apInfos) {
             if (apInfos == null) {
                 throw new IllegalArgumentException("Null list of ScanResults!");
             }
             for (ScanResult scanResult : apInfos) {
-                addAp(scanResult);
+                addAccessPoint(scanResult);
             }
             return this;
         }
 
         /**
+         * Add the device specified by the {@code peerMacAddress} to the list of devices with
+         * which to measure range.
+         *
+         * The MAC address may be obtained out-of-band from a peer Wi-Fi Aware device. A Wi-Fi
+         * Aware device may obtain its MAC address using the {@link IdentityChangedListener}
+         * provided to
+         * {@link WifiAwareManager#attach(AttachCallback, IdentityChangedListener, Handler)}.
+         *
+         * * Note: in order to use this API the device must support Wi-Fi Aware
+         * {@link android.net.wifi.aware}.
+         *
+         * @param peerMacAddress The MAC address of the Wi-Fi Aware peer.
+         * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
+         */
+        public Builder addWifiAwarePeer(byte[] peerMacAddress) {
+            mRttPeers.add(new RttPeerAware(peerMacAddress));
+            return this;
+        }
+
+        /**
+         * Add a device specified by a {@link PeerHandle} to the list of devices with which to
+         * measure range.
+         *
+         * The {@link PeerHandle} may be obtained as part of the Wi-Fi Aware discovery process. E.g.
+         * using {@link DiscoverySessionCallback#onServiceDiscovered(PeerHandle, byte[], List)}.
+         *
+         * Note: in order to use this API the device must support Wi-Fi Aware
+         * {@link android.net.wifi.aware}.
+         *
+         * @param peerHandle The peer handler of the peer Wi-Fi Aware device.
+         * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
+         */
+        public Builder addWifiAwarePeer(PeerHandle peerHandle) {
+            mRttPeers.add(new RttPeerAware(peerHandle));
+            return this;
+        }
+
+        /**
          * Build {@link RangingRequest} given the current configurations made on the
          * builder.
          */
@@ -234,4 +304,89 @@
             return scanResult.hashCode();
         }
     }
-}
\ No newline at end of file
+
+    /** @hide */
+    public static class RttPeerAware implements RttPeer, Parcelable {
+        public PeerHandle peerHandle;
+        public byte[] peerMacAddress;
+
+        public RttPeerAware(PeerHandle peerHandle) {
+            if (peerHandle == null) {
+                throw new IllegalArgumentException("Null peerHandle");
+            }
+            this.peerHandle = peerHandle;
+            peerMacAddress = null;
+        }
+
+        public RttPeerAware(byte[] peerMacAddress) {
+            if (peerMacAddress == null) {
+                throw new IllegalArgumentException("Null peerMacAddress");
+            }
+
+            this.peerMacAddress = peerMacAddress;
+            peerHandle = null;
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            if (peerHandle == null) {
+                dest.writeBoolean(false);
+                dest.writeByteArray(peerMacAddress);
+            } else {
+                dest.writeBoolean(true);
+                dest.writeInt(peerHandle.peerId);
+            }
+        }
+
+        public static final Creator<RttPeerAware> CREATOR = new Creator<RttPeerAware>() {
+            @Override
+            public RttPeerAware[] newArray(int size) {
+                return new RttPeerAware[size];
+            }
+
+            @Override
+            public RttPeerAware createFromParcel(Parcel in) {
+                boolean peerHandleAvail = in.readBoolean();
+                if (peerHandleAvail) {
+                    return new RttPeerAware(new PeerHandle(in.readInt()));
+                } else {
+                    return new RttPeerAware(in.createByteArray());
+                }
+            }
+        };
+
+        @Override
+        public String toString() {
+            return new StringBuilder("RttPeerAware: peerHandle=").append(
+                    peerHandle == null ? "<null>" : Integer.toString(peerHandle.peerId)).append(
+                    ", peerMacAddress=").append(peerMacAddress == null ? "<null>"
+                    : new String(HexEncoding.encode(peerMacAddress))).toString();
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+
+            if (!(o instanceof RttPeerAware)) {
+                return false;
+            }
+
+            RttPeerAware lhs = (RttPeerAware) o;
+
+            return Objects.equals(peerHandle, lhs.peerHandle) && Arrays.equals(peerMacAddress,
+                    lhs.peerMacAddress);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(peerHandle.peerId, peerMacAddress);
+        }
+    }
+}
diff --git a/android/net/wifi/rtt/RangingResult.java b/android/net/wifi/rtt/RangingResult.java
index 918803e..93e52ae 100644
--- a/android/net/wifi/rtt/RangingResult.java
+++ b/android/net/wifi/rtt/RangingResult.java
@@ -16,13 +16,16 @@
 
 package android.net.wifi.rtt;
 
+import android.annotation.IntDef;
+import android.net.wifi.aware.PeerHandle;
 import android.os.Handler;
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.util.Log;
 
 import libcore.util.HexEncoding;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Objects;
@@ -40,28 +43,61 @@
 public final class RangingResult implements Parcelable {
     private static final String TAG = "RangingResult";
 
+    /** @hide */
+    @IntDef({STATUS_SUCCESS, STATUS_FAIL})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface RangeResultStatus {
+    }
+
+    /**
+     * Individual range request status, {@link #getStatus()}. Indicates ranging operation was
+     * successful and distance value is valid.
+     */
+    public static final int STATUS_SUCCESS = 0;
+
+    /**
+     * Individual range request status, {@link #getStatus()}. Indicates ranging operation failed
+     * and the distance value is invalid.
+     */
+    public static final int STATUS_FAIL = 1;
+
     private final int mStatus;
     private final byte[] mMac;
-    private final int mDistanceCm;
-    private final int mDistanceStdDevCm;
+    private final PeerHandle mPeerHandle;
+    private final int mDistanceMm;
+    private final int mDistanceStdDevMm;
     private final int mRssi;
     private final long mTimestamp;
 
     /** @hide */
-    public RangingResult(int status, byte[] mac, int distanceCm, int distanceStdDevCm, int rssi,
-            long timestamp) {
+    public RangingResult(@RangeResultStatus int status, byte[] mac, int distanceMm,
+            int distanceStdDevMm, int rssi, long timestamp) {
         mStatus = status;
         mMac = mac;
-        mDistanceCm = distanceCm;
-        mDistanceStdDevCm = distanceStdDevCm;
+        mPeerHandle = null;
+        mDistanceMm = distanceMm;
+        mDistanceStdDevMm = distanceStdDevMm;
+        mRssi = rssi;
+        mTimestamp = timestamp;
+    }
+
+    /** @hide */
+    public RangingResult(@RangeResultStatus int status, PeerHandle peerHandle, int distanceMm,
+            int distanceStdDevMm, int rssi, long timestamp) {
+        mStatus = status;
+        mMac = null;
+        mPeerHandle = peerHandle;
+        mDistanceMm = distanceMm;
+        mDistanceStdDevMm = distanceStdDevMm;
         mRssi = rssi;
         mTimestamp = timestamp;
     }
 
     /**
-     * @return The status of ranging measurement: {@link RangingResultCallback#STATUS_SUCCESS} in
-     * case of success, and {@link RangingResultCallback#STATUS_FAIL} in case of failure.
+     * @return The status of ranging measurement: {@link #STATUS_SUCCESS} in case of success, and
+     * {@link #STATUS_FAIL} in case of failure.
      */
+    @RangeResultStatus
     public int getStatus() {
         return mStatus;
     }
@@ -70,57 +106,80 @@
      * @return The MAC address of the device whose range measurement was requested. Will correspond
      * to the MAC address of the device in the {@link RangingRequest}.
      * <p>
-     * Always valid (i.e. when {@link #getStatus()} is either SUCCESS or FAIL.
+     * Will return a {@code null} for results corresponding to requests issued using a {@code
+     * PeerHandle}, i.e. using the {@link RangingRequest.Builder#addWifiAwarePeer(PeerHandle)} API.
      */
     public byte[] getMacAddress() {
         return mMac;
     }
 
     /**
-     * @return The distance (in cm) to the device specified by {@link #getMacAddress()}.
+     * @return The PeerHandle of the device whose reange measurement was requested. Will correspond
+     * to the PeerHandle of the devices requested using
+     * {@link RangingRequest.Builder#addWifiAwarePeer(PeerHandle)}.
      * <p>
-     * Only valid if {@link #getStatus()} returns {@link RangingResultCallback#STATUS_SUCCESS}.
+     * Will return a {@code null} for results corresponding to requests issued using a MAC address.
      */
-    public int getDistanceCm() {
-        if (mStatus != RangingResultCallback.STATUS_SUCCESS) {
-            Log.e(TAG, "getDistanceCm(): invalid value retrieved");
-        }
-        return mDistanceCm;
+    public PeerHandle getPeerHandle() {
+        return mPeerHandle;
     }
 
     /**
-     * @return The standard deviation of the measured distance (in cm) to the device specified by
-     * {@link #getMacAddress()}. The standard deviation is calculated over the measurements
-     * executed in a single RTT burst.
+     * @return The distance (in mm) to the device specified by {@link #getMacAddress()} or
+     * {@link #getPeerHandle()}.
      * <p>
-     * Only valid if {@link #getStatus()} returns {@link RangingResultCallback#STATUS_SUCCESS}.
+     * Only valid if {@link #getStatus()} returns {@link #STATUS_SUCCESS}, otherwise will throw an
+     * exception.
      */
-    public int getDistanceStdDevCm() {
-        if (mStatus != RangingResultCallback.STATUS_SUCCESS) {
-            Log.e(TAG, "getDistanceStdDevCm(): invalid value retrieved");
+    public int getDistanceMm() {
+        if (mStatus != STATUS_SUCCESS) {
+            throw new IllegalStateException(
+                    "getDistanceMm(): invoked on an invalid result: getStatus()=" + mStatus);
         }
-        return mDistanceStdDevCm;
+        return mDistanceMm;
+    }
+
+    /**
+     * @return The standard deviation of the measured distance (in mm) to the device specified by
+     * {@link #getMacAddress()} or {@link #getPeerHandle()}. The standard deviation is calculated
+     * over the measurements executed in a single RTT burst.
+     * <p>
+     * Only valid if {@link #getStatus()} returns {@link #STATUS_SUCCESS}, otherwise will throw an
+     * exception.
+     */
+    public int getDistanceStdDevMm() {
+        if (mStatus != STATUS_SUCCESS) {
+            throw new IllegalStateException(
+                    "getDistanceStdDevMm(): invoked on an invalid result: getStatus()=" + mStatus);
+        }
+        return mDistanceStdDevMm;
     }
 
     /**
      * @return The average RSSI (in units of -0.5dB) observed during the RTT measurement.
      * <p>
-     * Only valid if {@link #getStatus()} returns {@link RangingResultCallback#STATUS_SUCCESS}.
+     * Only valid if {@link #getStatus()} returns {@link #STATUS_SUCCESS}, otherwise will throw an
+     * exception.
      */
     public int getRssi() {
-        if (mStatus != RangingResultCallback.STATUS_SUCCESS) {
-            // TODO: should this be an exception?
-            Log.e(TAG, "getRssi(): invalid value retrieved");
+        if (mStatus != STATUS_SUCCESS) {
+            throw new IllegalStateException(
+                    "getRssi(): invoked on an invalid result: getStatus()=" + mStatus);
         }
         return mRssi;
     }
 
     /**
-     * @return The timestamp (in us) at which the ranging operation was performed
+     * @return The timestamp, in us since boot, at which the ranging operation was performed.
      * <p>
-     * Only valid if {@link #getStatus()} returns {@link RangingResultCallback#STATUS_SUCCESS}.
+     * Only valid if {@link #getStatus()} returns {@link #STATUS_SUCCESS}, otherwise will throw an
+     * exception.
      */
-    public long getRangingTimestamp() {
+    public long getRangingTimestampUs() {
+        if (mStatus != STATUS_SUCCESS) {
+            throw new IllegalStateException(
+                    "getRangingTimestamp(): invoked on an invalid result: getStatus()=" + mStatus);
+        }
         return mTimestamp;
     }
 
@@ -135,8 +194,14 @@
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeInt(mStatus);
         dest.writeByteArray(mMac);
-        dest.writeInt(mDistanceCm);
-        dest.writeInt(mDistanceStdDevCm);
+        if (mPeerHandle == null) {
+            dest.writeBoolean(false);
+        } else {
+            dest.writeBoolean(true);
+            dest.writeInt(mPeerHandle.peerId);
+        }
+        dest.writeInt(mDistanceMm);
+        dest.writeInt(mDistanceStdDevMm);
         dest.writeInt(mRssi);
         dest.writeLong(mTimestamp);
     }
@@ -152,11 +217,22 @@
         public RangingResult createFromParcel(Parcel in) {
             int status = in.readInt();
             byte[] mac = in.createByteArray();
-            int distanceCm = in.readInt();
-            int distanceStdDevCm = in.readInt();
+            boolean peerHandlePresent = in.readBoolean();
+            PeerHandle peerHandle = null;
+            if (peerHandlePresent) {
+                peerHandle = new PeerHandle(in.readInt());
+            }
+            int distanceMm = in.readInt();
+            int distanceStdDevMm = in.readInt();
             int rssi = in.readInt();
             long timestamp = in.readLong();
-            return new RangingResult(status, mac, distanceCm, distanceStdDevCm, rssi, timestamp);
+            if (peerHandlePresent) {
+                return new RangingResult(status, peerHandle, distanceMm, distanceStdDevMm, rssi,
+                        timestamp);
+            } else {
+                return new RangingResult(status, mac, distanceMm, distanceStdDevMm, rssi,
+                        timestamp);
+            }
         }
     };
 
@@ -164,9 +240,10 @@
     @Override
     public String toString() {
         return new StringBuilder("RangingResult: [status=").append(mStatus).append(", mac=").append(
-                mMac == null ? "<null>" : HexEncoding.encodeToString(mMac)).append(
-                ", distanceCm=").append(mDistanceCm).append(", distanceStdDevCm=").append(
-                mDistanceStdDevCm).append(", rssi=").append(mRssi).append(", timestamp=").append(
+                mMac == null ? "<null>" : new String(HexEncoding.encodeToString(mMac))).append(
+                ", peerHandle=").append(mPeerHandle == null ? "<null>" : mPeerHandle.peerId).append(
+                ", distanceMm=").append(mDistanceMm).append(", distanceStdDevMm=").append(
+                mDistanceStdDevMm).append(", rssi=").append(mRssi).append(", timestamp=").append(
                 mTimestamp).append("]").toString();
     }
 
@@ -182,13 +259,15 @@
 
         RangingResult lhs = (RangingResult) o;
 
-        return mStatus == lhs.mStatus && Arrays.equals(mMac, lhs.mMac)
-                && mDistanceCm == lhs.mDistanceCm && mDistanceStdDevCm == lhs.mDistanceStdDevCm
-                && mRssi == lhs.mRssi && mTimestamp == lhs.mTimestamp;
+        return mStatus == lhs.mStatus && Arrays.equals(mMac, lhs.mMac) && Objects.equals(
+                mPeerHandle, lhs.mPeerHandle) && mDistanceMm == lhs.mDistanceMm
+                && mDistanceStdDevMm == lhs.mDistanceStdDevMm && mRssi == lhs.mRssi
+                && mTimestamp == lhs.mTimestamp;
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mStatus, mMac, mDistanceCm, mDistanceStdDevCm, mRssi, mTimestamp);
+        return Objects.hash(mStatus, mMac, mPeerHandle, mDistanceMm, mDistanceStdDevMm, mRssi,
+                mTimestamp);
     }
-}
\ No newline at end of file
+}
diff --git a/android/net/wifi/rtt/RangingResultCallback.java b/android/net/wifi/rtt/RangingResultCallback.java
index d7270ad..7405e82 100644
--- a/android/net/wifi/rtt/RangingResultCallback.java
+++ b/android/net/wifi/rtt/RangingResultCallback.java
@@ -16,35 +16,43 @@
 
 package android.net.wifi.rtt;
 
+import android.annotation.IntDef;
 import android.os.Handler;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.List;
 
 /**
  * Base class for ranging result callbacks. Should be extended by applications and set when calling
- * {@link WifiRttManager#startRanging(RangingRequest, RangingResultCallback, Handler)}. A single
- * result from a range request will be called in this object.
+ * {@link WifiRttManager#startRanging(RangingRequest, RangingResultCallback, Handler)}. If the
+ * ranging operation fails in whole (not attempted) then {@link #onRangingFailure(int)} will be
+ * called with a failure code. If the ranging operation is performed for each of the requested
+ * peers then the {@link #onRangingResults(List)} will be called with the set of results (@link
+ * {@link RangingResult}, each of which has its own success/failure code
+ * {@link RangingResult#getStatus()}.
  *
  * @hide RTT_API
  */
 public abstract class RangingResultCallback {
-    /**
-     * Individual range request status, {@link RangingResult#getStatus()}. Indicates ranging
-     * operation was successful and distance value is valid.
-     */
-    public static final int STATUS_SUCCESS = 0;
+    /** @hide */
+    @IntDef({STATUS_CODE_FAIL})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface RangingOperationStatus {
+    }
 
     /**
-     * Individual range request status, {@link RangingResult#getStatus()}. Indicates ranging
-     * operation failed and the distance value is invalid.
+     * A failure code for the whole ranging request operation. Indicates a failure.
      */
-    public static final int STATUS_FAIL = 1;
+    public static final int STATUS_CODE_FAIL = 1;
 
     /**
      * Called when a ranging operation failed in whole - i.e. no ranging operation to any of the
      * devices specified in the request was attempted.
+     *
+     * @param code A status code indicating the type of failure.
      */
-    public abstract void onRangingFailure();
+    public abstract void onRangingFailure(@RangingOperationStatus int code);
 
     /**
      * Called when a ranging operation was executed. The list of results corresponds to devices
diff --git a/android/net/wifi/rtt/WifiRttManager.java b/android/net/wifi/rtt/WifiRttManager.java
index a085de1..435bb37 100644
--- a/android/net/wifi/rtt/WifiRttManager.java
+++ b/android/net/wifi/rtt/WifiRttManager.java
@@ -83,17 +83,18 @@
         }
 
         @Override
-        public void onRangingResults(int status, List<RangingResult> results) throws RemoteException {
-            if (VDBG) {
-                Log.v(TAG, "RttCallbackProxy: onRanginResults: status=" + status + ", results="
-                        + results);
-            }
+        public void onRangingFailure(int status) throws RemoteException {
+            if (VDBG) Log.v(TAG, "RttCallbackProxy: onRangingFailure: status=" + status);
             mHandler.post(() -> {
-               if (status == RangingResultCallback.STATUS_SUCCESS) {
-                   mCallback.onRangingResults(results);
-               } else {
-                   mCallback.onRangingFailure();
-               }
+               mCallback.onRangingFailure(status);
+            });
+        }
+
+        @Override
+        public void onRangingResults(List<RangingResult> results) throws RemoteException {
+            if (VDBG) Log.v(TAG, "RttCallbackProxy: onRanginResults: results=" + results);
+            mHandler.post(() -> {
+               mCallback.onRangingResults(results);
             });
         }
     }
diff --git a/android/os/BatteryProperty.java b/android/os/BatteryProperty.java
index 84119bd..b7e7b17 100644
--- a/android/os/BatteryProperty.java
+++ b/android/os/BatteryProperty.java
@@ -43,6 +43,13 @@
         return mValueLong;
     }
 
+    /**
+     * @hide
+     */
+    public void setLong(long val) {
+        mValueLong = val;
+    }
+
     /*
      * Parcel read/write code must be kept in sync with
      * frameworks/native/services/batteryservice/BatteryProperty.cpp
diff --git a/android/os/BatteryStats.java b/android/os/BatteryStats.java
index 9881927..8682c01 100644
--- a/android/os/BatteryStats.java
+++ b/android/os/BatteryStats.java
@@ -447,8 +447,7 @@
 
         /**
          * Returns the max duration if it is being tracked.
-         * Not all Timer subclasses track the max, total, current durations.
-
+         * Not all Timer subclasses track the max, total, and current durations.
          */
         public long getMaxDurationMsLocked(long elapsedRealtimeMs) {
             return -1;
@@ -456,14 +455,14 @@
 
         /**
          * Returns the current time the timer has been active, if it is being tracked.
-         * Not all Timer subclasses track the max, total, current durations.
+         * Not all Timer subclasses track the max, total, and current durations.
          */
         public long getCurrentDurationMsLocked(long elapsedRealtimeMs) {
             return -1;
         }
 
         /**
-         * Returns the current time the timer has been active, if it is being tracked.
+         * Returns the total time the timer has been active, if it is being tracked.
          *
          * Returns the total cumulative duration (i.e. sum of past durations) that this timer has
          * been on since reset.
@@ -471,7 +470,7 @@
          * depending on the Timer, getTotalTimeLocked may represent the total 'blamed' or 'pooled'
          * time, rather than the actual time. By contrast, getTotalDurationMsLocked always gives
          * the actual total time.
-         * Not all Timer subclasses track the max, total, current durations.
+         * Not all Timer subclasses track the max, total, and current durations.
          */
         public long getTotalDurationMsLocked(long elapsedRealtimeMs) {
             return -1;
@@ -600,9 +599,17 @@
         public abstract long getFullWifiLockTime(long elapsedRealtimeUs, int which);
         public abstract long getWifiScanTime(long elapsedRealtimeUs, int which);
         public abstract int getWifiScanCount(int which);
+        /**
+         * Returns the timer keeping track of wifi scans.
+         */
+        public abstract Timer getWifiScanTimer();
         public abstract int getWifiScanBackgroundCount(int which);
         public abstract long getWifiScanActualTime(long elapsedRealtimeUs);
         public abstract long getWifiScanBackgroundTime(long elapsedRealtimeUs);
+        /**
+         * Returns the timer keeping track of background wifi scans.
+         */
+        public abstract Timer getWifiScanBackgroundTimer();
         public abstract long getWifiBatchedScanTime(int csphBin, long elapsedRealtimeUs, int which);
         public abstract int getWifiBatchedScanCount(int csphBin, int which);
         public abstract long getWifiMulticastTime(long elapsedRealtimeUs, int which);
@@ -1911,6 +1918,13 @@
             long elapsedRealtimeUs, int which);
 
     /**
+     * Returns the {@link Timer} object that tracks the given screen brightness.
+     *
+     * {@hide}
+     */
+    public abstract Timer getScreenBrightnessTimer(int brightnessBin);
+
+    /**
      * Returns the time in microseconds that power save mode has been enabled while the device was
      * running on battery.
      *
@@ -2019,6 +2033,14 @@
             long elapsedRealtimeUs, int which);
 
     /**
+     * Returns the {@link Timer} object that tracks how much the phone has been trying to
+     * acquire a signal.
+     *
+     * {@hide}
+     */
+    public abstract Timer getPhoneSignalScanningTimer();
+
+    /**
      * Returns the number of times the phone has entered the given signal strength.
      *
      * {@hide}
@@ -2026,6 +2048,12 @@
     public abstract int getPhoneSignalStrengthCount(int strengthBin, int which);
 
     /**
+     * Return the {@link Timer} object used to track the given signal strength's duration and
+     * counts.
+     */
+    protected abstract Timer getPhoneSignalStrengthTimer(int strengthBin);
+
+    /**
      * Returns the time in microseconds that the mobile network has been active
      * (in a high power state).
      *
@@ -2108,6 +2136,11 @@
      */
     public abstract int getPhoneDataConnectionCount(int dataType, int which);
 
+    /**
+     * Returns the {@link Timer} object that tracks the phone's data connection type stats.
+     */
+    public abstract Timer getPhoneDataConnectionTimer(int dataType);
+
     public static final int WIFI_SUPPL_STATE_INVALID = 0;
     public static final int WIFI_SUPPL_STATE_DISCONNECTED = 1;
     public static final int WIFI_SUPPL_STATE_INTERFACE_DISABLED = 2;
@@ -2267,6 +2300,13 @@
     public abstract int getWifiStateCount(int wifiState, int which);
 
     /**
+     * Returns the {@link Timer} object that tracks the given WiFi state.
+     *
+     * {@hide}
+     */
+    public abstract Timer getWifiStateTimer(int wifiState);
+
+    /**
      * Returns the time in microseconds that the wifi supplicant has been
      * in a given state.
      *
@@ -2282,6 +2322,13 @@
      */
     public abstract int getWifiSupplStateCount(int state, int which);
 
+    /**
+     * Returns the {@link Timer} object that tracks the given wifi supplicant state.
+     *
+     * {@hide}
+     */
+    public abstract Timer getWifiSupplStateTimer(int state);
+
     public static final int NUM_WIFI_SIGNAL_STRENGTH_BINS = 5;
 
     /**
@@ -2301,6 +2348,13 @@
     public abstract int getWifiSignalStrengthCount(int strengthBin, int which);
 
     /**
+     * Returns the {@link Timer} object that tracks the given WIFI signal strength.
+     *
+     * {@hide}
+     */
+    public abstract Timer getWifiSignalStrengthTimer(int strengthBin);
+
+    /**
      * Returns the time in microseconds that the flashlight has been on while the device was
      * running on battery.
      *
@@ -2487,13 +2541,13 @@
     public abstract int getDischargeAmountScreenOffSinceCharge();
 
     /**
-     * Get the amount the battery has discharged while the screen was doze,
+     * Get the amount the battery has discharged while the screen was dozing,
      * since the last time power was unplugged.
      */
     public abstract int getDischargeAmountScreenDoze();
 
     /**
-     * Get the amount the battery has discharged while the screen was doze,
+     * Get the amount the battery has discharged while the screen was dozing,
      * since the last time the device was charged.
      */
     public abstract int getDischargeAmountScreenDozeSinceCharge();
@@ -2626,20 +2680,20 @@
      * micro-Ampere-hours. This will be non-zero only if the device's battery has
      * a coulomb counter.
      */
-    public abstract long getMahDischargeScreenOff(int which);
+    public abstract long getUahDischargeScreenOff(int which);
 
     /**
      * Return the amount of battery discharge while the screen was in doze mode, measured in
      * micro-Ampere-hours. This will be non-zero only if the device's battery has
      * a coulomb counter.
      */
-    public abstract long getMahDischargeScreenDoze(int which);
+    public abstract long getUahDischargeScreenDoze(int which);
 
     /**
      * Return the amount of battery discharge  measured in micro-Ampere-hours. This will be
      * non-zero only if the device's battery has a coulomb counter.
      */
-    public abstract long getMahDischarge(int which);
+    public abstract long getUahDischarge(int which);
 
     /**
      * Returns the estimated real battery capacity, which may be less than the capacity
@@ -2777,6 +2831,10 @@
         }
     }
 
+    private static long roundUsToMs(long timeUs) {
+        return (timeUs + 500) / 1000;
+    }
+
     private static long computeWakeLock(Timer timer, long elapsedRealtimeUs, int which) {
         if (timer != null) {
             // Convert from microseconds to milliseconds with rounding
@@ -2981,10 +3039,9 @@
                                         Timer timer, long rawRealtime, int which) {
         if (timer != null) {
             // Convert from microseconds to milliseconds with rounding
-            final long totalTime = (timer.getTotalTimeLocked(rawRealtime, which) + 500)
-                    / 1000;
+            final long totalTime = roundUsToMs(timer.getTotalTimeLocked(rawRealtime, which));
             final int count = timer.getCountLocked(which);
-            if (totalTime != 0) {
+            if (totalTime != 0 || count != 0) {
                 dumpLine(pw, uid, category, type, totalTime, count);
             }
         }
@@ -3000,17 +3057,31 @@
      * @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT
      */
     private static void dumpTimer(ProtoOutputStream proto, long fieldId,
-                                        Timer timer, long rawRealtime, int which) {
+                                        Timer timer, long rawRealtimeUs, int which) {
         if (timer == null) {
             return;
         }
         // Convert from microseconds to milliseconds with rounding
-        final long totalTimeMs = (timer.getTotalTimeLocked(rawRealtime, which) + 500) / 1000;
+        final long timeMs = roundUsToMs(timer.getTotalTimeLocked(rawRealtimeUs, which));
         final int count = timer.getCountLocked(which);
-        if (totalTimeMs != 0 || count != 0) {
+        final long maxDurationMs = timer.getMaxDurationMsLocked(rawRealtimeUs / 1000);
+        final long curDurationMs = timer.getCurrentDurationMsLocked(rawRealtimeUs / 1000);
+        final long totalDurationMs = timer.getTotalDurationMsLocked(rawRealtimeUs / 1000);
+        if (timeMs != 0 || count != 0 || maxDurationMs != -1 || curDurationMs != -1
+                || totalDurationMs != -1) {
             final long token = proto.start(fieldId);
-            proto.write(TimerProto.DURATION_MS, totalTimeMs);
+            proto.write(TimerProto.DURATION_MS, timeMs);
             proto.write(TimerProto.COUNT, count);
+            // These values will be -1 for timers that don't implement the functionality.
+            if (maxDurationMs != -1) {
+                proto.write(TimerProto.MAX_DURATION_MS, maxDurationMs);
+            }
+            if (curDurationMs != -1) {
+                proto.write(TimerProto.CURRENT_DURATION_MS, curDurationMs);
+            }
+            if (totalDurationMs != -1) {
+                proto.write(TimerProto.TOTAL_DURATION_MS, totalDurationMs);
+            }
             proto.end(token);
         }
     }
@@ -3114,71 +3185,104 @@
         final long idleTimeMs = counter.getIdleTimeCounter().getCountLocked(which);
         final long rxTimeMs = counter.getRxTimeCounter().getCountLocked(which);
         final long powerDrainMaMs = counter.getPowerCounter().getCountLocked(which);
+        // Battery real time
+        final long totalControllerActivityTimeMs
+            = computeBatteryRealtime(SystemClock.elapsedRealtime() * 1000, which) / 1000;
         long totalTxTimeMs = 0;
         for (LongCounter txState : counter.getTxTimeCounters()) {
             totalTxTimeMs += txState.getCountLocked(which);
         }
-
-        final long totalTimeMs = idleTimeMs + rxTimeMs + totalTxTimeMs;
+        final long sleepTimeMs
+            = totalControllerActivityTimeMs - (idleTimeMs + rxTimeMs + totalTxTimeMs);
 
         sb.setLength(0);
         sb.append(prefix);
-        sb.append("  ");
+        sb.append("     ");
+        sb.append(controllerName);
+        sb.append(" Sleep time:  ");
+        formatTimeMs(sb, sleepTimeMs);
+        sb.append("(");
+        sb.append(formatRatioLocked(sleepTimeMs, totalControllerActivityTimeMs));
+        sb.append(")");
+        pw.println(sb.toString());
+
+        sb.setLength(0);
+        sb.append(prefix);
+        sb.append("     ");
         sb.append(controllerName);
         sb.append(" Idle time:   ");
         formatTimeMs(sb, idleTimeMs);
         sb.append("(");
-        sb.append(formatRatioLocked(idleTimeMs, totalTimeMs));
+        sb.append(formatRatioLocked(idleTimeMs, totalControllerActivityTimeMs));
         sb.append(")");
         pw.println(sb.toString());
 
         sb.setLength(0);
         sb.append(prefix);
-        sb.append("  ");
+        sb.append("     ");
         sb.append(controllerName);
         sb.append(" Rx time:     ");
         formatTimeMs(sb, rxTimeMs);
         sb.append("(");
-        sb.append(formatRatioLocked(rxTimeMs, totalTimeMs));
+        sb.append(formatRatioLocked(rxTimeMs, totalControllerActivityTimeMs));
         sb.append(")");
         pw.println(sb.toString());
 
         sb.setLength(0);
         sb.append(prefix);
-        sb.append("  ");
+        sb.append("     ");
         sb.append(controllerName);
         sb.append(" Tx time:     ");
-        formatTimeMs(sb, totalTxTimeMs);
-        sb.append("(");
-        sb.append(formatRatioLocked(totalTxTimeMs, totalTimeMs));
-        sb.append(")");
-        pw.println(sb.toString());
 
-        final int numTxLvls = counter.getTxTimeCounters().length;
+        String [] powerLevel;
+        switch(controllerName) {
+            case "Cellular":
+                powerLevel = new String[] {
+                    "   less than 0dBm: ",
+                    "   0dBm to 8dBm: ",
+                    "   8dBm to 15dBm: ",
+                    "   15dBm to 20dBm: ",
+                    "   above 20dBm: "};
+                break;
+            default:
+                powerLevel = new String[] {"[0]", "[1]", "[2]", "[3]", "[4]"};
+                break;
+        }
+        final int numTxLvls = Math.min(counter.getTxTimeCounters().length, powerLevel.length);
         if (numTxLvls > 1) {
+            pw.println(sb.toString());
             for (int lvl = 0; lvl < numTxLvls; lvl++) {
                 final long txLvlTimeMs = counter.getTxTimeCounters()[lvl].getCountLocked(which);
                 sb.setLength(0);
                 sb.append(prefix);
-                sb.append("    [");
-                sb.append(lvl);
-                sb.append("] ");
+                sb.append("    ");
+                sb.append(powerLevel[lvl]);
+                sb.append(" ");
                 formatTimeMs(sb, txLvlTimeMs);
                 sb.append("(");
-                sb.append(formatRatioLocked(txLvlTimeMs, totalTxTimeMs));
+                sb.append(formatRatioLocked(txLvlTimeMs, totalControllerActivityTimeMs));
                 sb.append(")");
                 pw.println(sb.toString());
             }
+        } else {
+            final long txLvlTimeMs = counter.getTxTimeCounters()[0].getCountLocked(which);
+            formatTimeMs(sb, txLvlTimeMs);
+            sb.append("(");
+            sb.append(formatRatioLocked(txLvlTimeMs, totalControllerActivityTimeMs));
+            sb.append(")");
+            pw.println(sb.toString());
         }
 
-        sb.setLength(0);
-        sb.append(prefix);
-        sb.append("  ");
-        sb.append(controllerName);
-        sb.append(" Power drain: ").append(
+        if (powerDrainMaMs > 0) {
+            sb.setLength(0);
+            sb.append(prefix);
+            sb.append("     ");
+            sb.append(controllerName);
+            sb.append(" Battery drain: ").append(
                 BatteryStatsHelper.makemAh(powerDrainMaMs / (double) (1000*60*60)));
-        sb.append("mAh");
-        pw.println(sb.toString());
+            sb.append("mAh");
+            pw.println(sb.toString());
+        }
     }
 
     /**
@@ -3191,13 +3295,13 @@
     /**
      * Checkin server version of dump to produce more compact, computer-readable log.
      *
-     * NOTE: all times are expressed in 'ms'.
+     * NOTE: all times are expressed in microseconds, unless specified otherwise.
      */
     public final void dumpCheckinLocked(Context context, PrintWriter pw, int which, int reqUid,
             boolean wifiOnly) {
         final long rawUptime = SystemClock.uptimeMillis() * 1000;
-        final long rawRealtime = SystemClock.elapsedRealtime() * 1000;
-        final long rawRealtimeMs = (rawRealtime + 500) / 1000;
+        final long rawRealtimeMs = SystemClock.elapsedRealtime();
+        final long rawRealtime = rawRealtimeMs * 1000;
         final long batteryUptime = getBatteryUptime(rawUptime);
         final long whichBatteryUptime = computeBatteryUptime(rawUptime, which);
         final long whichBatteryRealtime = computeBatteryRealtime(rawRealtime, which);
@@ -3220,9 +3324,9 @@
                 rawRealtime, which);
         final int connChanges = getNumConnectivityChange(which);
         final long phoneOnTime = getPhoneOnTime(rawRealtime, which);
-        final long dischargeCount = getMahDischarge(which);
-        final long dischargeScreenOffCount = getMahDischargeScreenOff(which);
-        final long dischargeScreenDozeCount = getMahDischargeScreenDoze(which);
+        final long dischargeCount = getUahDischarge(which);
+        final long dischargeScreenOffCount = getUahDischargeScreenOff(which);
+        final long dischargeScreenDozeCount = getUahDischargeScreenDoze(which);
 
         final StringBuilder sb = new StringBuilder(128);
 
@@ -3460,9 +3564,9 @@
                     BatteryStatsHelper.makemAh(helper.getComputedPower()),
                     BatteryStatsHelper.makemAh(helper.getMinDrainedPower()),
                     BatteryStatsHelper.makemAh(helper.getMaxDrainedPower()));
+            int uid = 0;
             for (int i=0; i<sippers.size(); i++) {
                 final BatterySipper bs = sippers.get(i);
-                int uid = 0;
                 String label;
                 switch (bs.drainType) {
                     case IDLE:
@@ -3503,6 +3607,9 @@
                     case CAMERA:
                         label = "camera";
                         break;
+                    case MEMORY:
+                        label = "memory";
+                        break;
                     default:
                         label = "???";
                 }
@@ -3523,6 +3630,7 @@
             dumpLine(pw, 0 /* uid */, category, GLOBAL_CPU_FREQ_DATA, sb.toString());
         }
 
+        // Dump stats per UID.
         for (int iu = 0; iu < NU; iu++) {
             final int uid = uidStats.keyAt(iu);
             if (reqUid >= 0 && uid != reqUid) {
@@ -3686,7 +3794,7 @@
                 linePrefix = printWakeLockCheckin(sb, wl.getWakeTime(WAKE_TYPE_WINDOW),
                         rawRealtime, "w", which, linePrefix);
 
-                // Only log if we had at lease one wakelock...
+                // Only log if we had at least one wakelock...
                 if (sb.length() > 0) {
                     String name = wakelocks.keyAt(iw);
                     if (name.indexOf(',') >= 0) {
@@ -4020,7 +4128,7 @@
             pw.println(sb.toString());
         }
 
-        final long dischargeCount = getMahDischarge(which);
+        final long dischargeCount = getUahDischarge(which);
         if (dischargeCount >= 0) {
             sb.setLength(0);
             sb.append(prefix);
@@ -4030,7 +4138,7 @@
             pw.println(sb.toString());
         }
 
-        final long dischargeScreenOffCount = getMahDischargeScreenOff(which);
+        final long dischargeScreenOffCount = getUahDischargeScreenOff(which);
         if (dischargeScreenOffCount >= 0) {
             sb.setLength(0);
             sb.append(prefix);
@@ -4040,7 +4148,7 @@
             pw.println(sb.toString());
         }
 
-        final long dischargeScreenDozeCount = getMahDischargeScreenDoze(which);
+        final long dischargeScreenDozeCount = getUahDischargeScreenDoze(which);
         if (dischargeScreenDozeCount >= 0) {
             sb.setLength(0);
             sb.append(prefix);
@@ -4246,51 +4354,50 @@
             pw.println(sb.toString());
         }
 
+        pw.println("");
         pw.print(prefix);
-                pw.print("  Mobile total received: "); pw.print(formatBytesLocked(mobileRxTotalBytes));
-                pw.print(", sent: "); pw.print(formatBytesLocked(mobileTxTotalBytes));
-                pw.print(" (packets received "); pw.print(mobileRxTotalPackets);
-                pw.print(", sent "); pw.print(mobileTxTotalPackets); pw.println(")");
         sb.setLength(0);
         sb.append(prefix);
-        sb.append("  Phone signal levels:");
-        didOne = false;
-        for (int i=0; i<SignalStrength.NUM_SIGNAL_STRENGTH_BINS; i++) {
-            final long time = getPhoneSignalStrengthTime(i, rawRealtime, which);
-            if (time == 0) {
-                continue;
-            }
-            sb.append("\n    ");
-            sb.append(prefix);
-            didOne = true;
-            sb.append(SignalStrength.SIGNAL_STRENGTH_NAMES[i]);
-            sb.append(" ");
-            formatTimeMs(sb, time/1000);
-            sb.append("(");
-            sb.append(formatRatioLocked(time, whichBatteryRealtime));
-            sb.append(") ");
-            sb.append(getPhoneSignalStrengthCount(i, which));
-            sb.append("x");
-        }
-        if (!didOne) sb.append(" (no activity)");
+        sb.append("  CONNECTIVITY POWER SUMMARY START");
+        pw.println(sb.toString());
+
+        pw.print(prefix);
+        sb.setLength(0);
+        sb.append(prefix);
+        sb.append("  Logging duration for connectivity statistics: ");
+        formatTimeMs(sb, whichBatteryRealtime / 1000);
         pw.println(sb.toString());
 
         sb.setLength(0);
         sb.append(prefix);
-        sb.append("  Signal scanning time: ");
-        formatTimeMsNoSpace(sb, getPhoneSignalScanningTime(rawRealtime, which) / 1000);
+        sb.append("  Cellular Statistics:");
         pw.println(sb.toString());
 
+        pw.print(prefix);
+        sb.setLength(0);
+        sb.append(prefix);
+        sb.append("     Cellular kernel active time: ");
+        final long mobileActiveTime = getMobileRadioActiveTime(rawRealtime, which);
+        formatTimeMs(sb, mobileActiveTime / 1000);
+        sb.append("("); sb.append(formatRatioLocked(mobileActiveTime, whichBatteryRealtime));
+        sb.append(")");
+        pw.println(sb.toString());
+
+        pw.print("     Cellular data received: "); pw.println(formatBytesLocked(mobileRxTotalBytes));
+        pw.print("     Cellular data sent: "); pw.println(formatBytesLocked(mobileTxTotalBytes));
+        pw.print("     Cellular packets received: "); pw.println(mobileRxTotalPackets);
+        pw.print("     Cellular packets sent: "); pw.println(mobileTxTotalPackets);
+
         sb.setLength(0);
         sb.append(prefix);
-        sb.append("  Radio types:");
+        sb.append("     Cellular Radio Access Technology:");
         didOne = false;
         for (int i=0; i<NUM_DATA_CONNECTION_TYPES; i++) {
             final long time = getPhoneDataConnectionTime(i, rawRealtime, which);
             if (time == 0) {
                 continue;
             }
-            sb.append("\n    ");
+            sb.append("\n       ");
             sb.append(prefix);
             didOne = true;
             sb.append(DATA_CONNECTION_NAMES[i]);
@@ -4299,73 +4406,64 @@
             sb.append("(");
             sb.append(formatRatioLocked(time, whichBatteryRealtime));
             sb.append(") ");
-            sb.append(getPhoneDataConnectionCount(i, which));
-            sb.append("x");
         }
         if (!didOne) sb.append(" (no activity)");
         pw.println(sb.toString());
 
         sb.setLength(0);
         sb.append(prefix);
-        sb.append("  Mobile radio active time: ");
-        final long mobileActiveTime = getMobileRadioActiveTime(rawRealtime, which);
-        formatTimeMs(sb, mobileActiveTime / 1000);
-        sb.append("("); sb.append(formatRatioLocked(mobileActiveTime, whichBatteryRealtime));
-        sb.append(") "); sb.append(getMobileRadioActiveCount(which));
-        sb.append("x");
+        sb.append("     Cellular Rx signal strength (RSRP):");
+        final String[] cellularRxSignalStrengthDescription = new String[]{
+            "very poor (less than -128dBm): ",
+            "poor (-128dBm to -118dBm): ",
+            "moderate (-118dBm to -108dBm): ",
+            "good (-108dBm to -98dBm): ",
+            "great (greater than -98dBm): "};
+        didOne = false;
+        final int numCellularRxBins = Math.min(SignalStrength.NUM_SIGNAL_STRENGTH_BINS,
+            cellularRxSignalStrengthDescription.length);
+        for (int i=0; i<numCellularRxBins; i++) {
+            final long time = getPhoneSignalStrengthTime(i, rawRealtime, which);
+            if (time == 0) {
+                continue;
+            }
+            sb.append("\n       ");
+            sb.append(prefix);
+            didOne = true;
+            sb.append(cellularRxSignalStrengthDescription[i]);
+            sb.append(" ");
+            formatTimeMs(sb, time/1000);
+            sb.append("(");
+            sb.append(formatRatioLocked(time, whichBatteryRealtime));
+            sb.append(") ");
+        }
+        if (!didOne) sb.append(" (no activity)");
         pw.println(sb.toString());
 
-        final long mobileActiveUnknownTime = getMobileRadioActiveUnknownTime(which);
-        if (mobileActiveUnknownTime != 0) {
-            sb.setLength(0);
-            sb.append(prefix);
-            sb.append("  Mobile radio active unknown time: ");
-            formatTimeMs(sb, mobileActiveUnknownTime / 1000);
-            sb.append("(");
-            sb.append(formatRatioLocked(mobileActiveUnknownTime, whichBatteryRealtime));
-            sb.append(") "); sb.append(getMobileRadioActiveUnknownCount(which));
-            sb.append("x");
-            pw.println(sb.toString());
-        }
-
-        final long mobileActiveAdjustedTime = getMobileRadioActiveAdjustedTime(which);
-        if (mobileActiveAdjustedTime != 0) {
-            sb.setLength(0);
-            sb.append(prefix);
-            sb.append("  Mobile radio active adjusted time: ");
-            formatTimeMs(sb, mobileActiveAdjustedTime / 1000);
-            sb.append("(");
-            sb.append(formatRatioLocked(mobileActiveAdjustedTime, whichBatteryRealtime));
-            sb.append(")");
-            pw.println(sb.toString());
-        }
-
-        printControllerActivity(pw, sb, prefix, "Radio", getModemControllerActivity(), which);
+        printControllerActivity(pw, sb, prefix, "Cellular",
+            getModemControllerActivity(), which);
 
         pw.print(prefix);
-                pw.print("  Wi-Fi total received: "); pw.print(formatBytesLocked(wifiRxTotalBytes));
-                pw.print(", sent: "); pw.print(formatBytesLocked(wifiTxTotalBytes));
-                pw.print(" (packets received "); pw.print(wifiRxTotalPackets);
-                pw.print(", sent "); pw.print(wifiTxTotalPackets); pw.println(")");
         sb.setLength(0);
         sb.append(prefix);
-                sb.append("  Wifi on: "); formatTimeMs(sb, wifiOnTime / 1000);
-                sb.append("("); sb.append(formatRatioLocked(wifiOnTime, whichBatteryRealtime));
-                sb.append("), Wifi running: "); formatTimeMs(sb, wifiRunningTime / 1000);
-                sb.append("("); sb.append(formatRatioLocked(wifiRunningTime, whichBatteryRealtime));
-                sb.append(")");
+        sb.append("  Wifi Statistics:");
         pw.println(sb.toString());
 
+        pw.print("     Wifi data received: "); pw.println(formatBytesLocked(wifiRxTotalBytes));
+        pw.print("     Wifi data sent: "); pw.println(formatBytesLocked(wifiTxTotalBytes));
+        pw.print("     Wifi packets received: "); pw.println(wifiRxTotalPackets);
+        pw.print("     Wifi packets sent: "); pw.println(wifiTxTotalPackets);
+
         sb.setLength(0);
         sb.append(prefix);
-        sb.append("  Wifi states:");
+        sb.append("     Wifi states:");
         didOne = false;
         for (int i=0; i<NUM_WIFI_STATES; i++) {
             final long time = getWifiStateTime(i, rawRealtime, which);
             if (time == 0) {
                 continue;
             }
-            sb.append("\n    ");
+            sb.append("\n       ");
             didOne = true;
             sb.append(WIFI_STATE_NAMES[i]);
             sb.append(" ");
@@ -4373,22 +4471,20 @@
             sb.append("(");
             sb.append(formatRatioLocked(time, whichBatteryRealtime));
             sb.append(") ");
-            sb.append(getWifiStateCount(i, which));
-            sb.append("x");
         }
         if (!didOne) sb.append(" (no activity)");
         pw.println(sb.toString());
 
         sb.setLength(0);
         sb.append(prefix);
-        sb.append("  Wifi supplicant states:");
+        sb.append("     Wifi supplicant states:");
         didOne = false;
         for (int i=0; i<NUM_WIFI_SUPPL_STATES; i++) {
             final long time = getWifiSupplStateTime(i, rawRealtime, which);
             if (time == 0) {
                 continue;
             }
-            sb.append("\n    ");
+            sb.append("\n       ");
             didOne = true;
             sb.append(WIFI_SUPPL_STATE_NAMES[i]);
             sb.append(" ");
@@ -4396,17 +4492,23 @@
             sb.append("(");
             sb.append(formatRatioLocked(time, whichBatteryRealtime));
             sb.append(") ");
-            sb.append(getWifiSupplStateCount(i, which));
-            sb.append("x");
         }
         if (!didOne) sb.append(" (no activity)");
         pw.println(sb.toString());
 
         sb.setLength(0);
         sb.append(prefix);
-        sb.append("  Wifi signal levels:");
+        sb.append("     Wifi Rx signal strength (RSSI):");
+        final String[] wifiRxSignalStrengthDescription = new String[]{
+            "very poor (less than -88.75dBm): ",
+            "poor (-88.75 to -77.5dBm): ",
+            "moderate (-77.5dBm to -66.25dBm): ",
+            "good (-66.25dBm to -55dBm): ",
+            "great (greater than -55dBm): "};
         didOne = false;
-        for (int i=0; i<NUM_WIFI_SIGNAL_STRENGTH_BINS; i++) {
+        final int numWifiRxBins = Math.min(NUM_WIFI_SIGNAL_STRENGTH_BINS,
+            wifiRxSignalStrengthDescription.length);
+        for (int i=0; i<numWifiRxBins; i++) {
             final long time = getWifiSignalStrengthTime(i, rawRealtime, which);
             if (time == 0) {
                 continue;
@@ -4414,15 +4516,12 @@
             sb.append("\n    ");
             sb.append(prefix);
             didOne = true;
-            sb.append("level(");
-            sb.append(i);
-            sb.append(") ");
+            sb.append("     ");
+            sb.append(wifiRxSignalStrengthDescription[i]);
             formatTimeMs(sb, time/1000);
             sb.append("(");
             sb.append(formatRatioLocked(time, whichBatteryRealtime));
             sb.append(") ");
-            sb.append(getWifiSignalStrengthCount(i, which));
-            sb.append("x");
         }
         if (!didOne) sb.append(" (no activity)");
         pw.println(sb.toString());
@@ -4430,6 +4529,13 @@
         printControllerActivity(pw, sb, prefix, "WiFi", getWifiControllerActivity(), which);
 
         pw.print(prefix);
+        sb.setLength(0);
+        sb.append(prefix);
+        sb.append("  CONNECTIVITY POWER SUMMARY END");
+        pw.println(sb.toString());
+        pw.println("");
+
+        pw.print(prefix);
         pw.print("  Bluetooth total received: "); pw.print(formatBytesLocked(btRxTotalBytes));
         pw.print(", sent: "); pw.println(formatBytesLocked(btTxTotalBytes));
 
@@ -6038,6 +6144,60 @@
         return true;
     }
 
+    private static void dumpDurationSteps(ProtoOutputStream proto, long fieldId,
+            LevelStepTracker steps) {
+        if (steps == null) {
+            return;
+        }
+        int count = steps.mNumStepDurations;
+        for (int i = 0; i < count; ++i) {
+            long token = proto.start(fieldId);
+            proto.write(SystemProto.BatteryLevelStep.DURATION_MS, steps.getDurationAt(i));
+            proto.write(SystemProto.BatteryLevelStep.LEVEL, steps.getLevelAt(i));
+
+            final long initMode = steps.getInitModeAt(i);
+            final long modMode = steps.getModModeAt(i);
+
+            int ds = SystemProto.BatteryLevelStep.DS_MIXED;
+            if ((modMode & STEP_LEVEL_MODE_SCREEN_STATE) == 0) {
+                switch ((int) (initMode & STEP_LEVEL_MODE_SCREEN_STATE) + 1) {
+                    case Display.STATE_OFF:
+                        ds = SystemProto.BatteryLevelStep.DS_OFF;
+                        break;
+                    case Display.STATE_ON:
+                        ds = SystemProto.BatteryLevelStep.DS_ON;
+                        break;
+                    case Display.STATE_DOZE:
+                        ds = SystemProto.BatteryLevelStep.DS_DOZE;
+                        break;
+                    case Display.STATE_DOZE_SUSPEND:
+                        ds = SystemProto.BatteryLevelStep.DS_DOZE_SUSPEND;
+                        break;
+                    default:
+                        ds = SystemProto.BatteryLevelStep.DS_ERROR;
+                        break;
+                }
+            }
+            proto.write(SystemProto.BatteryLevelStep.DISPLAY_STATE, ds);
+
+            int psm = SystemProto.BatteryLevelStep.PSM_MIXED;
+            if ((modMode & STEP_LEVEL_MODE_POWER_SAVE) == 0) {
+                psm = (initMode & STEP_LEVEL_MODE_POWER_SAVE) != 0
+                    ? SystemProto.BatteryLevelStep.PSM_ON : SystemProto.BatteryLevelStep.PSM_OFF;
+            }
+            proto.write(SystemProto.BatteryLevelStep.POWER_SAVE_MODE, psm);
+
+            int im = SystemProto.BatteryLevelStep.IM_MIXED;
+            if ((modMode & STEP_LEVEL_MODE_DEVICE_IDLE) == 0) {
+                im = (initMode & STEP_LEVEL_MODE_DEVICE_IDLE) != 0
+                    ? SystemProto.BatteryLevelStep.IM_ON : SystemProto.BatteryLevelStep.IM_OFF;
+            }
+            proto.write(SystemProto.BatteryLevelStep.IDLE_MODE, im);
+
+            proto.end(token);
+        }
+    }
+
     public static final int DUMP_CHARGED_ONLY = 1<<1;
     public static final int DUMP_DAILY_ONLY = 1<<2;
     public static final int DUMP_HISTORY_ONLY = 1<<3;
@@ -6463,7 +6623,7 @@
         }
     }
 
-    /** Dump batterystats data to a proto. @hide */
+    /** Dump #STATS_SINCE_CHARGED batterystats data to a proto. @hide */
     public void dumpProtoLocked(Context context, FileDescriptor fd, List<ApplicationInfo> apps,
             int flags, long historyStart) {
         final ProtoOutputStream proto = new ProtoOutputStream(fd);
@@ -6484,11 +6644,802 @@
         }
 
         if ((flags & (DUMP_HISTORY_ONLY | DUMP_DAILY_ONLY)) == 0) {
-            // TODO: implement dumpProtoAppsLocked(proto, apps);
-            // TODO: implement dumpProtoSystemLocked(proto);
+            final BatteryStatsHelper helper = new BatteryStatsHelper(context, false,
+                    (flags & DUMP_DEVICE_WIFI_ONLY) != 0);
+            helper.create(this);
+            helper.refreshStats(STATS_SINCE_CHARGED, UserHandle.USER_ALL);
+
+            dumpProtoAppsLocked(proto, helper, apps);
+            dumpProtoSystemLocked(proto, helper);
         }
 
         proto.end(bToken);
         proto.flush();
     }
+
+    private void dumpProtoAppsLocked(ProtoOutputStream proto, BatteryStatsHelper helper,
+            List<ApplicationInfo> apps) {
+        final int which = STATS_SINCE_CHARGED;
+        final long rawUptimeUs = SystemClock.uptimeMillis() * 1000;
+        final long rawRealtimeMs = SystemClock.elapsedRealtime();
+        final long rawRealtimeUs = rawRealtimeMs * 1000;
+        final long batteryUptimeUs = getBatteryUptime(rawUptimeUs);
+
+        SparseArray<ArrayList<String>> aidToPackages = new SparseArray<>();
+        if (apps != null) {
+            for (int i = 0; i < apps.size(); ++i) {
+                ApplicationInfo ai = apps.get(i);
+                int aid = UserHandle.getAppId(ai.uid);
+                ArrayList<String> pkgs = aidToPackages.get(aid);
+                if (pkgs == null) {
+                    pkgs = new ArrayList<String>();
+                    aidToPackages.put(aid, pkgs);
+                }
+                pkgs.add(ai.packageName);
+            }
+        }
+
+        SparseArray<BatterySipper> uidToSipper = new SparseArray<>();
+        final List<BatterySipper> sippers = helper.getUsageList();
+        if (sippers != null) {
+            for (int i = 0; i < sippers.size(); ++i) {
+                final BatterySipper bs = sippers.get(i);
+                if (bs.drainType != BatterySipper.DrainType.APP) {
+                    // Others are handled by dumpProtoSystemLocked()
+                    continue;
+                }
+                uidToSipper.put(bs.uidObj.getUid(), bs);
+            }
+        }
+
+        SparseArray<? extends Uid> uidStats = getUidStats();
+        final int n = uidStats.size();
+        for (int iu = 0; iu < n; ++iu) {
+            final long uTkn = proto.start(BatteryStatsProto.UIDS);
+            final Uid u = uidStats.valueAt(iu);
+
+            final int uid = uidStats.keyAt(iu);
+            proto.write(UidProto.UID, uid);
+
+            // Print packages and apk stats (UID_DATA & APK_DATA)
+            ArrayList<String> pkgs = aidToPackages.get(UserHandle.getAppId(uid));
+            if (pkgs == null) {
+                pkgs = new ArrayList<String>();
+            }
+            final ArrayMap<String, ? extends BatteryStats.Uid.Pkg> packageStats =
+                    u.getPackageStats();
+            for (int ipkg = packageStats.size() - 1; ipkg >= 0; --ipkg) {
+                String pkg = packageStats.keyAt(ipkg);
+                final ArrayMap<String, ? extends  Uid.Pkg.Serv> serviceStats =
+                        packageStats.valueAt(ipkg).getServiceStats();
+                if (serviceStats.size() == 0) {
+                    // Due to the way ActivityManagerService logs wakeup alarms, some packages (for
+                    // example, "android") may be included in the packageStats that aren't part of
+                    // the UID. If they don't have any services, then they shouldn't be listed here.
+                    // These packages won't be a part in the pkgs List.
+                    continue;
+                }
+
+                final long pToken = proto.start(UidProto.PACKAGES);
+                proto.write(UidProto.Package.NAME, pkg);
+                // Remove from the packages list since we're logging it here.
+                pkgs.remove(pkg);
+
+                for (int isvc = serviceStats.size() - 1; isvc >= 0; --isvc) {
+                    final BatteryStats.Uid.Pkg.Serv ss = serviceStats.valueAt(isvc);
+                    long sToken = proto.start(UidProto.Package.SERVICES);
+
+                    proto.write(UidProto.Package.Service.NAME, serviceStats.keyAt(isvc));
+                    proto.write(UidProto.Package.Service.START_DURATION_MS,
+                            roundUsToMs(ss.getStartTime(batteryUptimeUs, which)));
+                    proto.write(UidProto.Package.Service.START_COUNT, ss.getStarts(which));
+                    proto.write(UidProto.Package.Service.LAUNCH_COUNT, ss.getLaunches(which));
+
+                    proto.end(sToken);
+                }
+                proto.end(pToken);
+            }
+            // Print any remaining packages that weren't in the packageStats map. pkgs is pulled
+            // from PackageManager data. Packages are only included in packageStats if there was
+            // specific data tracked for them (services and wakeup alarms, etc.).
+            for (String p : pkgs) {
+                final long pToken = proto.start(UidProto.PACKAGES);
+                proto.write(UidProto.Package.NAME, p);
+                proto.end(pToken);
+            }
+
+            // Total wakelock data (AGGREGATED_WAKELOCK_DATA)
+            if (u.getAggregatedPartialWakelockTimer() != null) {
+                final Timer timer = u.getAggregatedPartialWakelockTimer();
+                // Times are since reset (regardless of 'which')
+                final long totTimeMs = timer.getTotalDurationMsLocked(rawRealtimeMs);
+                final Timer bgTimer = timer.getSubTimer();
+                final long bgTimeMs = bgTimer != null
+                        ? bgTimer.getTotalDurationMsLocked(rawRealtimeMs) : 0;
+                final long awToken = proto.start(UidProto.AGGREGATED_WAKELOCK);
+                proto.write(UidProto.AggregatedWakelock.PARTIAL_DURATION_MS, totTimeMs);
+                proto.write(UidProto.AggregatedWakelock.BACKGROUND_PARTIAL_DURATION_MS, bgTimeMs);
+                proto.end(awToken);
+            }
+
+            // Audio (AUDIO_DATA)
+            dumpTimer(proto, UidProto.AUDIO, u.getAudioTurnedOnTimer(), rawRealtimeUs, which);
+
+            // Bluetooth Controller (BLUETOOTH_CONTROLLER_DATA)
+            dumpControllerActivityProto(proto, UidProto.BLUETOOTH_CONTROLLER,
+                    u.getBluetoothControllerActivity(), which);
+
+            // BLE scans (BLUETOOTH_MISC_DATA) (uses totalDurationMsLocked and MaxDurationMsLocked)
+            final Timer bleTimer = u.getBluetoothScanTimer();
+            if (bleTimer != null) {
+                final long bmToken = proto.start(UidProto.BLUETOOTH_MISC);
+
+                dumpTimer(proto, UidProto.BluetoothMisc.APPORTIONED_BLE_SCAN, bleTimer,
+                        rawRealtimeUs, which);
+                dumpTimer(proto, UidProto.BluetoothMisc.BACKGROUND_BLE_SCAN,
+                        u.getBluetoothScanBackgroundTimer(), rawRealtimeUs, which);
+                // Unoptimized scan timer. Unpooled and since reset (regardless of 'which').
+                dumpTimer(proto, UidProto.BluetoothMisc.UNOPTIMIZED_BLE_SCAN,
+                        u.getBluetoothUnoptimizedScanTimer(), rawRealtimeUs, which);
+                // Unoptimized bg scan timer. Unpooled and since reset (regardless of 'which').
+                dumpTimer(proto, UidProto.BluetoothMisc.BACKGROUND_UNOPTIMIZED_BLE_SCAN,
+                        u.getBluetoothUnoptimizedScanBackgroundTimer(), rawRealtimeUs, which);
+                // Result counters
+                proto.write(UidProto.BluetoothMisc.BLE_SCAN_RESULT_COUNT,
+                        u.getBluetoothScanResultCounter() != null
+                            ? u.getBluetoothScanResultCounter().getCountLocked(which) : 0);
+                proto.write(UidProto.BluetoothMisc.BACKGROUND_BLE_SCAN_RESULT_COUNT,
+                        u.getBluetoothScanResultBgCounter() != null
+                            ? u.getBluetoothScanResultBgCounter().getCountLocked(which) : 0);
+
+                proto.end(bmToken);
+            }
+
+            // Camera (CAMERA_DATA)
+            dumpTimer(proto, UidProto.CAMERA, u.getCameraTurnedOnTimer(), rawRealtimeUs, which);
+
+            // CPU stats (CPU_DATA & CPU_TIMES_AT_FREQ_DATA)
+            final long cpuToken = proto.start(UidProto.CPU);
+            proto.write(UidProto.Cpu.USER_DURATION_MS, roundUsToMs(u.getUserCpuTimeUs(which)));
+            proto.write(UidProto.Cpu.SYSTEM_DURATION_MS, roundUsToMs(u.getSystemCpuTimeUs(which)));
+
+            final long[] cpuFreqs = getCpuFreqs();
+            if (cpuFreqs != null) {
+                final long[] cpuFreqTimeMs = u.getCpuFreqTimes(which);
+                // If total cpuFreqTimes is null, then we don't need to check for
+                // screenOffCpuFreqTimes.
+                if (cpuFreqTimeMs != null && cpuFreqTimeMs.length == cpuFreqs.length) {
+                    long[] screenOffCpuFreqTimeMs = u.getScreenOffCpuFreqTimes(which);
+                    if (screenOffCpuFreqTimeMs == null) {
+                        screenOffCpuFreqTimeMs = new long[cpuFreqTimeMs.length];
+                    }
+                    for (int ic = 0; ic < cpuFreqTimeMs.length; ++ic) {
+                        long cToken = proto.start(UidProto.Cpu.BY_FREQUENCY);
+                        proto.write(UidProto.Cpu.ByFrequency.FREQUENCY_INDEX, ic + 1);
+                        proto.write(UidProto.Cpu.ByFrequency.TOTAL_DURATION_MS,
+                                cpuFreqTimeMs[ic]);
+                        proto.write(UidProto.Cpu.ByFrequency.SCREEN_OFF_DURATION_MS,
+                                screenOffCpuFreqTimeMs[ic]);
+                        proto.end(cToken);
+                    }
+                }
+            }
+            proto.end(cpuToken);
+
+            // Flashlight (FLASHLIGHT_DATA)
+            dumpTimer(proto, UidProto.FLASHLIGHT, u.getFlashlightTurnedOnTimer(),
+                    rawRealtimeUs, which);
+
+            // Foreground activity (FOREGROUND_ACTIVITY_DATA)
+            dumpTimer(proto, UidProto.FOREGROUND_ACTIVITY, u.getForegroundActivityTimer(),
+                    rawRealtimeUs, which);
+
+            // Foreground service (FOREGROUND_SERVICE_DATA)
+            dumpTimer(proto, UidProto.FOREGROUND_SERVICE, u.getForegroundServiceTimer(),
+                    rawRealtimeUs, which);
+
+            // Job completion (JOB_COMPLETION_DATA)
+            final ArrayMap<String, SparseIntArray> completions = u.getJobCompletionStats();
+            final int[] reasons = new int[]{
+                JobParameters.REASON_CANCELED,
+                JobParameters.REASON_CONSTRAINTS_NOT_SATISFIED,
+                JobParameters.REASON_PREEMPT,
+                JobParameters.REASON_TIMEOUT,
+                JobParameters.REASON_DEVICE_IDLE,
+            };
+            for (int ic = 0; ic < completions.size(); ++ic) {
+                SparseIntArray types = completions.valueAt(ic);
+                if (types != null) {
+                    final long jcToken = proto.start(UidProto.JOB_COMPLETION);
+
+                    proto.write(UidProto.JobCompletion.NAME, completions.keyAt(ic));
+
+                    for (int r : reasons) {
+                        long rToken = proto.start(UidProto.JobCompletion.REASON_COUNT);
+                        proto.write(UidProto.JobCompletion.ReasonCount.NAME, r);
+                        proto.write(UidProto.JobCompletion.ReasonCount.COUNT, types.get(r, 0));
+                        proto.end(rToken);
+                    }
+
+                    proto.end(jcToken);
+                }
+            }
+
+            // Scheduled jobs (JOB_DATA)
+            final ArrayMap<String, ? extends Timer> jobs = u.getJobStats();
+            for (int ij = jobs.size() - 1; ij >= 0; --ij) {
+                final Timer timer = jobs.valueAt(ij);
+                final Timer bgTimer = timer.getSubTimer();
+                final long jToken = proto.start(UidProto.JOBS);
+
+                proto.write(UidProto.Job.NAME, jobs.keyAt(ij));
+                // Background uses totalDurationMsLocked, while total uses totalTimeLocked
+                dumpTimer(proto, UidProto.Job.TOTAL, timer, rawRealtimeUs, which);
+                dumpTimer(proto, UidProto.Job.BACKGROUND, bgTimer, rawRealtimeUs, which);
+
+                proto.end(jToken);
+            }
+
+            // Modem Controller (MODEM_CONTROLLER_DATA)
+            dumpControllerActivityProto(proto, UidProto.MODEM_CONTROLLER,
+                    u.getModemControllerActivity(), which);
+
+            // Network stats (NETWORK_DATA)
+            final long nToken = proto.start(UidProto.NETWORK);
+            proto.write(UidProto.Network.MOBILE_BYTES_RX,
+                    u.getNetworkActivityBytes(NETWORK_MOBILE_RX_DATA, which));
+            proto.write(UidProto.Network.MOBILE_BYTES_TX,
+                    u.getNetworkActivityBytes(NETWORK_MOBILE_TX_DATA, which));
+            proto.write(UidProto.Network.WIFI_BYTES_RX,
+                    u.getNetworkActivityBytes(NETWORK_WIFI_RX_DATA, which));
+            proto.write(UidProto.Network.WIFI_BYTES_TX,
+                    u.getNetworkActivityBytes(NETWORK_WIFI_TX_DATA, which));
+            proto.write(UidProto.Network.BT_BYTES_RX,
+                    u.getNetworkActivityBytes(NETWORK_BT_RX_DATA, which));
+            proto.write(UidProto.Network.BT_BYTES_TX,
+                    u.getNetworkActivityBytes(NETWORK_BT_TX_DATA, which));
+            proto.write(UidProto.Network.MOBILE_PACKETS_RX,
+                    u.getNetworkActivityPackets(NETWORK_MOBILE_RX_DATA, which));
+            proto.write(UidProto.Network.MOBILE_PACKETS_TX,
+                    u.getNetworkActivityPackets(NETWORK_MOBILE_TX_DATA, which));
+            proto.write(UidProto.Network.WIFI_PACKETS_RX,
+                    u.getNetworkActivityPackets(NETWORK_WIFI_RX_DATA, which));
+            proto.write(UidProto.Network.WIFI_PACKETS_TX,
+                    u.getNetworkActivityPackets(NETWORK_WIFI_TX_DATA, which));
+            proto.write(UidProto.Network.MOBILE_ACTIVE_DURATION_MS,
+                    roundUsToMs(u.getMobileRadioActiveTime(which)));
+            proto.write(UidProto.Network.MOBILE_ACTIVE_COUNT,
+                    u.getMobileRadioActiveCount(which));
+            proto.write(UidProto.Network.MOBILE_WAKEUP_COUNT,
+                    u.getMobileRadioApWakeupCount(which));
+            proto.write(UidProto.Network.WIFI_WAKEUP_COUNT,
+                    u.getWifiRadioApWakeupCount(which));
+            proto.write(UidProto.Network.MOBILE_BYTES_BG_RX,
+                    u.getNetworkActivityBytes(NETWORK_MOBILE_BG_RX_DATA, which));
+            proto.write(UidProto.Network.MOBILE_BYTES_BG_TX,
+                    u.getNetworkActivityBytes(NETWORK_MOBILE_BG_TX_DATA, which));
+            proto.write(UidProto.Network.WIFI_BYTES_BG_RX,
+                    u.getNetworkActivityBytes(NETWORK_WIFI_BG_RX_DATA, which));
+            proto.write(UidProto.Network.WIFI_BYTES_BG_TX,
+                    u.getNetworkActivityBytes(NETWORK_WIFI_BG_TX_DATA, which));
+            proto.write(UidProto.Network.MOBILE_PACKETS_BG_RX,
+                    u.getNetworkActivityPackets(NETWORK_MOBILE_BG_RX_DATA, which));
+            proto.write(UidProto.Network.MOBILE_PACKETS_BG_TX,
+                    u.getNetworkActivityPackets(NETWORK_MOBILE_BG_TX_DATA, which));
+            proto.write(UidProto.Network.WIFI_PACKETS_BG_RX,
+                    u.getNetworkActivityPackets(NETWORK_WIFI_BG_RX_DATA, which));
+            proto.write(UidProto.Network.WIFI_PACKETS_BG_TX,
+                    u.getNetworkActivityPackets(NETWORK_WIFI_BG_TX_DATA, which));
+            proto.end(nToken);
+
+            // Power use item (POWER_USE_ITEM_DATA)
+            BatterySipper bs = uidToSipper.get(uid);
+            if (bs != null) {
+                final long bsToken = proto.start(UidProto.POWER_USE_ITEM);
+                proto.write(UidProto.PowerUseItem.COMPUTED_POWER_MAH, bs.totalPowerMah);
+                proto.write(UidProto.PowerUseItem.SHOULD_HIDE, bs.shouldHide);
+                proto.write(UidProto.PowerUseItem.SCREEN_POWER_MAH, bs.screenPowerMah);
+                proto.write(UidProto.PowerUseItem.PROPORTIONAL_SMEAR_MAH,
+                        bs.proportionalSmearMah);
+                proto.end(bsToken);
+            }
+
+            // Processes (PROCESS_DATA)
+            final ArrayMap<String, ? extends BatteryStats.Uid.Proc> processStats =
+                    u.getProcessStats();
+            for (int ipr = processStats.size() - 1; ipr >= 0; --ipr) {
+                final Uid.Proc ps = processStats.valueAt(ipr);
+                final long prToken = proto.start(UidProto.PROCESS);
+
+                proto.write(UidProto.Process.NAME, processStats.keyAt(ipr));
+                proto.write(UidProto.Process.USER_DURATION_MS, ps.getUserTime(which));
+                proto.write(UidProto.Process.SYSTEM_DURATION_MS, ps.getSystemTime(which));
+                proto.write(UidProto.Process.FOREGROUND_DURATION_MS, ps.getForegroundTime(which));
+                proto.write(UidProto.Process.START_COUNT, ps.getStarts(which));
+                proto.write(UidProto.Process.ANR_COUNT, ps.getNumAnrs(which));
+                proto.write(UidProto.Process.CRASH_COUNT, ps.getNumCrashes(which));
+
+                proto.end(prToken);
+            }
+
+            // Sensors (SENSOR_DATA)
+            final SparseArray<? extends BatteryStats.Uid.Sensor> sensors = u.getSensorStats();
+            for (int ise = 0; ise < sensors.size(); ++ise) {
+                final Uid.Sensor se = sensors.valueAt(ise);
+                final Timer timer = se.getSensorTime();
+                if (timer == null) {
+                    continue;
+                }
+                final Timer bgTimer = se.getSensorBackgroundTime();
+                final int sensorNumber = sensors.keyAt(ise);
+                final long seToken = proto.start(UidProto.SENSORS);
+
+                proto.write(UidProto.Sensor.ID, sensorNumber);
+                // Background uses totalDurationMsLocked, while total uses totalTimeLocked
+                dumpTimer(proto, UidProto.Sensor.APPORTIONED, timer, rawRealtimeUs, which);
+                dumpTimer(proto, UidProto.Sensor.BACKGROUND, bgTimer, rawRealtimeUs, which);
+
+                proto.end(seToken);
+            }
+
+            // State times (STATE_TIME_DATA)
+            for (int ips = 0; ips < Uid.NUM_PROCESS_STATE; ++ips) {
+                long durMs = roundUsToMs(u.getProcessStateTime(ips, rawRealtimeUs, which));
+                if (durMs == 0) {
+                    continue;
+                }
+                final long stToken = proto.start(UidProto.STATES);
+                proto.write(UidProto.StateTime.STATE, ips);
+                proto.write(UidProto.StateTime.DURATION_MS, durMs);
+                proto.end(stToken);
+            }
+
+            // Syncs (SYNC_DATA)
+            final ArrayMap<String, ? extends Timer> syncs = u.getSyncStats();
+            for (int isy = syncs.size() - 1; isy >= 0; --isy) {
+                final Timer timer = syncs.valueAt(isy);
+                final Timer bgTimer = timer.getSubTimer();
+                final long syToken = proto.start(UidProto.SYNCS);
+
+                proto.write(UidProto.Sync.NAME, syncs.keyAt(isy));
+                // Background uses totalDurationMsLocked, while total uses totalTimeLocked
+                dumpTimer(proto, UidProto.Sync.TOTAL, timer, rawRealtimeUs, which);
+                dumpTimer(proto, UidProto.Sync.BACKGROUND, bgTimer, rawRealtimeUs, which);
+
+                proto.end(syToken);
+            }
+
+            // User activity (USER_ACTIVITY_DATA)
+            if (u.hasUserActivity()) {
+                for (int i = 0; i < Uid.NUM_USER_ACTIVITY_TYPES; ++i) {
+                    int val = u.getUserActivityCount(i, which);
+                    if (val != 0) {
+                        final long uaToken = proto.start(UidProto.USER_ACTIVITY);
+                        proto.write(UidProto.UserActivity.NAME, i);
+                        proto.write(UidProto.UserActivity.COUNT, val);
+                        proto.end(uaToken);
+                    }
+                }
+            }
+
+            // Vibrator (VIBRATOR_DATA)
+            dumpTimer(proto, UidProto.VIBRATOR, u.getVibratorOnTimer(), rawRealtimeUs, which);
+
+            // Video (VIDEO_DATA)
+            dumpTimer(proto, UidProto.VIDEO, u.getVideoTurnedOnTimer(), rawRealtimeUs, which);
+
+            // Wakelocks (WAKELOCK_DATA)
+            final ArrayMap<String, ? extends Uid.Wakelock> wakelocks = u.getWakelockStats();
+            for (int iw = wakelocks.size() - 1; iw >= 0; --iw) {
+                final Uid.Wakelock wl = wakelocks.valueAt(iw);
+                final long wToken = proto.start(UidProto.WAKELOCKS);
+                proto.write(UidProto.Wakelock.NAME, wakelocks.keyAt(iw));
+                dumpTimer(proto, UidProto.Wakelock.FULL, wl.getWakeTime(WAKE_TYPE_FULL),
+                        rawRealtimeUs, which);
+                final Timer pTimer = wl.getWakeTime(WAKE_TYPE_PARTIAL);
+                if (pTimer != null) {
+                    dumpTimer(proto, UidProto.Wakelock.PARTIAL, pTimer, rawRealtimeUs, which);
+                    dumpTimer(proto, UidProto.Wakelock.BACKGROUND_PARTIAL, pTimer.getSubTimer(),
+                            rawRealtimeUs, which);
+                }
+                dumpTimer(proto, UidProto.Wakelock.WINDOW, wl.getWakeTime(WAKE_TYPE_WINDOW),
+                        rawRealtimeUs, which);
+                proto.end(wToken);
+            }
+
+            // Wakeup alarms (WAKEUP_ALARM_DATA)
+            for (int ipkg = packageStats.size() - 1; ipkg >= 0; --ipkg) {
+                final Uid.Pkg ps = packageStats.valueAt(ipkg);
+                final ArrayMap<String, ? extends Counter> alarms = ps.getWakeupAlarmStats();
+                for (int iwa = alarms.size() - 1; iwa >= 0; --iwa) {
+                    final long waToken = proto.start(UidProto.WAKEUP_ALARM);
+                    proto.write(UidProto.WakeupAlarm.NAME, alarms.keyAt(iwa));
+                    proto.write(UidProto.WakeupAlarm.COUNT,
+                            alarms.valueAt(iwa).getCountLocked(which));
+                    proto.end(waToken);
+                }
+            }
+
+            // Wifi Controller (WIFI_CONTROLLER_DATA)
+            dumpControllerActivityProto(proto, UidProto.WIFI_CONTROLLER,
+                    u.getWifiControllerActivity(), which);
+
+            // Wifi data (WIFI_DATA)
+            final long wToken = proto.start(UidProto.WIFI);
+            proto.write(UidProto.Wifi.FULL_WIFI_LOCK_DURATION_MS,
+                    roundUsToMs(u.getFullWifiLockTime(rawRealtimeUs, which)));
+            dumpTimer(proto, UidProto.Wifi.APPORTIONED_SCAN, u.getWifiScanTimer(),
+                    rawRealtimeUs, which);
+            proto.write(UidProto.Wifi.RUNNING_DURATION_MS,
+                    roundUsToMs(u.getWifiRunningTime(rawRealtimeUs, which)));
+            dumpTimer(proto, UidProto.Wifi.BACKGROUND_SCAN, u.getWifiScanBackgroundTimer(),
+                    rawRealtimeUs, which);
+            proto.end(wToken);
+
+            proto.end(uTkn);
+        }
+    }
+
+    private void dumpProtoSystemLocked(ProtoOutputStream proto, BatteryStatsHelper helper) {
+        final long sToken = proto.start(BatteryStatsProto.SYSTEM);
+        final long rawUptimeUs = SystemClock.uptimeMillis() * 1000;
+        final long rawRealtimeMs = SystemClock.elapsedRealtime();
+        final long rawRealtimeUs = rawRealtimeMs * 1000;
+        final int which = STATS_SINCE_CHARGED;
+
+        // Battery data (BATTERY_DATA)
+        final long bToken = proto.start(SystemProto.BATTERY);
+        proto.write(SystemProto.Battery.START_CLOCK_TIME_MS, getStartClockTime());
+        proto.write(SystemProto.Battery.START_COUNT, getStartCount());
+        proto.write(SystemProto.Battery.TOTAL_REALTIME_MS,
+                computeRealtime(rawRealtimeUs, which) / 1000);
+        proto.write(SystemProto.Battery.TOTAL_UPTIME_MS,
+                computeUptime(rawUptimeUs, which) / 1000);
+        proto.write(SystemProto.Battery.BATTERY_REALTIME_MS,
+                computeBatteryRealtime(rawRealtimeUs, which) / 1000);
+        proto.write(SystemProto.Battery.BATTERY_UPTIME_MS,
+                computeBatteryUptime(rawUptimeUs, which) / 1000);
+        proto.write(SystemProto.Battery.SCREEN_OFF_REALTIME_MS,
+                computeBatteryScreenOffRealtime(rawRealtimeUs, which) / 1000);
+        proto.write(SystemProto.Battery.SCREEN_OFF_UPTIME_MS,
+                computeBatteryScreenOffUptime(rawUptimeUs, which) / 1000);
+        proto.write(SystemProto.Battery.SCREEN_DOZE_DURATION_MS,
+                getScreenDozeTime(rawRealtimeUs, which) / 1000);
+        proto.write(SystemProto.Battery.ESTIMATED_BATTERY_CAPACITY_MAH,
+                getEstimatedBatteryCapacity());
+        proto.write(SystemProto.Battery.MIN_LEARNED_BATTERY_CAPACITY_UAH,
+                getMinLearnedBatteryCapacity());
+        proto.write(SystemProto.Battery.MAX_LEARNED_BATTERY_CAPACITY_UAH,
+                getMaxLearnedBatteryCapacity());
+        proto.end(bToken);
+
+        // Battery discharge (BATTERY_DISCHARGE_DATA)
+        final long bdToken = proto.start(SystemProto.BATTERY_DISCHARGE);
+        proto.write(SystemProto.BatteryDischarge.LOWER_BOUND_SINCE_CHARGE,
+                getLowDischargeAmountSinceCharge());
+        proto.write(SystemProto.BatteryDischarge.UPPER_BOUND_SINCE_CHARGE,
+                getHighDischargeAmountSinceCharge());
+        proto.write(SystemProto.BatteryDischarge.SCREEN_ON_SINCE_CHARGE,
+                getDischargeAmountScreenOnSinceCharge());
+        proto.write(SystemProto.BatteryDischarge.SCREEN_OFF_SINCE_CHARGE,
+                getDischargeAmountScreenOffSinceCharge());
+        proto.write(SystemProto.BatteryDischarge.SCREEN_DOZE_SINCE_CHARGE,
+                getDischargeAmountScreenDozeSinceCharge());
+        proto.write(SystemProto.BatteryDischarge.TOTAL_MAH,
+                getUahDischarge(which) / 1000);
+        proto.write(SystemProto.BatteryDischarge.TOTAL_MAH_SCREEN_OFF,
+                getUahDischargeScreenOff(which) / 1000);
+        proto.write(SystemProto.BatteryDischarge.TOTAL_MAH_SCREEN_DOZE,
+                getUahDischargeScreenDoze(which) / 1000);
+        proto.end(bdToken);
+
+        // Time remaining
+        long timeRemainingUs = computeChargeTimeRemaining(rawRealtimeUs);
+        // These are part of a oneof, so we should only set one of them.
+        if (timeRemainingUs >= 0) {
+            // Charge time remaining (CHARGE_TIME_REMAIN_DATA)
+            proto.write(SystemProto.CHARGE_TIME_REMAINING_MS, timeRemainingUs / 1000);
+        } else {
+            timeRemainingUs = computeBatteryTimeRemaining(rawRealtimeUs);
+            // Discharge time remaining (DISCHARGE_TIME_REMAIN_DATA)
+            if (timeRemainingUs >= 0) {
+                proto.write(SystemProto.DISCHARGE_TIME_REMAINING_MS, timeRemainingUs / 1000);
+            } else {
+                proto.write(SystemProto.DISCHARGE_TIME_REMAINING_MS, -1);
+            }
+        }
+
+        // Charge step (CHARGE_STEP_DATA)
+        dumpDurationSteps(proto, SystemProto.CHARGE_STEP, getChargeLevelStepTracker());
+
+        // Phone data connection (DATA_CONNECTION_TIME_DATA and DATA_CONNECTION_COUNT_DATA)
+        for (int i = 0; i < NUM_DATA_CONNECTION_TYPES; ++i) {
+            final long pdcToken = proto.start(SystemProto.DATA_CONNECTION);
+            proto.write(SystemProto.DataConnection.NAME, i);
+            dumpTimer(proto, SystemProto.DataConnection.TOTAL, getPhoneDataConnectionTimer(i),
+                    rawRealtimeUs, which);
+            proto.end(pdcToken);
+        }
+
+        // Discharge step (DISCHARGE_STEP_DATA)
+        dumpDurationSteps(proto, SystemProto.DISCHARGE_STEP, getDischargeLevelStepTracker());
+
+        // CPU frequencies (GLOBAL_CPU_FREQ_DATA)
+        final long[] cpuFreqs = getCpuFreqs();
+        if (cpuFreqs != null) {
+            for (long i : cpuFreqs) {
+                proto.write(SystemProto.CPU_FREQUENCY, i);
+            }
+        }
+
+        // Bluetooth controller (GLOBAL_BLUETOOTH_CONTROLLER_DATA)
+        dumpControllerActivityProto(proto, SystemProto.GLOBAL_BLUETOOTH_CONTROLLER,
+                getBluetoothControllerActivity(), which);
+
+        // Modem controller (GLOBAL_MODEM_CONTROLLER_DATA)
+        dumpControllerActivityProto(proto, SystemProto.GLOBAL_MODEM_CONTROLLER,
+                getModemControllerActivity(), which);
+
+        // Global network data (GLOBAL_NETWORK_DATA)
+        final long gnToken = proto.start(SystemProto.GLOBAL_NETWORK);
+        proto.write(SystemProto.GlobalNetwork.MOBILE_BYTES_RX,
+                getNetworkActivityBytes(NETWORK_MOBILE_RX_DATA, which));
+        proto.write(SystemProto.GlobalNetwork.MOBILE_BYTES_TX,
+                getNetworkActivityBytes(NETWORK_MOBILE_TX_DATA, which));
+        proto.write(SystemProto.GlobalNetwork.MOBILE_PACKETS_RX,
+                getNetworkActivityPackets(NETWORK_MOBILE_RX_DATA, which));
+        proto.write(SystemProto.GlobalNetwork.MOBILE_PACKETS_TX,
+                getNetworkActivityPackets(NETWORK_MOBILE_TX_DATA, which));
+        proto.write(SystemProto.GlobalNetwork.WIFI_BYTES_RX,
+                getNetworkActivityBytes(NETWORK_WIFI_RX_DATA, which));
+        proto.write(SystemProto.GlobalNetwork.WIFI_BYTES_TX,
+                getNetworkActivityBytes(NETWORK_WIFI_TX_DATA, which));
+        proto.write(SystemProto.GlobalNetwork.WIFI_PACKETS_RX,
+                getNetworkActivityPackets(NETWORK_WIFI_RX_DATA, which));
+        proto.write(SystemProto.GlobalNetwork.WIFI_PACKETS_TX,
+                getNetworkActivityPackets(NETWORK_WIFI_TX_DATA, which));
+        proto.write(SystemProto.GlobalNetwork.BT_BYTES_RX,
+                getNetworkActivityBytes(NETWORK_BT_RX_DATA, which));
+        proto.write(SystemProto.GlobalNetwork.BT_BYTES_TX,
+                getNetworkActivityBytes(NETWORK_BT_TX_DATA, which));
+        proto.end(gnToken);
+
+        // Wifi controller (GLOBAL_WIFI_CONTROLLER_DATA)
+        dumpControllerActivityProto(proto, SystemProto.GLOBAL_WIFI_CONTROLLER,
+                getWifiControllerActivity(), which);
+
+
+        // Global wifi (GLOBAL_WIFI_DATA)
+        final long gwToken = proto.start(SystemProto.GLOBAL_WIFI);
+        proto.write(SystemProto.GlobalWifi.ON_DURATION_MS,
+                getWifiOnTime(rawRealtimeUs, which) / 1000);
+        proto.write(SystemProto.GlobalWifi.RUNNING_DURATION_MS,
+                getGlobalWifiRunningTime(rawRealtimeUs, which) / 1000);
+        proto.end(gwToken);
+
+        // Kernel wakelock (KERNEL_WAKELOCK_DATA)
+        final Map<String, ? extends Timer> kernelWakelocks = getKernelWakelockStats();
+        for (Map.Entry<String, ? extends Timer> ent : kernelWakelocks.entrySet()) {
+            final long kwToken = proto.start(SystemProto.KERNEL_WAKELOCK);
+            proto.write(SystemProto.KernelWakelock.NAME, ent.getKey());
+            dumpTimer(proto, SystemProto.KernelWakelock.TOTAL, ent.getValue(),
+                    rawRealtimeUs, which);
+            proto.end(kwToken);
+        }
+
+        // Misc (MISC_DATA)
+        // Calculate wakelock times across all uids.
+        long fullWakeLockTimeTotalUs = 0;
+        long partialWakeLockTimeTotalUs = 0;
+
+        final SparseArray<? extends Uid> uidStats = getUidStats();
+        for (int iu = 0; iu < uidStats.size(); iu++) {
+            final Uid u = uidStats.valueAt(iu);
+
+            final ArrayMap<String, ? extends BatteryStats.Uid.Wakelock> wakelocks =
+                    u.getWakelockStats();
+            for (int iw = wakelocks.size() - 1; iw >= 0; --iw) {
+                final Uid.Wakelock wl = wakelocks.valueAt(iw);
+
+                final Timer fullWakeTimer = wl.getWakeTime(WAKE_TYPE_FULL);
+                if (fullWakeTimer != null) {
+                    fullWakeLockTimeTotalUs += fullWakeTimer.getTotalTimeLocked(rawRealtimeUs,
+                            which);
+                }
+
+                final Timer partialWakeTimer = wl.getWakeTime(WAKE_TYPE_PARTIAL);
+                if (partialWakeTimer != null) {
+                    partialWakeLockTimeTotalUs += partialWakeTimer.getTotalTimeLocked(
+                        rawRealtimeUs, which);
+                }
+            }
+        }
+        final long mToken = proto.start(SystemProto.MISC);
+        proto.write(SystemProto.Misc.SCREEN_ON_DURATION_MS,
+                getScreenOnTime(rawRealtimeUs, which) / 1000);
+        proto.write(SystemProto.Misc.PHONE_ON_DURATION_MS,
+                getPhoneOnTime(rawRealtimeUs, which) / 1000);
+        proto.write(SystemProto.Misc.FULL_WAKELOCK_TOTAL_DURATION_MS,
+                fullWakeLockTimeTotalUs / 1000);
+        proto.write(SystemProto.Misc.PARTIAL_WAKELOCK_TOTAL_DURATION_MS,
+                partialWakeLockTimeTotalUs / 1000);
+        proto.write(SystemProto.Misc.MOBILE_RADIO_ACTIVE_DURATION_MS,
+                getMobileRadioActiveTime(rawRealtimeUs, which) / 1000);
+        proto.write(SystemProto.Misc.MOBILE_RADIO_ACTIVE_ADJUSTED_TIME_MS,
+                getMobileRadioActiveAdjustedTime(which) / 1000);
+        proto.write(SystemProto.Misc.MOBILE_RADIO_ACTIVE_COUNT,
+                getMobileRadioActiveCount(which));
+        proto.write(SystemProto.Misc.MOBILE_RADIO_ACTIVE_UNKNOWN_DURATION_MS,
+                getMobileRadioActiveUnknownTime(which) / 1000);
+        proto.write(SystemProto.Misc.INTERACTIVE_DURATION_MS,
+                getInteractiveTime(rawRealtimeUs, which) / 1000);
+        proto.write(SystemProto.Misc.BATTERY_SAVER_MODE_ENABLED_DURATION_MS,
+                getPowerSaveModeEnabledTime(rawRealtimeUs, which) / 1000);
+        proto.write(SystemProto.Misc.NUM_CONNECTIVITY_CHANGES,
+                getNumConnectivityChange(which));
+        proto.write(SystemProto.Misc.DEEP_DOZE_ENABLED_DURATION_MS,
+                getDeviceIdleModeTime(DEVICE_IDLE_MODE_DEEP, rawRealtimeUs, which) / 1000);
+        proto.write(SystemProto.Misc.DEEP_DOZE_COUNT,
+                getDeviceIdleModeCount(DEVICE_IDLE_MODE_DEEP, which));
+        proto.write(SystemProto.Misc.DEEP_DOZE_IDLING_DURATION_MS,
+                getDeviceIdlingTime(DEVICE_IDLE_MODE_DEEP, rawRealtimeUs, which) / 1000);
+        proto.write(SystemProto.Misc.DEEP_DOZE_IDLING_COUNT,
+                getDeviceIdlingCount(DEVICE_IDLE_MODE_DEEP, which));
+        proto.write(SystemProto.Misc.LONGEST_DEEP_DOZE_DURATION_MS,
+                getLongestDeviceIdleModeTime(DEVICE_IDLE_MODE_DEEP));
+        proto.write(SystemProto.Misc.LIGHT_DOZE_ENABLED_DURATION_MS,
+                getDeviceIdleModeTime(DEVICE_IDLE_MODE_LIGHT, rawRealtimeUs, which) / 1000);
+        proto.write(SystemProto.Misc.LIGHT_DOZE_COUNT,
+                getDeviceIdleModeCount(DEVICE_IDLE_MODE_LIGHT, which));
+        proto.write(SystemProto.Misc.LIGHT_DOZE_IDLING_DURATION_MS,
+                getDeviceIdlingTime(DEVICE_IDLE_MODE_LIGHT, rawRealtimeUs, which) / 1000);
+        proto.write(SystemProto.Misc.LIGHT_DOZE_IDLING_COUNT,
+                getDeviceIdlingCount(DEVICE_IDLE_MODE_LIGHT, which));
+        proto.write(SystemProto.Misc.LONGEST_LIGHT_DOZE_DURATION_MS,
+                getLongestDeviceIdleModeTime(DEVICE_IDLE_MODE_LIGHT));
+        proto.end(mToken);
+
+        // Power use item (POWER_USE_ITEM_DATA)
+        final List<BatterySipper> sippers = helper.getUsageList();
+        if (sippers != null) {
+            for (int i = 0; i < sippers.size(); ++i) {
+                final BatterySipper bs = sippers.get(i);
+                int n = SystemProto.PowerUseItem.UNKNOWN_SIPPER;
+                int uid = 0;
+                switch (bs.drainType) {
+                    case IDLE:
+                        n = SystemProto.PowerUseItem.IDLE;
+                        break;
+                    case CELL:
+                        n = SystemProto.PowerUseItem.CELL;
+                        break;
+                    case PHONE:
+                        n = SystemProto.PowerUseItem.PHONE;
+                        break;
+                    case WIFI:
+                        n = SystemProto.PowerUseItem.WIFI;
+                        break;
+                    case BLUETOOTH:
+                        n = SystemProto.PowerUseItem.BLUETOOTH;
+                        break;
+                    case SCREEN:
+                        n = SystemProto.PowerUseItem.SCREEN;
+                        break;
+                    case FLASHLIGHT:
+                        n = SystemProto.PowerUseItem.FLASHLIGHT;
+                        break;
+                    case APP:
+                        // dumpProtoAppsLocked will handle this.
+                        continue;
+                    case USER:
+                        n = SystemProto.PowerUseItem.USER;
+                        uid = UserHandle.getUid(bs.userId, 0);
+                        break;
+                    case UNACCOUNTED:
+                        n = SystemProto.PowerUseItem.UNACCOUNTED;
+                        break;
+                    case OVERCOUNTED:
+                        n = SystemProto.PowerUseItem.OVERCOUNTED;
+                        break;
+                    case CAMERA:
+                        n = SystemProto.PowerUseItem.CAMERA;
+                        break;
+                    case MEMORY:
+                        n = SystemProto.PowerUseItem.MEMORY;
+                        break;
+                }
+                final long puiToken = proto.start(SystemProto.POWER_USE_ITEM);
+                proto.write(SystemProto.PowerUseItem.NAME, n);
+                proto.write(SystemProto.PowerUseItem.UID, uid);
+                proto.write(SystemProto.PowerUseItem.COMPUTED_POWER_MAH, bs.totalPowerMah);
+                proto.write(SystemProto.PowerUseItem.SHOULD_HIDE, bs.shouldHide);
+                proto.write(SystemProto.PowerUseItem.SCREEN_POWER_MAH, bs.screenPowerMah);
+                proto.write(SystemProto.PowerUseItem.PROPORTIONAL_SMEAR_MAH,
+                        bs.proportionalSmearMah);
+                proto.end(puiToken);
+            }
+        }
+
+        // Power use summary (POWER_USE_SUMMARY_DATA)
+        final long pusToken = proto.start(SystemProto.POWER_USE_SUMMARY);
+        proto.write(SystemProto.PowerUseSummary.BATTERY_CAPACITY_MAH,
+                helper.getPowerProfile().getBatteryCapacity());
+        proto.write(SystemProto.PowerUseSummary.COMPUTED_POWER_MAH, helper.getComputedPower());
+        proto.write(SystemProto.PowerUseSummary.MIN_DRAINED_POWER_MAH, helper.getMinDrainedPower());
+        proto.write(SystemProto.PowerUseSummary.MAX_DRAINED_POWER_MAH, helper.getMaxDrainedPower());
+        proto.end(pusToken);
+
+        // RPM stats (RESOURCE_POWER_MANAGER_DATA)
+        final Map<String, ? extends Timer> rpmStats = getRpmStats();
+        final Map<String, ? extends Timer> screenOffRpmStats = getScreenOffRpmStats();
+        for (Map.Entry<String, ? extends Timer> ent : rpmStats.entrySet()) {
+            final long rpmToken = proto.start(SystemProto.RESOURCE_POWER_MANAGER);
+            proto.write(SystemProto.ResourcePowerManager.NAME, ent.getKey());
+            dumpTimer(proto, SystemProto.ResourcePowerManager.TOTAL,
+                    ent.getValue(), rawRealtimeUs, which);
+            dumpTimer(proto, SystemProto.ResourcePowerManager.SCREEN_OFF,
+                    screenOffRpmStats.get(ent.getKey()), rawRealtimeUs, which);
+            proto.end(rpmToken);
+        }
+
+        // Screen brightness (SCREEN_BRIGHTNESS_DATA)
+        for (int i = 0; i < NUM_SCREEN_BRIGHTNESS_BINS; ++i) {
+            final long sbToken = proto.start(SystemProto.SCREEN_BRIGHTNESS);
+            proto.write(SystemProto.ScreenBrightness.NAME, i);
+            dumpTimer(proto, SystemProto.ScreenBrightness.TOTAL, getScreenBrightnessTimer(i),
+                    rawRealtimeUs, which);
+            proto.end(sbToken);
+        }
+
+        // Signal scanning time (SIGNAL_SCANNING_TIME_DATA)
+        dumpTimer(proto, SystemProto.SIGNAL_SCANNING, getPhoneSignalScanningTimer(), rawRealtimeUs,
+                which);
+
+        // Phone signal strength (SIGNAL_STRENGTH_TIME_DATA and SIGNAL_STRENGTH_COUNT_DATA)
+        for (int i = 0; i < SignalStrength.NUM_SIGNAL_STRENGTH_BINS; ++i) {
+            final long pssToken = proto.start(SystemProto.PHONE_SIGNAL_STRENGTH);
+            proto.write(SystemProto.PhoneSignalStrength.NAME, i);
+            dumpTimer(proto, SystemProto.PhoneSignalStrength.TOTAL, getPhoneSignalStrengthTimer(i),
+                    rawRealtimeUs, which);
+            proto.end(pssToken);
+        }
+
+        // Wakeup reasons (WAKEUP_REASON_DATA)
+        final Map<String, ? extends Timer> wakeupReasons = getWakeupReasonStats();
+        for (Map.Entry<String, ? extends Timer> ent : wakeupReasons.entrySet()) {
+            final long wrToken = proto.start(SystemProto.WAKEUP_REASON);
+            proto.write(SystemProto.WakeupReason.NAME, ent.getKey());
+            dumpTimer(proto, SystemProto.WakeupReason.TOTAL, ent.getValue(), rawRealtimeUs, which);
+            proto.end(wrToken);
+        }
+
+        // Wifi signal strength (WIFI_SIGNAL_STRENGTH_TIME_DATA and WIFI_SIGNAL_STRENGTH_COUNT_DATA)
+        for (int i = 0; i < NUM_WIFI_SIGNAL_STRENGTH_BINS; ++i) {
+            final long wssToken = proto.start(SystemProto.WIFI_SIGNAL_STRENGTH);
+            proto.write(SystemProto.WifiSignalStrength.NAME, i);
+            dumpTimer(proto, SystemProto.WifiSignalStrength.TOTAL, getWifiSignalStrengthTimer(i),
+                    rawRealtimeUs, which);
+            proto.end(wssToken);
+        }
+
+        // Wifi state (WIFI_STATE_TIME_DATA and WIFI_STATE_COUNT_DATA)
+        for (int i = 0; i < NUM_WIFI_STATES; ++i) {
+            final long wsToken = proto.start(SystemProto.WIFI_STATE);
+            proto.write(SystemProto.WifiState.NAME, i);
+            dumpTimer(proto, SystemProto.WifiState.TOTAL, getWifiStateTimer(i),
+                    rawRealtimeUs, which);
+            proto.end(wsToken);
+        }
+
+        // Wifi supplicant state (WIFI_SUPPL_STATE_TIME_DATA and WIFI_SUPPL_STATE_COUNT_DATA)
+        for (int i = 0; i < NUM_WIFI_SUPPL_STATES; ++i) {
+            final long wssToken = proto.start(SystemProto.WIFI_SUPPLICANT_STATE);
+            proto.write(SystemProto.WifiSupplicantState.NAME, i);
+            dumpTimer(proto, SystemProto.WifiSupplicantState.TOTAL, getWifiSupplStateTimer(i),
+                    rawRealtimeUs, which);
+            proto.end(wssToken);
+        }
+
+        proto.end(sToken);
+    }
 }
diff --git a/android/os/Debug.java b/android/os/Debug.java
index b46c6b1..017c213 100644
--- a/android/os/Debug.java
+++ b/android/os/Debug.java
@@ -1748,22 +1748,26 @@
     public static final int MEMINFO_SHMEM = 4;
     /** @hide */
     public static final int MEMINFO_SLAB = 5;
+     /** @hide */
+    public static final int MEMINFO_SLAB_RECLAIMABLE = 6;
+     /** @hide */
+    public static final int MEMINFO_SLAB_UNRECLAIMABLE = 7;
     /** @hide */
-    public static final int MEMINFO_SWAP_TOTAL = 6;
+    public static final int MEMINFO_SWAP_TOTAL = 8;
     /** @hide */
-    public static final int MEMINFO_SWAP_FREE = 7;
+    public static final int MEMINFO_SWAP_FREE = 9;
     /** @hide */
-    public static final int MEMINFO_ZRAM_TOTAL = 8;
+    public static final int MEMINFO_ZRAM_TOTAL = 10;
     /** @hide */
-    public static final int MEMINFO_MAPPED = 9;
+    public static final int MEMINFO_MAPPED = 11;
     /** @hide */
-    public static final int MEMINFO_VM_ALLOC_USED = 10;
+    public static final int MEMINFO_VM_ALLOC_USED = 12;
     /** @hide */
-    public static final int MEMINFO_PAGE_TABLES = 11;
+    public static final int MEMINFO_PAGE_TABLES = 13;
     /** @hide */
-    public static final int MEMINFO_KERNEL_STACK = 12;
+    public static final int MEMINFO_KERNEL_STACK = 14;
     /** @hide */
-    public static final int MEMINFO_COUNT = 13;
+    public static final int MEMINFO_COUNT = 15;
 
     /**
      * Retrieves /proc/meminfo.  outSizes is filled with fields
diff --git a/android/os/ParcelFileDescriptor.java b/android/os/ParcelFileDescriptor.java
index c091420..7f588ad 100644
--- a/android/os/ParcelFileDescriptor.java
+++ b/android/os/ParcelFileDescriptor.java
@@ -737,7 +737,9 @@
     private void closeWithStatus(int status, String msg) {
         if (mClosed) return;
         mClosed = true;
-        mGuard.close();
+        if (mGuard != null) {
+            mGuard.close();
+        }
         // Status MUST be sent before closing actual descriptor
         writeCommStatusAndClose(status, msg);
         IoUtils.closeQuietly(mFd);
diff --git a/android/os/PatternMatcher.java b/android/os/PatternMatcher.java
index 1f3a1e6..76b2142 100644
--- a/android/os/PatternMatcher.java
+++ b/android/os/PatternMatcher.java
@@ -16,7 +16,7 @@
 
 package android.os;
 
-import android.util.Log;
+import android.util.proto.ProtoOutputStream;
 
 import java.util.Arrays;
 
@@ -131,7 +131,17 @@
         }
         return "PatternMatcher{" + type + mPattern + "}";
     }
-    
+
+    /** @hide */
+    public void writeToProto(ProtoOutputStream proto, long fieldId) {
+        long token = proto.start(fieldId);
+        proto.write(PatternMatcherProto.PATTERN, mPattern);
+        proto.write(PatternMatcherProto.TYPE, mType);
+        // PatternMatcherProto.PARSED_PATTERN is too much to dump, but the field is reserved to
+        // match the current data structure.
+        proto.end(token);
+    }
+
     public int describeContents() {
         return 0;
     }
@@ -141,7 +151,7 @@
         dest.writeInt(mType);
         dest.writeIntArray(mParsedPattern);
     }
-    
+
     public PatternMatcher(Parcel src) {
         mPattern = src.readString();
         mType = src.readInt();
diff --git a/android/os/ServiceManager.java b/android/os/ServiceManager.java
index f41848f..34c7845 100644
--- a/android/os/ServiceManager.java
+++ b/android/os/ServiceManager.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2007 The Android Open Source Project
+ * Copyright (C) 2009 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.
@@ -16,29 +16,9 @@
 
 package android.os;
 
-import android.util.Log;
-
-import com.android.internal.os.BinderInternal;
-
-import java.util.HashMap;
 import java.util.Map;
 
-/** @hide */
 public final class ServiceManager {
-    private static final String TAG = "ServiceManager";
-    private static IServiceManager sServiceManager;
-    private static HashMap<String, IBinder> sCache = new HashMap<String, IBinder>();
-
-    private static IServiceManager getIServiceManager() {
-        if (sServiceManager != null) {
-            return sServiceManager;
-        }
-
-        // Find the service manager
-        sServiceManager = ServiceManagerNative
-                .asInterface(Binder.allowBlocking(BinderInternal.getContextObject()));
-        return sServiceManager;
-    }
 
     /**
      * Returns a reference to a service with the given name.
@@ -47,32 +27,14 @@
      * @return a reference to the service, or <code>null</code> if the service doesn't exist
      */
     public static IBinder getService(String name) {
-        try {
-            IBinder service = sCache.get(name);
-            if (service != null) {
-                return service;
-            } else {
-                return Binder.allowBlocking(getIServiceManager().getService(name));
-            }
-        } catch (RemoteException e) {
-            Log.e(TAG, "error in getService", e);
-        }
         return null;
     }
 
     /**
-     * Returns a reference to a service with the given name, or throws
-     * {@link NullPointerException} if none is found.
-     *
-     * @hide
+     * Is not supposed to return null, but that is fine for layoutlib.
      */
     public static IBinder getServiceOrThrow(String name) throws ServiceNotFoundException {
-        final IBinder binder = getService(name);
-        if (binder != null) {
-            return binder;
-        } else {
-            throw new ServiceNotFoundException(name);
-        }
+        throw new ServiceNotFoundException(name);
     }
 
     /**
@@ -83,39 +45,7 @@
      * @param service the service object
      */
     public static void addService(String name, IBinder service) {
-        addService(name, service, false, IServiceManager.DUMP_PRIORITY_NORMAL);
-    }
-
-    /**
-     * Place a new @a service called @a name into the service
-     * manager.
-     *
-     * @param name the name of the new service
-     * @param service the service object
-     * @param allowIsolated set to true to allow isolated sandboxed processes
-     * to access this service
-     */
-    public static void addService(String name, IBinder service, boolean allowIsolated) {
-        addService(name, service, allowIsolated, IServiceManager.DUMP_PRIORITY_NORMAL);
-    }
-
-    /**
-     * Place a new @a service called @a name into the service
-     * manager.
-     *
-     * @param name the name of the new service
-     * @param service the service object
-     * @param allowIsolated set to true to allow isolated sandboxed processes
-     * @param dumpPriority supported dump priority levels as a bitmask
-     * to access this service
-     */
-    public static void addService(String name, IBinder service, boolean allowIsolated,
-            int dumpPriority) {
-        try {
-            getIServiceManager().addService(name, service, allowIsolated, dumpPriority);
-        } catch (RemoteException e) {
-            Log.e(TAG, "error in addService", e);
-        }
+        // pass
     }
 
     /**
@@ -123,17 +53,7 @@
      * service manager.  Non-blocking.
      */
     public static IBinder checkService(String name) {
-        try {
-            IBinder service = sCache.get(name);
-            if (service != null) {
-                return service;
-            } else {
-                return Binder.allowBlocking(getIServiceManager().checkService(name));
-            }
-        } catch (RemoteException e) {
-            Log.e(TAG, "error in checkService", e);
-            return null;
-        }
+        return null;
     }
 
     /**
@@ -142,12 +62,9 @@
      * case of an exception
      */
     public static String[] listServices() {
-        try {
-            return getIServiceManager().listServices(IServiceManager.DUMP_PRIORITY_ALL);
-        } catch (RemoteException e) {
-            Log.e(TAG, "error in listServices", e);
-            return null;
-        }
+        // actual implementation returns null sometimes, so it's ok
+        // to return null instead of an empty list.
+        return null;
     }
 
     /**
@@ -159,10 +76,7 @@
      * @hide
      */
     public static void initServiceCache(Map<String, IBinder> cache) {
-        if (sCache.size() != 0) {
-            throw new IllegalStateException("setServiceCache may only be called once");
-        }
-        sCache.putAll(cache);
+        // pass
     }
 
     /**
@@ -173,6 +87,7 @@
      * @hide
      */
     public static class ServiceNotFoundException extends Exception {
+        // identical to the original implementation
         public ServiceNotFoundException(String name) {
             super("No service published for: " + name);
         }
diff --git a/android/os/SomeService.java b/android/os/SomeService.java
new file mode 100644
index 0000000..bdfaa44
--- /dev/null
+++ b/android/os/SomeService.java
@@ -0,0 +1,42 @@
+package android.os;
+
+import android.app.Service;
+import android.content.Intent;
+import java.io.File;
+import java.io.IOException;
+
+/** Service in separate process available for calling over binder. */
+public class SomeService extends Service {
+
+    private File mTempFile;
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        try {
+            mTempFile = File.createTempFile("foo", "bar");
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private final ISomeService.Stub mBinder =
+            new ISomeService.Stub() {
+                public void readDisk(int times) {
+                    for (int i = 0; i < times; i++) {
+                        mTempFile.exists();
+                    }
+                }
+            };
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mBinder;
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        mTempFile.delete();
+    }
+}
diff --git a/android/os/StatsLogEventWrapper.java b/android/os/StatsLogEventWrapper.java
new file mode 100644
index 0000000..9491bec
--- /dev/null
+++ b/android/os/StatsLogEventWrapper.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2017 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;
+
+import java.io.ByteArrayOutputStream;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * Wrapper class for sending data from Android OS to StatsD.
+ *
+ * @hide
+ */
+public final class StatsLogEventWrapper implements Parcelable {
+    private ByteArrayOutputStream mStorage = new ByteArrayOutputStream();
+
+    // Below are constants copied from log/log.h
+    private static final int EVENT_TYPE_INT = 0;  /* int32_t */
+    private static final int EVENT_TYPE_LONG = 1; /* int64_t */
+    private static final int EVENT_TYPE_STRING = 2;
+    private static final int EVENT_TYPE_LIST = 3;
+    private static final int EVENT_TYPE_FLOAT = 4;
+
+    /**
+     * Creates a log_event that is binary-encoded as implemented in
+     * system/core/liblog/log_event_list.c; this allows us to use the same parsing logic in statsd
+     * for pushed and pulled data. The write* methods must be called in the same order as their
+     * field number. There is no checking that the correct number of write* methods is called.
+     * We also write an END_LIST character before beginning to write to parcel, but this END_LIST
+     * may be unnecessary.
+     *
+     * @param tag    The integer representing the tag for this event.
+     * @param fields The number of fields specified in this event.
+     */
+    public StatsLogEventWrapper(int tag, int fields) {
+        // Write four bytes from tag, starting with least-significant bit.
+        write4Bytes(tag);
+        mStorage.write(EVENT_TYPE_LIST); // This is required to start the log entry.
+        mStorage.write(fields); // Indicate number of elements in this list.
+    }
+
+    /**
+     * Boilerplate for Parcel.
+     */
+    public static final Parcelable.Creator<StatsLogEventWrapper> CREATOR = new
+            Parcelable.Creator<StatsLogEventWrapper>() {
+                public StatsLogEventWrapper createFromParcel(Parcel in) {
+                    return new StatsLogEventWrapper(in);
+                }
+
+                public StatsLogEventWrapper[] newArray(int size) {
+                    return new StatsLogEventWrapper[size];
+                }
+            };
+
+    private void write4Bytes(int val) {
+        mStorage.write(val);
+        mStorage.write(val >>> 8);
+        mStorage.write(val >>> 16);
+        mStorage.write(val >>> 24);
+    }
+
+    private void write8Bytes(long val) {
+        write4Bytes((int) (val & 0xFFFFFFFF)); // keep the lowe 32-bits
+        write4Bytes((int) (val >>> 32)); // Write the high 32-bits.
+    }
+
+    /**
+     * Adds 32-bit integer to output.
+     */
+    public void writeInt(int val) {
+        mStorage.write(EVENT_TYPE_INT);
+        write4Bytes(val);
+    }
+
+    /**
+     * Adds 64-bit long to output.
+     */
+    public void writeLong(long val) {
+        mStorage.write(EVENT_TYPE_LONG);
+        write8Bytes(val);
+    }
+
+    /**
+     * Adds a 4-byte floating point value to output.
+     */
+    public void writeFloat(float val) {
+        int v = Float.floatToIntBits(val);
+        mStorage.write(EVENT_TYPE_FLOAT);
+        write4Bytes(v);
+    }
+
+    /**
+     * Adds a string to the output.
+     */
+    public void writeString(String val) {
+        mStorage.write(EVENT_TYPE_STRING);
+        write4Bytes(val.length());
+        byte[] bytes = val.getBytes(StandardCharsets.UTF_8);
+        mStorage.write(bytes, 0, bytes.length);
+    }
+
+    private StatsLogEventWrapper(Parcel in) {
+        readFromParcel(in);
+    }
+
+    /**
+     * Writes the stored fields to a byte array. Will first write a new-line character to denote
+     * END_LIST before writing contents to byte array.
+     */
+    public void writeToParcel(Parcel out, int flags) {
+        mStorage.write(10); // new-line character is same as END_LIST
+        out.writeByteArray(mStorage.toByteArray());
+    }
+
+    /**
+     * Not implemented.
+     */
+    public void readFromParcel(Parcel in) {
+        // Not needed since this java class is for sending to statsd only.
+    }
+
+    /**
+     * Boilerplate for Parcel.
+     */
+    public int describeContents() {
+        return 0;
+    }
+}
diff --git a/android/os/StrictModeTest.java b/android/os/StrictModeTest.java
new file mode 100644
index 0000000..d973c20
--- /dev/null
+++ b/android/os/StrictModeTest.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2017 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;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.net.Uri;
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
+import android.support.test.runner.AndroidJUnit4;
+import com.google.common.util.concurrent.SettableFuture;
+import java.io.File;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class StrictModeTest {
+    @Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+    @Test
+    public void timeVmViolation() {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().detectAll().penaltyLog().build());
+        causeVmViolations(state);
+    }
+
+    @Test
+    public void timeVmViolationNoStrictMode() {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        causeVmViolations(state);
+    }
+
+    private static void causeVmViolations(BenchmarkState state) {
+        Intent intent = new Intent(Intent.ACTION_VIEW);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        intent.setDataAndType(Uri.parse("content://com.example/foobar"), "image/jpeg");
+        final Context context = InstrumentationRegistry.getTargetContext();
+        while (state.keepRunning()) {
+            context.startActivity(intent);
+        }
+    }
+
+    @Test
+    public void timeThreadViolation() throws IOException {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        StrictMode.setThreadPolicy(
+                new StrictMode.ThreadPolicy.Builder().detectAll().penaltyLog().build());
+        causeThreadViolations(state);
+    }
+
+    @Test
+    public void timeThreadViolationNoStrictMode() throws IOException {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        causeThreadViolations(state);
+    }
+
+    private static void causeThreadViolations(BenchmarkState state) throws IOException {
+        final File test = File.createTempFile("foo", "bar");
+        while (state.keepRunning()) {
+            test.exists();
+        }
+        test.delete();
+    }
+
+    @Test
+    public void timeCrossBinderThreadViolation() throws Exception {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        StrictMode.setThreadPolicy(
+                new StrictMode.ThreadPolicy.Builder().detectAll().penaltyLog().build());
+        causeCrossProcessThreadViolations(state);
+    }
+
+    @Test
+    public void timeCrossBinderThreadViolationNoStrictMode() throws Exception {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        causeCrossProcessThreadViolations(state);
+    }
+
+    private static void causeCrossProcessThreadViolations(BenchmarkState state)
+            throws ExecutionException, InterruptedException, RemoteException {
+        final Context context = InstrumentationRegistry.getTargetContext();
+
+        SettableFuture<IBinder> binder = SettableFuture.create();
+        ServiceConnection connection =
+                new ServiceConnection() {
+                    @Override
+                    public void onServiceConnected(ComponentName className, IBinder service) {
+                        binder.set(service);
+                    }
+
+                    @Override
+                    public void onServiceDisconnected(ComponentName arg0) {
+                        binder.set(null);
+                    }
+                };
+        context.bindService(
+                new Intent(context, SomeService.class), connection, Context.BIND_AUTO_CREATE);
+        ISomeService someService = ISomeService.Stub.asInterface(binder.get());
+        while (state.keepRunning()) {
+            // Violate strictmode heavily.
+            someService.readDisk(10);
+        }
+        context.unbindService(connection);
+    }
+}
diff --git a/android/os/SystemProperties.java b/android/os/SystemProperties.java
index 560b4b3..4f6d322 100644
--- a/android/os/SystemProperties.java
+++ b/android/os/SystemProperties.java
@@ -84,9 +84,6 @@
     /**
      * Get the String value for the given {@code key}.
      *
-     * <b>WARNING:</b> Do not use this method if the value may not be a valid UTF string! This
-     * method will crash in native code.
-     *
      * @param key the key to lookup
      * @return an empty string if the {@code key} isn't found
      */
@@ -99,9 +96,6 @@
     /**
      * Get the String value for the given {@code key}.
      *
-     * <b>WARNING:</b> Do not use this method if the value may not be a valid UTF string! This
-     * method will crash in native code.
-     *
      * @param key the key to lookup
      * @param def the default value in case the property is not set or empty
      * @return if the {@code key} isn't found, return {@code def} if it isn't null, or an empty
@@ -163,7 +157,7 @@
      * @throws IllegalArgumentException if the {@code val} exceeds 91 characters
      */
     public static void set(@NonNull String key, @Nullable String val) {
-        if (val != null && val.length() > PROP_VALUE_MAX) {
+        if (val != null && !val.startsWith("ro.") && val.length() > PROP_VALUE_MAX) {
             throw new IllegalArgumentException("value of system property '" + key
                     + "' is longer than " + PROP_VALUE_MAX + " characters: " + val);
         }
diff --git a/android/os/UserManager.java b/android/os/UserManager.java
index 430a5e3..8c68871 100644
--- a/android/os/UserManager.java
+++ b/android/os/UserManager.java
@@ -574,6 +574,25 @@
     public static final String DISALLOW_CREATE_WINDOWS = "no_create_windows";
 
     /**
+     * Specifies that system error dialogs for crashed or unresponsive apps should not be shown.
+     * In this case, the system will force-stop the app as if the user chooses the "close app"
+     * option on the UI. No feedback report will be collected as there is no way for the user to
+     * provide explicit consent.
+     *
+     * When this user restriction is set by device owners, it's applied to all users; when it's set
+     * by profile owners, it's only applied to the relevant profiles.
+     * The default value is <code>false</code>.
+     *
+     * <p>This user restriction has no effect on managed profiles.
+     * <p>Key for user restrictions.
+     * <p>Type: Boolean
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+     * @see #getUserRestrictions()
+     */
+    public static final String DISALLOW_SYSTEM_ERROR_DIALOGS = "no_system_error_dialogs";
+
+    /**
      * Specifies if what is copied in the clipboard of this profile can
      * be pasted in related profiles. Does not restrict if the clipboard of related profiles can be
      * pasted in this profile.
diff --git a/android/preference/SeekBarVolumizer.java b/android/preference/SeekBarVolumizer.java
index ee8eed1..3d2e1d1 100644
--- a/android/preference/SeekBarVolumizer.java
+++ b/android/preference/SeekBarVolumizer.java
@@ -206,8 +206,7 @@
                     try {
                         mRingtone.setAudioAttributes(new AudioAttributes.Builder(mRingtone
                                 .getAudioAttributes())
-                                .setFlags(AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY |
-                                        AudioAttributes.FLAG_BYPASS_MUTE)
+                                .setFlags(AudioAttributes.FLAG_BYPASS_MUTE)
                                 .build());
                         mRingtone.play();
                     } catch (Throwable e) {
diff --git a/android/provider/Settings.java b/android/provider/Settings.java
index a062db4..a27df3a 100644
--- a/android/provider/Settings.java
+++ b/android/provider/Settings.java
@@ -442,6 +442,18 @@
             "android.settings.ASSIST_GESTURE_SETTINGS";
 
     /**
+     * Activity Action: Show settings to enroll fingerprints, and setup PIN/Pattern/Pass if
+     * necessary.
+     * <p>
+     * Input: Nothing.
+     * <p>
+     * Output: Nothing.
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_FINGERPRINT_ENROLL =
+            "android.settings.FINGERPRINT_ENROLL";
+
+    /**
      * Activity Action: Show settings to allow configuration of cast endpoints.
      * <p>
      * In some cases, a matching Activity may not exist, so ensure you
@@ -5708,6 +5720,7 @@
          *
          * @hide
          */
+        @TestApi
         public static final String ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED =
                 "accessibility_display_magnification_enabled";
 
@@ -6792,14 +6805,6 @@
                 "lock_screen_show_notifications";
 
         /**
-         * This preference stores the last stack active task time for each user, which affects what
-         * tasks will be visible in Overview.
-         * @hide
-         */
-        public static final String OVERVIEW_LAST_STACK_ACTIVE_TIME =
-                "overview_last_stack_active_time";
-
-        /**
          * List of TV inputs that are currently hidden. This is a string
          * containing the IDs of all hidden TV inputs. Each ID is encoded by
          * {@link android.net.Uri#encode(String)} and separated by ':'.
@@ -7610,6 +7615,13 @@
         public static final String AIRPLANE_MODE_TOGGLEABLE_RADIOS = "airplane_mode_toggleable_radios";
 
         /**
+         * An integer representing the Bluetooth Class of Device (CoD).
+         *
+         * @hide
+         */
+        public static final String BLUETOOTH_CLASS_OF_DEVICE = "bluetooth_class_of_device";
+
+        /**
          * A Long representing a bitmap of profiles that should be disabled when bluetooth starts.
          * See {@link android.bluetooth.BluetoothProfile}.
          * {@hide}
@@ -9261,6 +9273,13 @@
          */
         public static final String DEFAULT_DNS_SERVER = "default_dns_server";
 
+        /**
+         * Whether to disable DNS over TLS (boolean)
+         *
+         * @hide
+         */
+        public static final String DNS_TLS_DISABLED = "dns_tls_disabled";
+
         /** {@hide} */
         public static final String
                 BLUETOOTH_HEADSET_PRIORITY_PREFIX = "bluetooth_headset_priority_";
@@ -9575,6 +9594,22 @@
         public static final String DEVICE_POLICY_CONSTANTS = "device_policy_constants";
 
         /**
+         * TextClassifier specific settings.
+         * This is encoded as a key=value list, separated by commas. Ex:
+         *
+         * <pre>
+         * smart_selection_dark_launch              (boolean)
+         * smart_selection_enabled_for_edit_text    (boolean)
+         * </pre>
+         *
+         * <p>
+         * Type: string
+         * @hide
+         * see also android.view.textclassifier.TextClassifierConstants
+         */
+        public static final String TEXT_CLASSIFIER_CONSTANTS = "text_classifier_constants";
+
+        /**
          * Get the key that retrieves a bluetooth headset's priority.
          * @hide
          */
@@ -9621,7 +9656,7 @@
          * Get the key that retrieves a bluetooth Input Device's priority.
          * @hide
          */
-        public static final String getBluetoothInputDevicePriorityKey(String address) {
+        public static final String getBluetoothHidHostPriorityKey(String address) {
             return BLUETOOTH_INPUT_DEVICE_PRIORITY_PREFIX + address.toUpperCase(Locale.ROOT);
         }
 
diff --git a/android/security/net/config/ManifestConfigSource.java b/android/security/net/config/ManifestConfigSource.java
index 8fcd5ab..79115a5 100644
--- a/android/security/net/config/ManifestConfigSource.java
+++ b/android/security/net/config/ManifestConfigSource.java
@@ -20,6 +20,7 @@
 import android.content.pm.ApplicationInfo;
 import android.util.Log;
 import android.util.Pair;
+
 import java.util.Set;
 
 /** @hide */
@@ -29,21 +30,14 @@
 
     private final Object mLock = new Object();
     private final Context mContext;
-    private final int mApplicationInfoFlags;
-    private final int mTargetSdkVersion;
-    private final int mConfigResourceId;
-    private final int mTargetSandboxVesrsion;
+    private final ApplicationInfo mApplicationInfo;
 
     private ConfigSource mConfigSource;
 
     public ManifestConfigSource(Context context) {
         mContext = context;
-        // Cache values because ApplicationInfo is mutable and apps do modify it :(
-        ApplicationInfo info = context.getApplicationInfo();
-        mApplicationInfoFlags = info.flags;
-        mTargetSdkVersion = info.targetSdkVersion;
-        mConfigResourceId = info.networkSecurityConfigRes;
-        mTargetSandboxVesrsion = info.targetSandboxVersion;
+        // Cache the info because ApplicationInfo is mutable and apps do modify it :(
+        mApplicationInfo = new ApplicationInfo(context.getApplicationInfo());
     }
 
     @Override
@@ -61,17 +55,18 @@
             if (mConfigSource != null) {
                 return mConfigSource;
             }
-
+            int configResource = mApplicationInfo.networkSecurityConfigRes;
             ConfigSource source;
-            if (mConfigResourceId != 0) {
-                boolean debugBuild = (mApplicationInfoFlags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
+            if (configResource != 0) {
+                boolean debugBuild =
+                        (mApplicationInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
                 if (DBG) {
                     Log.d(LOG_TAG, "Using Network Security Config from resource "
-                            + mContext.getResources().getResourceEntryName(mConfigResourceId)
+                            + mContext.getResources()
+                                .getResourceEntryName(configResource)
                             + " debugBuild: " + debugBuild);
                 }
-                source = new XmlConfigSource(mContext, mConfigResourceId, debugBuild,
-                        mTargetSdkVersion, mTargetSandboxVesrsion);
+                source = new XmlConfigSource(mContext, configResource, mApplicationInfo);
             } else {
                 if (DBG) {
                     Log.d(LOG_TAG, "No Network Security Config specified, using platform default");
@@ -79,10 +74,9 @@
                 // the legacy FLAG_USES_CLEARTEXT_TRAFFIC is not supported for Ephemeral apps, they
                 // should use the network security config.
                 boolean usesCleartextTraffic =
-                        (mApplicationInfoFlags & ApplicationInfo.FLAG_USES_CLEARTEXT_TRAFFIC) != 0
-                        && mTargetSandboxVesrsion < 2;
-                source = new DefaultConfigSource(usesCleartextTraffic, mTargetSdkVersion,
-                        mTargetSandboxVesrsion);
+                        (mApplicationInfo.flags & ApplicationInfo.FLAG_USES_CLEARTEXT_TRAFFIC) != 0
+                        && mApplicationInfo.targetSandboxVersion < 2;
+                source = new DefaultConfigSource(usesCleartextTraffic, mApplicationInfo);
             }
             mConfigSource = source;
             return mConfigSource;
@@ -93,10 +87,8 @@
 
         private final NetworkSecurityConfig mDefaultConfig;
 
-        public DefaultConfigSource(boolean usesCleartextTraffic, int targetSdkVersion,
-                int targetSandboxVesrsion) {
-            mDefaultConfig = NetworkSecurityConfig.getDefaultBuilder(targetSdkVersion,
-                    targetSandboxVesrsion)
+        DefaultConfigSource(boolean usesCleartextTraffic, ApplicationInfo info) {
+            mDefaultConfig = NetworkSecurityConfig.getDefaultBuilder(info)
                     .setCleartextTrafficPermitted(usesCleartextTraffic)
                     .build();
         }
diff --git a/android/security/net/config/NetworkSecurityConfig.java b/android/security/net/config/NetworkSecurityConfig.java
index 789fc27..b9e5505 100644
--- a/android/security/net/config/NetworkSecurityConfig.java
+++ b/android/security/net/config/NetworkSecurityConfig.java
@@ -16,9 +16,11 @@
 
 package android.security.net.config;
 
+import android.content.pm.ApplicationInfo;
 import android.os.Build;
 import android.util.ArrayMap;
 import android.util.ArraySet;
+
 import java.security.cert.X509Certificate;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -28,8 +30,6 @@
 import java.util.Map;
 import java.util.Set;
 
-import javax.net.ssl.X509TrustManager;
-
 /**
  * @hide
  */
@@ -170,22 +170,24 @@
      * <li>No certificate pinning is used.</li>
      * <li>The system certificate store is trusted for connections.</li>
      * <li>If the application targets API level 23 (Android M) or lower then the user certificate
-     * store is trusted by default as well.</li>
+     * store is trusted by default as well for non-privileged applications.</li>
+     * <li>Privileged applications do not trust the user certificate store on Android P and higher.
+     * </li>
      * </ol>
      *
      * @hide
      */
-    public static final Builder getDefaultBuilder(int targetSdkVersion, int targetSandboxVesrsion) {
+    public static Builder getDefaultBuilder(ApplicationInfo info) {
         Builder builder = new Builder()
                 .setHstsEnforced(DEFAULT_HSTS_ENFORCED)
                 // System certificate store, does not bypass static pins.
                 .addCertificatesEntryRef(
                         new CertificatesEntryRef(SystemCertificateSource.getInstance(), false));
-        final boolean cleartextTrafficPermitted = targetSandboxVesrsion < 2;
+        final boolean cleartextTrafficPermitted = info.targetSandboxVersion < 2;
         builder.setCleartextTrafficPermitted(cleartextTrafficPermitted);
         // Applications targeting N and above must opt in into trusting the user added certificate
         // store.
-        if (targetSdkVersion <= Build.VERSION_CODES.M) {
+        if (info.targetSdkVersion <= Build.VERSION_CODES.M && !info.isPrivilegedApp()) {
             // User certificate store, does not bypass static pins.
             builder.addCertificatesEntryRef(
                     new CertificatesEntryRef(UserCertificateSource.getInstance(), false));
diff --git a/android/security/net/config/XmlConfigSource.java b/android/security/net/config/XmlConfigSource.java
index a111fbc..02be403 100644
--- a/android/security/net/config/XmlConfigSource.java
+++ b/android/security/net/config/XmlConfigSource.java
@@ -1,13 +1,13 @@
 package android.security.net.config;
 
 import android.content.Context;
+import android.content.pm.ApplicationInfo;
 import android.content.res.Resources;
 import android.content.res.XmlResourceParser;
-import android.os.Build;
 import android.util.ArraySet;
 import android.util.Base64;
 import android.util.Pair;
-import com.android.internal.annotations.VisibleForTesting;
+
 import com.android.internal.util.XmlUtils;
 
 import org.xmlpull.v1.XmlPullParser;
@@ -36,37 +36,19 @@
     private final Object mLock = new Object();
     private final int mResourceId;
     private final boolean mDebugBuild;
-    private final int mTargetSdkVersion;
-    private final int mTargetSandboxVesrsion;
+    private final ApplicationInfo mApplicationInfo;
 
     private boolean mInitialized;
     private NetworkSecurityConfig mDefaultConfig;
     private Set<Pair<Domain, NetworkSecurityConfig>> mDomainMap;
     private Context mContext;
 
-    @VisibleForTesting
-    public XmlConfigSource(Context context, int resourceId) {
-        this(context, resourceId, false);
-    }
-
-    @VisibleForTesting
-    public XmlConfigSource(Context context, int resourceId, boolean debugBuild) {
-        this(context, resourceId, debugBuild, Build.VERSION_CODES.CUR_DEVELOPMENT);
-    }
-
-    @VisibleForTesting
-    public XmlConfigSource(Context context, int resourceId, boolean debugBuild,
-            int targetSdkVersion) {
-        this(context, resourceId, debugBuild, targetSdkVersion, 1 /*targetSandboxVersion*/);
-    }
-
-    public XmlConfigSource(Context context, int resourceId, boolean debugBuild,
-            int targetSdkVersion, int targetSandboxVesrsion) {
-        mResourceId = resourceId;
+    public XmlConfigSource(Context context, int resourceId, ApplicationInfo info) {
         mContext = context;
-        mDebugBuild = debugBuild;
-        mTargetSdkVersion = targetSdkVersion;
-        mTargetSandboxVesrsion = targetSandboxVesrsion;
+        mResourceId = resourceId;
+        mApplicationInfo = new ApplicationInfo(info);
+
+        mDebugBuild = (mApplicationInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
     }
 
     public Set<Pair<Domain, NetworkSecurityConfig>> getPerDomainConfigs() {
@@ -365,7 +347,7 @@
         // Use the platform default as the parent of the base config for any values not provided
         // there. If there is no base config use the platform default.
         NetworkSecurityConfig.Builder platformDefaultBuilder =
-                NetworkSecurityConfig.getDefaultBuilder(mTargetSdkVersion, mTargetSandboxVesrsion);
+                NetworkSecurityConfig.getDefaultBuilder(mApplicationInfo);
         addDebugAnchorsIfNeeded(debugConfigBuilder, platformDefaultBuilder);
         if (baseConfigBuilder != null) {
             baseConfigBuilder.setParent(platformDefaultBuilder);
diff --git a/android/service/autofill/AutofillService.java b/android/service/autofill/AutofillService.java
index 2e59f6c..953501c 100644
--- a/android/service/autofill/AutofillService.java
+++ b/android/service/autofill/AutofillService.java
@@ -65,7 +65,7 @@
  *   <li>The service replies through {@link FillCallback#onSuccess(FillResponse)}.
  *   <li>The Android System calls {@link #onDisconnected()} and unbinds from the
  *       {@code AutofillService}.
- *   <li>The Android System displays an UI affordance with the options sent by the service.
+ *   <li>The Android System displays an autofill UI with the options sent by the service.
  *   <li>The user picks an option.
  *   <li>The proper views are autofilled.
  * </ol>
@@ -365,6 +365,81 @@
  * <p><b>Note:</b> The autofill service could also whitelist well-known browser apps and skip the
  * verifications above, as long as the service can verify the authenticity of the browser app by
  * checking its signing certificate.
+ *
+ * <a name="MultipleStepsSave"></a>
+ * <h3>Saving when data is split in multiple screens</h3>
+ *
+ * Apps often split the user data in multiple screens in the same activity, specially in
+ * activities used to create a new user account. For example, the first screen asks for a username,
+ * and if the username is available, it moves to a second screen, which asks for a password.
+ *
+ * <p>It's tricky to handle save for autofill in these situations, because the autofill service must
+ * wait until the user enters both fields before the autofill save UI can be shown. But it can be
+ * done by following the steps below:
+ *
+ * <ol>
+ * <li>In the first
+ * {@link #onFillRequest(FillRequest, CancellationSignal, FillCallback) fill request}, the service
+ * adds a {@link FillResponse.Builder#setClientState(android.os.Bundle) client state bundle} in
+ * the response, containing the autofill ids of the partial fields present in the screen.
+ * <li>In the second
+ * {@link #onFillRequest(FillRequest, CancellationSignal, FillCallback) fill request}, the service
+ * retrieves the {@link FillRequest#getClientState() client state bundle}, gets the autofill ids
+ * set in the previous request from the client state, and adds these ids and the
+ * {@link SaveInfo#FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE} to the {@link SaveInfo} used in the second
+ * response.
+ * <li>In the {@link #onSaveRequest(SaveRequest, SaveCallback) save request}, the service uses the
+ * proper {@link FillContext fill contexts} to get the value of each field (there is one fill
+ * context per fill request).
+ * </ol>
+ *
+ * <p>For example, in an app that uses 2 steps for the username and password fields, the workflow
+ * would be:
+ * <pre class="prettyprint">
+ *  // On first fill request
+ *  AutofillId usernameId = // parse from AssistStructure;
+ *  Bundle clientState = new Bundle();
+ *  clientState.putParcelable("usernameId", usernameId);
+ *  fillCallback.onSuccess(
+ *    new FillResponse.Builder()
+ *        .setClientState(clientState)
+ *        .setSaveInfo(new SaveInfo
+ *             .Builder(SaveInfo.SAVE_DATA_TYPE_USERNAME, new AutofillId[] {usernameId})
+ *             .build())
+ *        .build());
+ *
+ *  // On second fill request
+ *  Bundle clientState = fillRequest.getClientState();
+ *  AutofillId usernameId = clientState.getParcelable("usernameId");
+ *  AutofillId passwordId = // parse from AssistStructure
+ *  clientState.putParcelable("passwordId", passwordId);
+ *  fillCallback.onSuccess(
+ *    new FillResponse.Builder()
+ *        .setClientState(clientState)
+ *        .setSaveInfo(new SaveInfo
+ *             .Builder(SaveInfo.SAVE_DATA_TYPE_USERNAME | SaveInfo.SAVE_DATA_TYPE_PASSWORD,
+ *                      new AutofillId[] {usernameId, passwordId})
+ *             .setFlags(SaveInfo.FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE)
+ *             .build())
+ *        .build());
+ *
+ *  // On save request
+ *  Bundle clientState = saveRequest.getClientState();
+ *  AutofillId usernameId = clientState.getParcelable("usernameId");
+ *  AutofillId passwordId = clientState.getParcelable("passwordId");
+ *  List<FillContext> fillContexts = saveRequest.getFillContexts();
+ *
+ *  FillContext usernameContext = fillContexts.get(0);
+ *  ViewNode usernameNode = findNodeByAutofillId(usernameContext.getStructure(), usernameId);
+ *  AutofillValue username = usernameNode.getAutofillValue().getTextValue().toString();
+ *
+ *  FillContext passwordContext = fillContexts.get(1);
+ *  ViewNode passwordNode = findNodeByAutofillId(passwordContext.getStructure(), passwordId);
+ *  AutofillValue password = passwordNode.getAutofillValue().getTextValue().toString();
+ *
+ *  save(username, password);
+ *
+ * </pre>
  */
 public abstract class AutofillService extends Service {
     private static final String TAG = "AutofillService";
diff --git a/android/service/autofill/BatchUpdates.java b/android/service/autofill/BatchUpdates.java
new file mode 100644
index 0000000..90acc88
--- /dev/null
+++ b/android/service/autofill/BatchUpdates.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2017 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.service.autofill;
+
+import static android.view.autofill.Helper.sDebug;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Pair;
+import android.widget.RemoteViews;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
+
+/**
+ * Defines actions to be applied to a {@link RemoteViews template presentation}.
+ *
+ *
+ * <p>It supports 2 types of actions:
+ *
+ * <ol>
+ *   <li>{@link RemoteViews Actions} to be applied to the template.
+ *   <li>{@link Transformation Transformations} to be applied on child views.
+ * </ol>
+ *
+ * <p>Typically used on {@link CustomDescription custom descriptions} to conditionally display
+ * differents views based on user input - see
+ * {@link CustomDescription.Builder#batchUpdate(Validator, BatchUpdates)} for more information.
+ */
+public final class BatchUpdates implements Parcelable {
+
+    private final ArrayList<Pair<Integer, InternalTransformation>> mTransformations;
+    private final RemoteViews mUpdates;
+
+    private BatchUpdates(Builder builder) {
+        mTransformations = builder.mTransformations;
+        mUpdates = builder.mUpdates;
+    }
+
+    /** @hide */
+    @Nullable
+    public ArrayList<Pair<Integer, InternalTransformation>> getTransformations() {
+        return mTransformations;
+    }
+
+    /** @hide */
+    @Nullable
+    public RemoteViews getUpdates() {
+        return mUpdates;
+    }
+
+    /**
+     * Builder for {@link BatchUpdates} objects.
+     */
+    public static class Builder {
+        private RemoteViews mUpdates;
+
+        private boolean mDestroyed;
+        private ArrayList<Pair<Integer, InternalTransformation>> mTransformations;
+
+        /**
+         * Applies the {@code updates} in the underlying presentation template.
+         *
+         * <p><b>Note:</b> The updates are applied before the
+         * {@link #transformChild(int, Transformation) transformations} are applied to the children
+         * views.
+         *
+         * @param updates a {@link RemoteViews} with the updated actions to be applied in the
+         * underlying presentation template.
+         *
+         * @return this builder
+         * @throws IllegalArgumentException if {@code condition} is not a class provided
+         * by the Android System.
+         */
+        public Builder updateTemplate(@NonNull RemoteViews updates) {
+            throwIfDestroyed();
+            mUpdates = Preconditions.checkNotNull(updates);
+            return this;
+        }
+
+        /**
+         * Adds a transformation to replace the value of a child view with the fields in the
+         * screen.
+         *
+         * <p>When multiple transformations are added for the same child view, they are applied
+         * in the same order as added.
+         *
+         * <p><b>Note:</b> The transformations are applied after the
+         * {@link #updateTemplate(RemoteViews) updates} are applied to the presentation template.
+         *
+         * @param id view id of the children view.
+         * @param transformation an implementation provided by the Android System.
+         * @return this builder.
+         * @throws IllegalArgumentException if {@code transformation} is not a class provided
+         * by the Android System.
+         */
+        public Builder transformChild(int id, @NonNull Transformation transformation) {
+            throwIfDestroyed();
+            Preconditions.checkArgument((transformation instanceof InternalTransformation),
+                    "not provided by Android System: " + transformation);
+            if (mTransformations == null) {
+                mTransformations = new ArrayList<>();
+            }
+            mTransformations.add(new Pair<>(id, (InternalTransformation) transformation));
+            return this;
+        }
+
+        /**
+         * Creates a new {@link BatchUpdates} instance.
+         *
+         * @throws IllegalStateException if {@link #build()} was already called before or no call
+         * to {@link #updateTemplate(RemoteViews)} or {@link #transformChild(int, Transformation)}
+         * has been made.
+         */
+        public BatchUpdates build() {
+            throwIfDestroyed();
+            Preconditions.checkState(mUpdates != null || mTransformations != null,
+                    "must call either updateTemplate() or transformChild() at least once");
+            mDestroyed = true;
+            return new BatchUpdates(this);
+        }
+
+        private void throwIfDestroyed() {
+            if (mDestroyed) {
+                throw new IllegalStateException("Already called #build()");
+            }
+        }
+    }
+
+    /////////////////////////////////////
+    // Object "contract" methods. //
+    /////////////////////////////////////
+    @Override
+    public String toString() {
+        if (!sDebug) return super.toString();
+
+        return new StringBuilder("BatchUpdates: [")
+                .append(", transformations=")
+                    .append(mTransformations == null ? "N/A" : mTransformations.size())
+                .append(", updates=").append(mUpdates)
+                .append("]").toString();
+    }
+
+    /////////////////////////////////////
+    // Parcelable "contract" methods. //
+    /////////////////////////////////////
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        if (mTransformations == null) {
+            dest.writeIntArray(null);
+        } else {
+            final int size = mTransformations.size();
+            final int[] ids = new int[size];
+            final InternalTransformation[] values = new InternalTransformation[size];
+            for (int i = 0; i < size; i++) {
+                final Pair<Integer, InternalTransformation> pair = mTransformations.get(i);
+                ids[i] = pair.first;
+                values[i] = pair.second;
+            }
+            dest.writeIntArray(ids);
+            dest.writeParcelableArray(values, flags);
+        }
+        dest.writeParcelable(mUpdates, flags);
+    }
+    public static final Parcelable.Creator<BatchUpdates> CREATOR =
+            new Parcelable.Creator<BatchUpdates>() {
+        @Override
+        public BatchUpdates createFromParcel(Parcel parcel) {
+            // Always go through the builder to ensure the data ingested by
+            // the system obeys the contract of the builder to avoid attacks
+            // using specially crafted parcels.
+            final Builder builder = new Builder();
+            final int[] ids = parcel.createIntArray();
+            if (ids != null) {
+                final InternalTransformation[] values =
+                    parcel.readParcelableArray(null, InternalTransformation.class);
+                final int size = ids.length;
+                for (int i = 0; i < size; i++) {
+                    builder.transformChild(ids[i], values[i]);
+                }
+            }
+            final RemoteViews updates = parcel.readParcelable(null);
+            if (updates != null) {
+                builder.updateTemplate(updates);
+            }
+            return builder.build();
+        }
+
+        @Override
+        public BatchUpdates[] newArray(int size) {
+            return new BatchUpdates[size];
+        }
+    };
+}
diff --git a/android/service/autofill/CustomDescription.java b/android/service/autofill/CustomDescription.java
index 9a4cbc4..fd30857 100644
--- a/android/service/autofill/CustomDescription.java
+++ b/android/service/autofill/CustomDescription.java
@@ -19,11 +19,11 @@
 import static android.view.autofill.Helper.sDebug;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.Activity;
 import android.app.PendingIntent;
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.util.Log;
 import android.util.Pair;
 import android.widget.RemoteViews;
 
@@ -67,18 +67,18 @@
  * // Image child - different logo for each bank, based on credit card prefix
  * builder.addChild(R.id.templateccLogo,
  *   new ImageTransformation.Builder(ccNumberId)
- *     .addOption(Pattern.compile(""^4815.*$"), R.drawable.ic_credit_card_logo1)
- *     .addOption(Pattern.compile(""^1623.*$"), R.drawable.ic_credit_card_logo2)
- *     .addOption(Pattern.compile(""^42.*$"), R.drawable.ic_credit_card_logo3)
+ *     .addOption(Pattern.compile("^4815.*$"), R.drawable.ic_credit_card_logo1)
+ *     .addOption(Pattern.compile("^1623.*$"), R.drawable.ic_credit_card_logo2)
+ *     .addOption(Pattern.compile("^42.*$"), R.drawable.ic_credit_card_logo3)
  *     .build();
  * // Masked credit card number (as .....LAST_4_DIGITS)
  * builder.addChild(R.id.templateCcNumber, new CharSequenceTransformation
- *     .Builder(ccNumberId, Pattern.compile(""^.*(\\d\\d\\d\\d)$"), "...$1")
+ *     .Builder(ccNumberId, Pattern.compile("^.*(\\d\\d\\d\\d)$"), "...$1")
  *     .build();
  * // Expiration date as MM / YYYY:
  * builder.addChild(R.id.templateExpDate, new CharSequenceTransformation
- *     .Builder(ccExpMonthId, Pattern.compile(""^(\\d\\d)$"), "Exp: $1")
- *     .addField(ccExpYearId, Pattern.compile(""^(\\d\\d)$"), "/$1")
+ *     .Builder(ccExpMonthId, Pattern.compile("^(\\d\\d)$"), "Exp: $1")
+ *     .addField(ccExpYearId, Pattern.compile("^(\\d\\d)$"), "/$1")
  *     .build();
  * </pre>
  *
@@ -87,47 +87,43 @@
  */
 public final class CustomDescription implements Parcelable {
 
-    private static final String TAG = "CustomDescription";
-
     private final RemoteViews mPresentation;
     private final ArrayList<Pair<Integer, InternalTransformation>> mTransformations;
+    private final ArrayList<Pair<InternalValidator, BatchUpdates>> mUpdates;
 
     private CustomDescription(Builder builder) {
         mPresentation = builder.mPresentation;
         mTransformations = builder.mTransformations;
+        mUpdates = builder.mUpdates;
     }
 
     /** @hide */
-    public RemoteViews getPresentation(ValueFinder finder) {
-        if (mTransformations != null) {
-            final int size = mTransformations.size();
-            if (sDebug) Log.d(TAG, "getPresentation(): applying " + size + " transformations");
-            for (int i = 0; i < size; i++) {
-                final Pair<Integer, InternalTransformation> pair = mTransformations.get(i);
-                final int id = pair.first;
-                final InternalTransformation transformation = pair.second;
-                if (sDebug) Log.d(TAG, "#" + i + ": " + transformation);
-
-                try {
-                    transformation.apply(finder, mPresentation, id);
-                } catch (Exception e) {
-                    // Do not log full exception to avoid PII leaking
-                    Log.e(TAG, "Could not apply transformation " + transformation + ": "
-                            + e.getClass());
-                    return null;
-                }
-            }
-        }
+    @Nullable
+    public RemoteViews getPresentation() {
         return mPresentation;
     }
 
+    /** @hide */
+    @Nullable
+    public ArrayList<Pair<Integer, InternalTransformation>> getTransformations() {
+        return mTransformations;
+    }
+
+    /** @hide */
+    @Nullable
+    public ArrayList<Pair<InternalValidator, BatchUpdates>> getUpdates() {
+        return mUpdates;
+    }
+
     /**
      * Builder for {@link CustomDescription} objects.
      */
     public static class Builder {
         private final RemoteViews mPresentation;
 
+        private boolean mDestroyed;
         private ArrayList<Pair<Integer, InternalTransformation>> mTransformations;
+        private ArrayList<Pair<InternalValidator, BatchUpdates>> mUpdates;
 
         /**
          * Default constructor.
@@ -145,9 +141,11 @@
          * </ul>
          *
          * @param parentPresentation template presentation with (optional) children views.
+         * @throws NullPointerException if {@code parentPresentation} is null (on Android
+         * {@link android.os.Build.VERSION_CODES#P} or higher).
          */
-        public Builder(RemoteViews parentPresentation) {
-            mPresentation = parentPresentation;
+        public Builder(@NonNull RemoteViews parentPresentation) {
+            mPresentation = Preconditions.checkNotNull(parentPresentation);
         }
 
         /**
@@ -164,6 +162,7 @@
          * by the Android System.
          */
         public Builder addChild(int id, @NonNull Transformation transformation) {
+            throwIfDestroyed();
             Preconditions.checkArgument((transformation instanceof InternalTransformation),
                     "not provided by Android System: " + transformation);
             if (mTransformations == null) {
@@ -174,11 +173,109 @@
         }
 
         /**
+         * Updates the {@link RemoteViews presentation template} when a condition is satisfied.
+         *
+         * <p>The updates are applied in the sequence they are added, after the
+         * {@link #addChild(int, Transformation) transformations} are applied to the children
+         * views.
+         *
+         * <p>For example, to make children views visible when fields are not empty:
+         *
+         * <pre class="prettyprint">
+         * RemoteViews template = new RemoteViews(pgkName, R.layout.my_full_template);
+         *
+         * Pattern notEmptyPattern = Pattern.compile(".+");
+         * Validator hasAddress = new RegexValidator(addressAutofillId, notEmptyPattern);
+         * Validator hasCcNumber = new RegexValidator(ccNumberAutofillId, notEmptyPattern);
+         *
+         * RemoteViews addressUpdates = new RemoteViews(pgkName, R.layout.my_full_template)
+         * addressUpdates.setViewVisibility(R.id.address, View.VISIBLE);
+         *
+         * // Make address visible
+         * BatchUpdates addressBatchUpdates = new BatchUpdates.Builder()
+         *     .updateTemplate(addressUpdates)
+         *     .build();
+         *
+         * RemoteViews ccUpdates = new RemoteViews(pgkName, R.layout.my_full_template)
+         * ccUpdates.setViewVisibility(R.id.cc_number, View.VISIBLE);
+         *
+         * // Mask credit card number (as .....LAST_4_DIGITS) and make it visible
+         * BatchUpdates ccBatchUpdates = new BatchUpdates.Builder()
+         *     .updateTemplate(ccUpdates)
+         *     .transformChild(R.id.templateCcNumber, new CharSequenceTransformation
+         *                     .Builder(ccNumberId, Pattern.compile("^.*(\\d\\d\\d\\d)$"), "...$1")
+         *                     .build())
+         *     .build();
+         *
+         * CustomDescription customDescription = new CustomDescription.Builder(template)
+         *     .batchUpdate(hasAddress, addressBatchUpdates)
+         *     .batchUpdate(hasCcNumber, ccBatchUpdates)
+         *     .build();
+         * </pre>
+         *
+         * <p>Another approach is to add a child first, then apply the transformations. Example:
+         *
+         * <pre class="prettyprint">
+         * RemoteViews template = new RemoteViews(pgkName, R.layout.my_base_template);
+         *
+         * RemoteViews addressPresentation = new RemoteViews(pgkName, R.layout.address)
+         * RemoteViews addressUpdates = new RemoteViews(pgkName, R.layout.my_template)
+         * addressUpdates.addView(R.id.parentId, addressPresentation);
+         * BatchUpdates addressBatchUpdates = new BatchUpdates.Builder()
+         *     .updateTemplate(addressUpdates)
+         *     .build();
+         *
+         * RemoteViews ccPresentation = new RemoteViews(pgkName, R.layout.cc)
+         * RemoteViews ccUpdates = new RemoteViews(pgkName, R.layout.my_template)
+         * ccUpdates.addView(R.id.parentId, ccPresentation);
+         * BatchUpdates ccBatchUpdates = new BatchUpdates.Builder()
+         *     .updateTemplate(ccUpdates)
+         *     .transformChild(R.id.templateCcNumber, new CharSequenceTransformation
+         *                     .Builder(ccNumberId, Pattern.compile("^.*(\\d\\d\\d\\d)$"), "...$1")
+         *                     .build())
+         *     .build();
+         *
+         * CustomDescription customDescription = new CustomDescription.Builder(template)
+         *     .batchUpdate(hasAddress, addressBatchUpdates)
+         *     .batchUpdate(hasCcNumber, ccBatchUpdates)
+         *     .build();
+         * </pre>
+         *
+         * @param condition condition used to trigger the updates.
+         * @param updates actions to be applied to the
+         * {@link #CustomDescription.Builder(RemoteViews) template presentation} when the condition
+         * is satisfied.
+         *
+         * @return this builder
+         * @throws IllegalArgumentException if {@code condition} is not a class provided
+         * by the Android System.
+         */
+        public Builder batchUpdate(@NonNull Validator condition, @NonNull BatchUpdates updates) {
+            throwIfDestroyed();
+            Preconditions.checkArgument((condition instanceof InternalValidator),
+                    "not provided by Android System: " + condition);
+            Preconditions.checkNotNull(updates);
+            if (mUpdates == null) {
+                mUpdates = new ArrayList<>();
+            }
+            mUpdates.add(new Pair<>((InternalValidator) condition, updates));
+            return this;
+        }
+
+        /**
          * Creates a new {@link CustomDescription} instance.
          */
         public CustomDescription build() {
+            throwIfDestroyed();
+            mDestroyed = true;
             return new CustomDescription(this);
         }
+
+        private void throwIfDestroyed() {
+            if (mDestroyed) {
+                throw new IllegalStateException("Already called #build()");
+            }
+        }
     }
 
     /////////////////////////////////////
@@ -190,7 +287,10 @@
 
         return new StringBuilder("CustomDescription: [presentation=")
                 .append(mPresentation)
-                .append(", transformations=").append(mTransformations)
+                .append(", transformations=")
+                    .append(mTransformations == null ? "N/A" : mTransformations.size())
+                .append(", updates=")
+                    .append(mUpdates == null ? "N/A" : mUpdates.size())
                 .append("]").toString();
     }
 
@@ -205,6 +305,8 @@
     @Override
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeParcelable(mPresentation, flags);
+        if (mPresentation == null) return;
+
         if (mTransformations == null) {
             dest.writeIntArray(null);
         } else {
@@ -219,6 +321,21 @@
             dest.writeIntArray(ids);
             dest.writeParcelableArray(values, flags);
         }
+        if (mUpdates == null) {
+            dest.writeParcelableArray(null, flags);
+        } else {
+            final int size = mUpdates.size();
+            final InternalValidator[] conditions = new InternalValidator[size];
+            final BatchUpdates[] updates = new BatchUpdates[size];
+
+            for (int i = 0; i < size; i++) {
+                final Pair<InternalValidator, BatchUpdates> pair = mUpdates.get(i);
+                conditions[i] = pair.first;
+                updates[i] = pair.second;
+            }
+            dest.writeParcelableArray(conditions, flags);
+            dest.writeParcelableArray(updates, flags);
+        }
     }
     public static final Parcelable.Creator<CustomDescription> CREATOR =
             new Parcelable.Creator<CustomDescription>() {
@@ -227,7 +344,10 @@
             // Always go through the builder to ensure the data ingested by
             // the system obeys the contract of the builder to avoid attacks
             // using specially crafted parcels.
-            final Builder builder = new Builder(parcel.readParcelable(null));
+            final RemoteViews parentPresentation = parcel.readParcelable(null);
+            if (parentPresentation == null) return null;
+
+            final Builder builder = new Builder(parentPresentation);
             final int[] ids = parcel.createIntArray();
             if (ids != null) {
                 final InternalTransformation[] values =
@@ -237,6 +357,15 @@
                     builder.addChild(ids[i], values[i]);
                 }
             }
+            final InternalValidator[] conditions =
+                    parcel.readParcelableArray(null, InternalValidator.class);
+            if (conditions != null) {
+                final BatchUpdates[] updates = parcel.readParcelableArray(null, BatchUpdates.class);
+                final int size = conditions.length;
+                for (int i = 0; i < size; i++) {
+                    builder.batchUpdate(conditions[i], updates[i]);
+                }
+            }
             return builder.build();
         }
 
diff --git a/android/service/autofill/Dataset.java b/android/service/autofill/Dataset.java
index ef9598a..331130e 100644
--- a/android/service/autofill/Dataset.java
+++ b/android/service/autofill/Dataset.java
@@ -150,8 +150,16 @@
     public String toString() {
         if (!sDebug) return super.toString();
 
-        return new StringBuilder("Dataset " + mId + " [")
-                .append("fieldIds=").append(mFieldIds)
+        final StringBuilder builder = new StringBuilder("Dataset[id=");
+        if (mId == null) {
+            builder.append("null");
+        } else {
+            // Cannot disclose id because it could contain PII.
+            builder.append(mId.length()).append("_chars");
+        }
+
+        return builder
+                .append(", fieldIds=").append(mFieldIds)
                 .append(", fieldValues=").append(mFieldValues)
                 .append(", fieldPresentations=")
                 .append(mFieldPresentations == null ? 0 : mFieldPresentations.size())
@@ -201,7 +209,8 @@
          * Creates a new builder for a dataset where each field will be visualized independently.
          *
          * <p>When using this constructor, fields must be set through
-         * {@link #setValue(AutofillId, AutofillValue, RemoteViews)}.
+         * {@link #setValue(AutofillId, AutofillValue, RemoteViews)} or
+         * {@link #setValue(AutofillId, AutofillValue, Pattern, RemoteViews)}.
          */
         public Builder() {
         }
@@ -280,19 +289,24 @@
         /**
          * Sets the value of a field.
          *
+         * <b>Note:</b> Prior to Android {@link android.os.Build.VERSION_CODES#P}, this method would
+         * throw an {@link IllegalStateException} if this builder was constructed without a
+         * {@link RemoteViews presentation}. Android {@link android.os.Build.VERSION_CODES#P} and
+         * higher removed this restriction because datasets used as an
+         * {@link android.view.autofill.AutofillManager#EXTRA_AUTHENTICATION_RESULT
+         * authentication result} do not need a presentation. But if you don't set the presentation
+         * in the constructor in a dataset that is meant to be shown to the user, the autofill UI
+         * for this field will not be displayed.
+         *
          * @param id id returned by {@link
          *         android.app.assist.AssistStructure.ViewNode#getAutofillId()}.
          * @param value value to be autofilled. Pass {@code null} if you do not have the value
          *        but the target view is a logical part of the dataset. For example, if
          *        the dataset needs authentication and you have no access to the value.
          * @return this builder.
-         * @throws IllegalStateException if the builder was constructed without a
-         * {@link RemoteViews presentation}.
          */
         public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value) {
             throwIfDestroyed();
-            Preconditions.checkState(mPresentation != null,
-                    "Dataset presentation not set on constructor");
             setLifeTheUniverseAndEverything(id, value, null, null);
             return this;
         }
@@ -334,7 +348,7 @@
          *
          * @return this builder.
          * @throws IllegalStateException if the builder was constructed without a
-         * {@link RemoteViews presentation}.
+         *         {@link RemoteViews presentation}.
          */
         public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value,
                 @NonNull Pattern filter) {
diff --git a/android/service/autofill/FillEventHistory.java b/android/service/autofill/FillEventHistory.java
index 3b719ac..b1857b3 100644
--- a/android/service/autofill/FillEventHistory.java
+++ b/android/service/autofill/FillEventHistory.java
@@ -17,19 +17,27 @@
 package android.service.autofill;
 
 import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.IntentSender;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.view.autofill.AutofillId;
 import android.view.autofill.AutofillManager;
 
+import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.Preconditions;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
 
 /**
  * Describes what happened after the last
@@ -121,6 +129,11 @@
     }
 
     @Override
+    public String toString() {
+        return mEvents == null ? "no events" : mEvents.toString();
+    }
+
+    @Override
     public int describeContents() {
         return 0;
     }
@@ -136,9 +149,21 @@
             int numEvents = mEvents.size();
             for (int i = 0; i < numEvents; i++) {
                 Event event = mEvents.get(i);
-                dest.writeInt(event.getType());
-                dest.writeString(event.getDatasetId());
-                dest.writeBundle(event.getClientState());
+                dest.writeInt(event.mEventType);
+                dest.writeString(event.mDatasetId);
+                dest.writeBundle(event.mClientState);
+                dest.writeStringList(event.mSelectedDatasetIds);
+                dest.writeArraySet(event.mIgnoredDatasetIds);
+                dest.writeTypedList(event.mChangedFieldIds);
+                dest.writeStringList(event.mChangedDatasetIds);
+
+                dest.writeTypedList(event.mManuallyFilledFieldIds);
+                if (event.mManuallyFilledFieldIds != null) {
+                    final int size = event.mManuallyFilledFieldIds.size();
+                    for (int j = 0; j < size; j++) {
+                        dest.writeStringList(event.mManuallyFilledDatasetIds.get(j));
+                    }
+                }
             }
         }
     }
@@ -176,12 +201,40 @@
         /** A save UI was shown. */
         public static final int TYPE_SAVE_SHOWN = 3;
 
+        /**
+         * A committed autofill context for which the autofill service provided datasets.
+         *
+         * <p>This event is useful to track:
+         * <ul>
+         *   <li>Which datasets (if any) were selected by the user
+         *       ({@link #getSelectedDatasetIds()}).
+         *   <li>Which datasets (if any) were NOT selected by the user
+         *       ({@link #getIgnoredDatasetIds()}).
+         *   <li>Which fields in the selected datasets were changed by the user after the dataset
+         *       was selected ({@link #getChangedFields()}.
+         * </ul>
+         *
+         * <p><b>Note: </b>This event is only generated when:
+         * <ul>
+         *   <li>The autofill context is committed.
+         *   <li>The service provides at least one dataset in the
+         *       {@link FillResponse fill responses} associated with the context.
+         *   <li>The last {@link FillResponse fill responses} associated with the context has the
+         *       {@link FillResponse#FLAG_TRACK_CONTEXT_COMMITED} flag.
+         * </ul>
+         *
+         * <p>See {@link android.view.autofill.AutofillManager} for more information about autofill
+         * contexts.
+         */
+        public static final int TYPE_CONTEXT_COMMITTED = 4;
+
         /** @hide */
         @IntDef(
                 value = {TYPE_DATASET_SELECTED,
                         TYPE_DATASET_AUTHENTICATION_SELECTED,
                         TYPE_AUTHENTICATION_SELECTED,
-                        TYPE_SAVE_SHOWN})
+                        TYPE_SAVE_SHOWN,
+                        TYPE_CONTEXT_COMMITTED})
         @Retention(RetentionPolicy.SOURCE)
         @interface EventIds{}
 
@@ -189,6 +242,17 @@
         @Nullable private final String mDatasetId;
         @Nullable private final Bundle mClientState;
 
+        // Note: mSelectedDatasetIds is stored as List<> instead of Set because Session already
+        // stores it as List
+        @Nullable private final List<String> mSelectedDatasetIds;
+        @Nullable private final ArraySet<String> mIgnoredDatasetIds;
+
+        @Nullable private final ArrayList<AutofillId> mChangedFieldIds;
+        @Nullable private final ArrayList<String> mChangedDatasetIds;
+
+        @Nullable private final ArrayList<AutofillId> mManuallyFilledFieldIds;
+        @Nullable private final ArrayList<ArrayList<String>> mManuallyFilledDatasetIds;
+
         /**
          * Returns the type of the event.
          *
@@ -220,25 +284,202 @@
         }
 
         /**
+         * Returns which datasets were selected by the user.
+         *
+         * <p><b>Note: </b>Only set on events of type {@link #TYPE_CONTEXT_COMMITTED}.
+         */
+        @NonNull public Set<String> getSelectedDatasetIds() {
+            return mSelectedDatasetIds == null ? Collections.emptySet()
+                    : new ArraySet<>(mSelectedDatasetIds);
+        }
+
+        /**
+         * Returns which datasets were NOT selected by the user.
+         *
+         * <p><b>Note: </b>Only set on events of type {@link #TYPE_CONTEXT_COMMITTED}.
+         */
+        @NonNull public Set<String> getIgnoredDatasetIds() {
+            return mIgnoredDatasetIds == null ? Collections.emptySet() : mIgnoredDatasetIds;
+        }
+
+        /**
+         * Returns which fields in the selected datasets were changed by the user after the dataset
+         * was selected.
+         *
+         * <p>For example, server provides:
+         *
+         * <pre class="prettyprint">
+         *  FillResponse response = new FillResponse.Builder()
+         *      .addDataset(new Dataset.Builder(presentation1)
+         *          .setId("4815")
+         *          .setValue(usernameId, AutofillValue.forText("MrPlow"))
+         *          .build())
+         *      .addDataset(new Dataset.Builder(presentation2)
+         *          .setId("162342")
+         *          .setValue(passwordId, AutofillValue.forText("D'OH"))
+         *          .build())
+         *      .build();
+         * </pre>
+         *
+         * <p>User select both datasets (for username and password) but after the fields are
+         * autofilled, user changes them to:
+         *
+         * <pre class="prettyprint">
+         *   username = "ElBarto";
+         *   password = "AyCaramba";
+         * </pre>
+         *
+         * <p>Then the result is the following map:
+         *
+         * <pre class="prettyprint">
+         *   usernameId => "4815"
+         *   passwordId => "162342"
+         * </pre>
+         *
+         * <p><b>Note: </b>Only set on events of type {@link #TYPE_CONTEXT_COMMITTED}.
+         *
+         * @return map map whose key is the id of the change fields, and value is the id of
+         * dataset that has that field and was selected by the user.
+         */
+        @NonNull public Map<AutofillId, String> getChangedFields() {
+            if (mChangedFieldIds == null || mChangedDatasetIds == null) {
+                return Collections.emptyMap();
+            }
+
+            final int size = mChangedFieldIds.size();
+            final ArrayMap<AutofillId, String> changedFields = new ArrayMap<>(size);
+            for (int i = 0; i < size; i++) {
+                changedFields.put(mChangedFieldIds.get(i), mChangedDatasetIds.get(i));
+            }
+            return changedFields;
+        }
+
+        /**
+         * Returns which fields were available on datasets provided by the service but manually
+         * entered by the user.
+         *
+         * <p>For example, server provides:
+         *
+         * <pre class="prettyprint">
+         *  FillResponse response = new FillResponse.Builder()
+         *      .addDataset(new Dataset.Builder(presentation1)
+         *          .setId("4815")
+         *          .setValue(usernameId, AutofillValue.forText("MrPlow"))
+         *          .setValue(passwordId, AutofillValue.forText("AyCaramba"))
+         *          .build())
+         *      .addDataset(new Dataset.Builder(presentation2)
+         *          .setId("162342")
+         *          .setValue(usernameId, AutofillValue.forText("ElBarto"))
+         *          .setValue(passwordId, AutofillValue.forText("D'OH"))
+         *          .build())
+         *      .addDataset(new Dataset.Builder(presentation3)
+         *          .setId("108")
+         *          .setValue(usernameId, AutofillValue.forText("MrPlow"))
+         *          .setValue(passwordId, AutofillValue.forText("D'OH"))
+         *          .build())
+         *      .build();
+         * </pre>
+         *
+         * <p>User doesn't select a dataset but manually enters:
+         *
+         * <pre class="prettyprint">
+         *   username = "MrPlow";
+         *   password = "D'OH";
+         * </pre>
+         *
+         * <p>Then the result is the following map:
+         *
+         * <pre class="prettyprint">
+         *   usernameId => { "4815", "108"}
+         *   passwordId => { "162342", "108" }
+         * </pre>
+         *
+         * <p><b>Note: </b>Only set on events of type {@link #TYPE_CONTEXT_COMMITTED}.
+         *
+         * @return map map whose key is the id of the manually-entered field, and value is the
+         * ids of the datasets that have that value but were not selected by the user.
+         */
+        @Nullable public Map<AutofillId, Set<String>> getManuallyEnteredField() {
+            if (mManuallyFilledFieldIds == null || mManuallyFilledDatasetIds == null) {
+                return Collections.emptyMap();
+            }
+
+            final int size = mManuallyFilledFieldIds.size();
+            final Map<AutofillId, Set<String>> manuallyFilledFields = new ArrayMap<>(size);
+            for (int i = 0; i < size; i++) {
+                final AutofillId fieldId = mManuallyFilledFieldIds.get(i);
+                final ArrayList<String> datasetIds = mManuallyFilledDatasetIds.get(i);
+                manuallyFilledFields.put(fieldId, new ArraySet<>(datasetIds));
+            }
+            return manuallyFilledFields;
+        }
+
+        /**
          * Creates a new event.
          *
          * @param eventType The type of the event
          * @param datasetId The dataset the event was on, or {@code null} if the event was on the
          *                  whole response.
          * @param clientState The client state associated with the event.
+         * @param selectedDatasetIds The ids of datasets selected by the user.
+         * @param ignoredDatasetIds The ids of datasets NOT select by the user.
+         * @param changedFieldIds The ids of fields changed by the user.
+         * @param changedDatasetIds The ids of the datasets that havd values matching the
+         * respective entry on {@code changedFieldIds}.
+         * @param manuallyFilledFieldIds The ids of fields that were manually entered by the user
+         * and belonged to datasets.
+         * @param manuallyFilledDatasetIds The ids of datasets that had values matching the
+         * respective entry on {@code manuallyFilledFieldIds}.
+         *
+         * @throws IllegalArgumentException If the length of {@code changedFieldIds} and
+         * {@code changedDatasetIds} doesn't match.
+         * @throws IllegalArgumentException If the length of {@code manuallyFilledFieldIds} and
+         * {@code manuallyFilledDatasetIds} doesn't match.
          *
          * @hide
          */
-        public Event(int eventType, @Nullable String datasetId, @Nullable Bundle clientState) {
-            mEventType = Preconditions.checkArgumentInRange(eventType, 0, TYPE_SAVE_SHOWN,
+        public Event(int eventType, @Nullable String datasetId, @Nullable Bundle clientState,
+                @Nullable List<String> selectedDatasetIds,
+                @Nullable ArraySet<String> ignoredDatasetIds,
+                @Nullable ArrayList<AutofillId> changedFieldIds,
+                @Nullable ArrayList<String> changedDatasetIds,
+                @Nullable ArrayList<AutofillId> manuallyFilledFieldIds,
+                @Nullable ArrayList<ArrayList<String>> manuallyFilledDatasetIds) {
+            mEventType = Preconditions.checkArgumentInRange(eventType, 0, TYPE_CONTEXT_COMMITTED,
                     "eventType");
             mDatasetId = datasetId;
             mClientState = clientState;
+            mSelectedDatasetIds = selectedDatasetIds;
+            mIgnoredDatasetIds = ignoredDatasetIds;
+            if (changedFieldIds != null) {
+                Preconditions.checkArgument(!ArrayUtils.isEmpty(changedFieldIds)
+                        && changedDatasetIds != null
+                        && changedFieldIds.size() == changedDatasetIds.size(),
+                        "changed ids must have same length and not be empty");
+            }
+            mChangedFieldIds = changedFieldIds;
+            mChangedDatasetIds = changedDatasetIds;
+            if (manuallyFilledFieldIds != null) {
+                Preconditions.checkArgument(!ArrayUtils.isEmpty(manuallyFilledFieldIds)
+                        && manuallyFilledDatasetIds != null
+                        && manuallyFilledFieldIds.size() == manuallyFilledDatasetIds.size(),
+                        "manually filled ids must have same length and not be empty");
+            }
+            mManuallyFilledFieldIds = manuallyFilledFieldIds;
+            mManuallyFilledDatasetIds = manuallyFilledDatasetIds;
         }
 
         @Override
         public String toString() {
-            return "FillEvent [datasetId=" + mDatasetId + ", type=" + mEventType + "]";
+            return "FillEvent [datasetId=" + mDatasetId
+                    + ", type=" + mEventType
+                    + ", selectedDatasets=" + mSelectedDatasetIds
+                    + ", ignoredDatasetIds=" + mIgnoredDatasetIds
+                    + ", changedFieldIds=" + mChangedFieldIds
+                    + ", changedDatasetsIds=" + mChangedDatasetIds
+                    + ", manuallyFilledFieldIds=" + mManuallyFilledFieldIds
+                    + ", manuallyFilledDatasetIds=" + mManuallyFilledDatasetIds
+                    + "]";
         }
     }
 
@@ -248,12 +489,37 @@
                 public FillEventHistory createFromParcel(Parcel parcel) {
                     FillEventHistory selection = new FillEventHistory(0, 0, parcel.readBundle());
 
-                    int numEvents = parcel.readInt();
+                    final int numEvents = parcel.readInt();
                     for (int i = 0; i < numEvents; i++) {
-                        selection.addEvent(new Event(parcel.readInt(), parcel.readString(),
-                                parcel.readBundle()));
-                    }
+                        final int eventType = parcel.readInt();
+                        final String datasetId = parcel.readString();
+                        final Bundle clientState = parcel.readBundle();
+                        final ArrayList<String> selectedDatasetIds = parcel.createStringArrayList();
+                        @SuppressWarnings("unchecked")
+                        final ArraySet<String> ignoredDatasets =
+                                (ArraySet<String>) parcel.readArraySet(null);
+                        final ArrayList<AutofillId> changedFieldIds =
+                                parcel.createTypedArrayList(AutofillId.CREATOR);
+                        final ArrayList<String> changedDatasetIds = parcel.createStringArrayList();
 
+                        final ArrayList<AutofillId> manuallyFilledFieldIds =
+                                parcel.createTypedArrayList(AutofillId.CREATOR);
+                        final ArrayList<ArrayList<String>> manuallyFilledDatasetIds;
+                        if (manuallyFilledFieldIds != null) {
+                            final int size = manuallyFilledFieldIds.size();
+                            manuallyFilledDatasetIds = new ArrayList<>(size);
+                            for (int j = 0; j < size; j++) {
+                                manuallyFilledDatasetIds.add(parcel.createStringArrayList());
+                            }
+                        } else {
+                            manuallyFilledDatasetIds = null;
+                        }
+
+                        selection.addEvent(new Event(eventType, datasetId, clientState,
+                                selectedDatasetIds, ignoredDatasets,
+                                changedFieldIds, changedDatasetIds,
+                                manuallyFilledFieldIds, manuallyFilledDatasetIds));
+                    }
                     return selection;
                 }
 
diff --git a/android/service/autofill/FillResponse.java b/android/service/autofill/FillResponse.java
index 6d8a959..d2033fa 100644
--- a/android/service/autofill/FillResponse.java
+++ b/android/service/autofill/FillResponse.java
@@ -19,6 +19,7 @@
 import static android.service.autofill.FillRequest.INVALID_REQUEST_ID;
 import static android.view.autofill.Helper.sDebug;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.Activity;
@@ -30,6 +31,8 @@
 import android.view.autofill.AutofillId;
 import android.widget.RemoteViews;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -42,6 +45,19 @@
  */
 public final class FillResponse implements Parcelable {
 
+    /**
+     * Must be set in the last response to generate
+     * {@link FillEventHistory.Event#TYPE_CONTEXT_COMMITTED} events.
+     */
+    public static final int FLAG_TRACK_CONTEXT_COMMITED = 0x1;
+
+    /** @hide */
+    @IntDef(flag = true, value = {
+            FLAG_TRACK_CONTEXT_COMMITED
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface FillResponseFlags {}
+
     private final @Nullable ParceledListSlice<Dataset> mDatasets;
     private final @Nullable SaveInfo mSaveInfo;
     private final @Nullable Bundle mClientState;
@@ -49,16 +65,18 @@
     private final @Nullable IntentSender mAuthentication;
     private final @Nullable AutofillId[] mAuthenticationIds;
     private final @Nullable AutofillId[] mIgnoredIds;
+    private final int mFlags;
     private int mRequestId;
 
     private FillResponse(@NonNull Builder builder) {
         mDatasets = (builder.mDatasets != null) ? new ParceledListSlice<>(builder.mDatasets) : null;
         mSaveInfo = builder.mSaveInfo;
-        mClientState = builder.mCLientState;
+        mClientState = builder.mClientState;
         mPresentation = builder.mPresentation;
         mAuthentication = builder.mAuthentication;
         mAuthenticationIds = builder.mAuthenticationIds;
         mIgnoredIds = builder.mIgnoredIds;
+        mFlags = builder.mFlags;
         mRequestId = INVALID_REQUEST_ID;
     }
 
@@ -97,6 +115,11 @@
         return mIgnoredIds;
     }
 
+    /** @hide */
+    public int getFlags() {
+        return mFlags;
+    }
+
     /**
      * Associates a {@link FillResponse} to a request.
      *
@@ -122,11 +145,12 @@
     public static final class Builder {
         private ArrayList<Dataset> mDatasets;
         private SaveInfo mSaveInfo;
-        private Bundle mCLientState;
+        private Bundle mClientState;
         private RemoteViews mPresentation;
         private IntentSender mAuthentication;
         private AutofillId[] mAuthenticationIds;
         private AutofillId[] mIgnoredIds;
+        private int mFlags;
         private boolean mDestroyed;
 
         /**
@@ -264,7 +288,20 @@
          */
         public Builder setClientState(@Nullable Bundle clientState) {
             throwIfDestroyed();
-            mCLientState = clientState;
+            mClientState = clientState;
+            return this;
+        }
+
+        /**
+         * Sets flags changing the response behavior.
+         *
+         * @param flags {@link #FLAG_TRACK_CONTEXT_COMMITED}, or {@code 0}.
+         *
+         * @return This builder.
+         */
+        public Builder setFlags(@FillResponseFlags int flags) {
+            throwIfDestroyed();
+            mFlags = flags;
             return this;
         }
 
@@ -311,6 +348,7 @@
                 .append(", hasAuthentication=").append(mAuthentication != null)
                 .append(", authenticationIds=").append(Arrays.toString(mAuthenticationIds))
                 .append(", ignoredIds=").append(Arrays.toString(mIgnoredIds))
+                .append(", flags=").append(mFlags)
                 .append("]")
                 .toString();
     }
@@ -333,6 +371,7 @@
         parcel.writeParcelable(mAuthentication, flags);
         parcel.writeParcelable(mPresentation, flags);
         parcel.writeParcelableArray(mIgnoredIds, flags);
+        parcel.writeInt(mFlags);
         parcel.writeInt(mRequestId);
     }
 
@@ -363,8 +402,9 @@
             }
 
             builder.setIgnoredIds(parcel.readParcelableArray(null, AutofillId.class));
-            final FillResponse response = builder.build();
+            builder.setFlags(parcel.readInt());
 
+            final FillResponse response = builder.build();
             response.setRequestId(parcel.readInt());
 
             return response;
diff --git a/android/service/autofill/InternalTransformation.java b/android/service/autofill/InternalTransformation.java
index 974397b..c9864a0 100644
--- a/android/service/autofill/InternalTransformation.java
+++ b/android/service/autofill/InternalTransformation.java
@@ -15,17 +15,27 @@
  */
 package android.service.autofill;
 
+import static android.view.autofill.Helper.sDebug;
+
 import android.annotation.NonNull;
+import android.annotation.TestApi;
 import android.os.Parcelable;
+import android.util.Log;
+import android.util.Pair;
 import android.widget.RemoteViews;
 
+import java.util.ArrayList;
+
 /**
  * Superclass of all transformation the system understands. As this is not public all
  * subclasses have to implement {@link Transformation} again.
  *
  * @hide
  */
-abstract class InternalTransformation implements Transformation, Parcelable {
+@TestApi
+public abstract class InternalTransformation implements Transformation, Parcelable {
+
+    private static final String TAG = "InternalTransformation";
 
     /**
      * Applies this transformation to a child view of a {@link android.widget.RemoteViews
@@ -39,4 +49,37 @@
      */
     abstract void apply(@NonNull ValueFinder finder, @NonNull RemoteViews template,
             int childViewId) throws Exception;
+
+    /**
+     * Applies multiple transformations to the children views of a
+     * {@link android.widget.RemoteViews presentation template}.
+     *
+     * @param finder object used to find the value of a field in the screen.
+     * @param template the {@link RemoteViews presentation template}.
+     * @param transformations map of resource id of the child view inside the template to
+     * transformation.
+     *
+     * @hide
+     */
+    public static boolean batchApply(@NonNull ValueFinder finder, @NonNull RemoteViews template,
+            @NonNull ArrayList<Pair<Integer, InternalTransformation>> transformations) {
+        final int size = transformations.size();
+        if (sDebug) Log.d(TAG, "getPresentation(): applying " + size + " transformations");
+        for (int i = 0; i < size; i++) {
+            final Pair<Integer, InternalTransformation> pair = transformations.get(i);
+            final int id = pair.first;
+            final InternalTransformation transformation = pair.second;
+            if (sDebug) Log.d(TAG, "#" + i + ": " + transformation);
+
+            try {
+                transformation.apply(finder, template, id);
+            } catch (Exception e) {
+                // Do not log full exception to avoid PII leaking
+                Log.e(TAG, "Could not apply transformation " + transformation + ": "
+                        + e.getClass());
+                return false;
+            }
+        }
+        return true;
+    }
 }
diff --git a/android/service/autofill/InternalValidator.java b/android/service/autofill/InternalValidator.java
index e11cf6a..e08bb6c 100644
--- a/android/service/autofill/InternalValidator.java
+++ b/android/service/autofill/InternalValidator.java
@@ -16,6 +16,7 @@
 package android.service.autofill;
 
 import android.annotation.NonNull;
+import android.annotation.TestApi;
 import android.os.Parcelable;
 
 /**
@@ -24,6 +25,7 @@
  *
  * @hide
  */
+@TestApi
 public abstract class InternalValidator implements Validator, Parcelable {
 
     /**
@@ -34,5 +36,6 @@
      *
      * @hide
      */
+    @TestApi
     public abstract boolean isValid(@NonNull ValueFinder finder);
 }
diff --git a/android/service/autofill/LuhnChecksumValidator.java b/android/service/autofill/LuhnChecksumValidator.java
index 0b5930d..c56ae84 100644
--- a/android/service/autofill/LuhnChecksumValidator.java
+++ b/android/service/autofill/LuhnChecksumValidator.java
@@ -27,6 +27,8 @@
 
 import com.android.internal.util.Preconditions;
 
+import java.util.Arrays;
+
 /**
  * Validator that returns {@code true} if the number created by concatenating all given fields
  * pass a Luhn algorithm checksum. All non-digits are ignored.
@@ -86,17 +88,27 @@
     public boolean isValid(@NonNull ValueFinder finder) {
         if (mIds == null || mIds.length == 0) return false;
 
-        final StringBuilder number = new StringBuilder();
+        final StringBuilder builder = new StringBuilder();
         for (AutofillId id : mIds) {
             final String partialNumber = finder.findByAutofillId(id);
             if (partialNumber == null) {
                 if (sDebug) Log.d(TAG, "No partial number for id " + id);
                 return false;
             }
-            number.append(partialNumber);
+            builder.append(partialNumber);
         }
 
-        return isLuhnChecksumValid(number.toString());
+        final String number = builder.toString();
+        boolean valid = isLuhnChecksumValid(number);
+        if (sDebug) Log.d(TAG, "isValid(" + number.length() + " chars): " + valid);
+        return valid;
+    }
+
+    @Override
+    public String toString() {
+        if (!sDebug) return super.toString();
+
+        return "LuhnChecksumValidator: [ids=" + Arrays.toString(mIds) + "]";
     }
 
     /////////////////////////////////////
diff --git a/android/service/autofill/OptionalValidators.java b/android/service/autofill/OptionalValidators.java
index f7edd6e..7aec59f 100644
--- a/android/service/autofill/OptionalValidators.java
+++ b/android/service/autofill/OptionalValidators.java
@@ -21,6 +21,7 @@
 import android.annotation.NonNull;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.util.Log;
 
 import com.android.internal.util.Preconditions;
 
@@ -34,6 +35,8 @@
  */
 final class OptionalValidators extends InternalValidator {
 
+    private static final String TAG = "OptionalValidators";
+
     @NonNull private final InternalValidator[] mValidators;
 
     OptionalValidators(@NonNull InternalValidator[] validators) {
@@ -44,6 +47,7 @@
     public boolean isValid(@NonNull ValueFinder finder) {
         for (InternalValidator validator : mValidators) {
             final boolean valid = validator.isValid(finder);
+            if (sDebug) Log.d(TAG, "isValid(" + validator + "): " + valid);
             if (valid) return true;
         }
 
diff --git a/android/service/autofill/RequiredValidators.java b/android/service/autofill/RequiredValidators.java
index ac85c28..9e1db2b 100644
--- a/android/service/autofill/RequiredValidators.java
+++ b/android/service/autofill/RequiredValidators.java
@@ -21,6 +21,7 @@
 import android.annotation.NonNull;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.util.Log;
 
 import com.android.internal.util.Preconditions;
 
@@ -34,6 +35,8 @@
  */
 final class RequiredValidators extends InternalValidator {
 
+    private static final String TAG = "RequiredValidators";
+
     @NonNull private final InternalValidator[] mValidators;
 
     RequiredValidators(@NonNull InternalValidator[] validators) {
@@ -44,6 +47,7 @@
     public boolean isValid(@NonNull ValueFinder finder) {
         for (InternalValidator validator : mValidators) {
             final boolean valid = validator.isValid(finder);
+            if (sDebug) Log.d(TAG, "isValid(" + validator + "): " + valid);
             if (!valid) return false;
         }
         return true;
diff --git a/android/service/autofill/SaveInfo.java b/android/service/autofill/SaveInfo.java
index 1b9240c..fde2416 100644
--- a/android/service/autofill/SaveInfo.java
+++ b/android/service/autofill/SaveInfo.java
@@ -68,7 +68,7 @@
  *       .build();
  * </pre>
  *
- * <p>The save type flags are used to display the appropriate strings in the save UI affordance.
+ * <p>The save type flags are used to display the appropriate strings in the autofill save UI.
  * You can pass multiple values, but try to keep it short if possible. In the above example, just
  * {@code SaveInfo.SAVE_DATA_TYPE_PASSWORD} would be enough.
  *
@@ -103,13 +103,17 @@
  *       .build();
  * </pre>
  *
+ * <a name="TriggeringSaveRequest"></a>
+ * <h3>Triggering a save request</h3>
+ *
  * <p>The {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)} can be triggered after
  * any of the following events:
  * <ul>
  *   <li>The {@link Activity} finishes.
- *   <li>The app explicitly called {@link AutofillManager#commit()}.
- *   <li>All required views became invisible (if the {@link SaveInfo} was created with the
+ *   <li>The app explicitly calls {@link AutofillManager#commit()}.
+ *   <li>All required views become invisible (if the {@link SaveInfo} was created with the
  *       {@link #FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE} flag).
+ *   <li>The user clicks a specific view (defined by {@link Builder#setTriggerId(AutofillId)}.
  * </ul>
  *
  * <p>But it is only triggered when all conditions below are met:
@@ -123,10 +127,13 @@
  *   <li>There is no {@link Dataset} in the last {@link FillResponse} that completely matches the
  *       screen state (i.e., all required and optional fields in the dataset have the same value as
  *       the fields in the screen).
- *   <li>The user explicitly tapped the UI affordance asking to save data for autofill.
+ *   <li>The user explicitly tapped the autofill save UI asking to save data for autofill.
  * </ul>
  *
- * <p>The service can also customize some aspects of the save UI affordance:
+ * <a name="CustomizingSaveUI"></a>
+ * <h3>Customizing the autofill save UI</h3>
+ *
+ * <p>The service can also customize some aspects of the autofill save UI:
  * <ul>
  *   <li>Add a simple subtitle by calling {@link Builder#setDescription(CharSequence)}.
  *   <li>Add a customized subtitle by calling
@@ -212,16 +219,25 @@
     @interface SaveDataType{}
 
     /**
-     * Usually {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)}
-     * is called once the {@link Activity} finishes. If this flag is set it is called once all
-     * saved views become invisible.
+     * Usually, a save request is only automatically <a href="#TriggeringSaveRequest">triggered</a>
+     * once the {@link Activity} finishes. If this flag is set, it is triggered once all saved views
+     * become invisible.
      */
     public static final int FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE = 0x1;
 
+    /**
+     * By default, a save request is automatically <a href="#TriggeringSaveRequest">triggered</a>
+     * once the {@link Activity} finishes. If this flag is set, finishing the activity doesn't
+     * trigger a save request.
+     *
+     * <p>This flag is typically used in conjunction with {@link Builder#setTriggerId(AutofillId)}.
+     */
+    public static final int FLAG_DONT_SAVE_ON_FINISH = 0x2;
+
     /** @hide */
     @IntDef(
             flag = true,
-            value = {FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE})
+            value = {FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE, FLAG_DONT_SAVE_ON_FINISH})
     @Retention(RetentionPolicy.SOURCE)
     @interface SaveInfoFlags{}
 
@@ -236,6 +252,7 @@
     private final InternalValidator mValidator;
     private final InternalSanitizer[] mSanitizerKeys;
     private final AutofillId[][] mSanitizerValues;
+    private final AutofillId mTriggerId;
 
     private SaveInfo(Builder builder) {
         mType = builder.mType;
@@ -259,6 +276,7 @@
                 mSanitizerValues[i] = builder.mSanitizers.valueAt(i);
             }
         }
+        mTriggerId = builder.mTriggerId;
     }
 
     /** @hide */
@@ -320,6 +338,12 @@
         return mSanitizerValues;
     }
 
+    /** @hide */
+    @Nullable
+    public AutofillId getTriggerId() {
+        return mTriggerId;
+    }
+
     /**
      * A builder for {@link SaveInfo} objects.
      */
@@ -338,6 +362,7 @@
         private ArrayMap<InternalSanitizer, AutofillId[]> mSanitizers;
         // Set used to validate against duplicate ids.
         private ArraySet<AutofillId> mSanitizerIds;
+        private AutofillId mTriggerId;
 
         /**
          * Creates a new builder.
@@ -394,13 +419,15 @@
         /**
          * Sets flags changing the save behavior.
          *
-         * @param flags {@link #FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE} or {@code 0}.
+         * @param flags {@link #FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE},
+         * {@link #FLAG_DONT_SAVE_ON_FINISH}, or {@code 0}.
          * @return This builder.
          */
         public @NonNull Builder setFlags(@SaveInfoFlags int flags) {
             throwIfDestroyed();
 
-            mFlags = Preconditions.checkFlagsArgument(flags, FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE);
+            mFlags = Preconditions.checkFlagsArgument(flags,
+                    FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE | FLAG_DONT_SAVE_ON_FINISH);
             return this;
         }
 
@@ -493,8 +520,8 @@
         }
 
         /**
-         * Sets an object used to validate the user input - if the input is not valid, the Save UI
-         * affordance is not shown.
+         * Sets an object used to validate the user input - if the input is not valid, the
+         * autofill save UI is not shown.
          *
          * <p>Typically used to validate credit card numbers. Examples:
          *
@@ -520,7 +547,7 @@
          *   );
          * </pre>
          *
-         * <p><b>NOTE: </b>the example above is just for illustrative purposes; the same validator
+         * <p><b>Note:</b> the example above is just for illustrative purposes; the same validator
          * could be created using a single regex for the {@code OR} part:
          *
          * <pre class="prettyprint">
@@ -615,6 +642,27 @@
             return this;
         }
 
+       /**
+         * Explicitly defines the view that should commit the autofill context when clicked.
+         *
+         * <p>Usually, the save request is only automatically
+         * <a href="#TriggeringSaveRequest">triggered</a> after the activity is
+         * finished or all relevant views become invisible, but there are scenarios where the
+         * autofill context is automatically commited too late
+         * &mdash;for example, when the activity manually clears the autofillable views when a
+         * button is tapped. This method can be used to trigger the autofill save UI earlier in
+         * these scenarios.
+         *
+         * <p><b>Note:</b> This method should only be used in scenarios where the automatic workflow
+         * is not enough, otherwise it could trigger the autofill save UI when it should not&mdash;
+         * for example, when the user entered invalid credentials for the autofillable views.
+         */
+        public @NonNull Builder setTriggerId(@NonNull AutofillId id) {
+            throwIfDestroyed();
+            mTriggerId = Preconditions.checkNotNull(id);
+            return this;
+        }
+
         /**
          * Builds a new {@link SaveInfo} instance.
          *
@@ -652,13 +700,14 @@
                 .append(", description=").append(mDescription)
                 .append(DebugUtils.flagsToString(SaveInfo.class, "NEGATIVE_BUTTON_STYLE_",
                         mNegativeButtonStyle))
-                .append(", mFlags=").append(mFlags)
-                .append(", mCustomDescription=").append(mCustomDescription)
-                .append(", validation=").append(mValidator)
+                .append(", flags=").append(mFlags)
+                .append(", customDescription=").append(mCustomDescription)
+                .append(", validator=").append(mValidator)
                 .append(", sanitizerKeys=")
                     .append(mSanitizerKeys == null ? "N/A:" : mSanitizerKeys.length)
                 .append(", sanitizerValues=")
                     .append(mSanitizerValues == null ? "N/A:" : mSanitizerValues.length)
+                .append(", triggerId=").append(mTriggerId)
                 .append("]").toString();
     }
 
@@ -687,6 +736,7 @@
                 parcel.writeParcelableArray(mSanitizerValues[i], flags);
             }
         }
+        parcel.writeParcelable(mTriggerId, flags);
         parcel.writeInt(mFlags);
     }
 
@@ -727,6 +777,10 @@
                     builder.addSanitizer(sanitizers[i], autofillIds);
                 }
             }
+            final AutofillId triggerId = parcel.readParcelable(null);
+            if (triggerId != null) {
+                builder.setTriggerId(triggerId);
+            }
             builder.setFlags(parcel.readInt());
             return builder.build();
         }
diff --git a/android/service/autofill/SaveRequest.java b/android/service/autofill/SaveRequest.java
index 65fdb5c..f53967b 100644
--- a/android/service/autofill/SaveRequest.java
+++ b/android/service/autofill/SaveRequest.java
@@ -19,7 +19,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.Bundle;
-import android.os.CancellationSignal;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -60,9 +59,14 @@
     }
 
     /**
-     * Gets the extra client state returned from the last {@link
-     * AutofillService#onFillRequest(FillRequest, CancellationSignal, FillCallback)}
-     * fill request}.
+     * Gets the latest client state extra returned from the service.
+     *
+     * <p><b>Note:</b> Prior to Android {@link android.os.Build.VERSION_CODES#P}, only client state
+     * bundles set by {@link FillResponse.Builder#setClientState(Bundle)} where considered. On
+     * Android {@link android.os.Build.VERSION_CODES#P} and higher, bundles set in the result of
+     * an authenticated request through the
+     * {@link android.view.autofill.AutofillManager#EXTRA_CLIENT_STATE} extra are
+     * also considered (and take precedence when set).
      *
      * @return The client state.
      */
diff --git a/android/service/autofill/Validator.java b/android/service/autofill/Validator.java
index 854aa1e..a4036f2 100644
--- a/android/service/autofill/Validator.java
+++ b/android/service/autofill/Validator.java
@@ -16,9 +16,9 @@
 package android.service.autofill;
 
 /**
- * Helper class used to define whether the contents of a screen are valid.
+ * Class used to define whether a condition is satisfied.
  *
- * <p>Typically used to avoid displaying the Save UI affordance when the user input is invalid.
+ * <p>Typically used to avoid displaying the save UI when the user input is invalid.
  */
 public interface Validator {
 }
diff --git a/android/service/notification/ZenModeConfig.java b/android/service/notification/ZenModeConfig.java
index 7bec898..735b822 100644
--- a/android/service/notification/ZenModeConfig.java
+++ b/android/service/notification/ZenModeConfig.java
@@ -76,10 +76,13 @@
     private static final int DAY_MINUTES = 24 * 60;
     private static final int ZERO_VALUE_MS = 10 * SECONDS_MS;
 
-    private static final boolean DEFAULT_ALLOW_CALLS = true;
+    // Default allow categories set in readXml() from default_zen_mode_config.xml, fallback values:
+    private static final boolean DEFAULT_ALLOW_ALARMS = true;
+    private static final boolean DEFAULT_ALLOW_MEDIA_SYSTEM_OTHER = true;
+    private static final boolean DEFAULT_ALLOW_CALLS = false;
     private static final boolean DEFAULT_ALLOW_MESSAGES = false;
-    private static final boolean DEFAULT_ALLOW_REMINDERS = true;
-    private static final boolean DEFAULT_ALLOW_EVENTS = true;
+    private static final boolean DEFAULT_ALLOW_REMINDERS = false;
+    private static final boolean DEFAULT_ALLOW_EVENTS = false;
     private static final boolean DEFAULT_ALLOW_REPEAT_CALLERS = false;
     private static final boolean DEFAULT_ALLOW_SCREEN_OFF = true;
     private static final boolean DEFAULT_ALLOW_SCREEN_ON = true;
@@ -89,6 +92,8 @@
     private static final String ZEN_ATT_VERSION = "version";
     private static final String ZEN_ATT_USER = "user";
     private static final String ALLOW_TAG = "allow";
+    private static final String ALLOW_ATT_ALARMS = "alarms";
+    private static final String ALLOW_ATT_MEDIA_SYSTEM_OTHER = "media_system_other";
     private static final String ALLOW_ATT_CALLS = "calls";
     private static final String ALLOW_ATT_REPEAT_CALLERS = "repeatCallers";
     private static final String ALLOW_ATT_MESSAGES = "messages";
@@ -100,8 +105,6 @@
     private static final String ALLOW_ATT_SCREEN_OFF = "visualScreenOff";
     private static final String ALLOW_ATT_SCREEN_ON = "visualScreenOn";
 
-    private static final String CONDITION_TAG = "condition";
-    private static final String CONDITION_ATT_COMPONENT = "component";
     private static final String CONDITION_ATT_ID = "id";
     private static final String CONDITION_ATT_SUMMARY = "summary";
     private static final String CONDITION_ATT_LINE1 = "line1";
@@ -123,6 +126,8 @@
     private static final String RULE_ATT_CREATION_TIME = "creationTime";
     private static final String RULE_ATT_ENABLER = "enabler";
 
+    public boolean allowAlarms = DEFAULT_ALLOW_ALARMS;
+    public boolean allowMediaSystemOther = DEFAULT_ALLOW_MEDIA_SYSTEM_OTHER;
     public boolean allowCalls = DEFAULT_ALLOW_CALLS;
     public boolean allowRepeatCallers = DEFAULT_ALLOW_REPEAT_CALLERS;
     public boolean allowMessages = DEFAULT_ALLOW_MESSAGES;
@@ -161,6 +166,8 @@
         }
         allowWhenScreenOff = source.readInt() == 1;
         allowWhenScreenOn = source.readInt() == 1;
+        allowAlarms = source.readInt() == 1;
+        allowMediaSystemOther = source.readInt() == 1;
     }
 
     @Override
@@ -190,19 +197,23 @@
         }
         dest.writeInt(allowWhenScreenOff ? 1 : 0);
         dest.writeInt(allowWhenScreenOn ? 1 : 0);
+        dest.writeInt(allowAlarms ? 1 : 0);
+        dest.writeInt(allowMediaSystemOther ? 1 : 0);
     }
 
     @Override
     public String toString() {
         return new StringBuilder(ZenModeConfig.class.getSimpleName()).append('[')
                 .append("user=").append(user)
+                .append(",allowAlarms=").append(allowAlarms)
+                .append(",allowMediaSystemOther=").append(allowMediaSystemOther)
+                .append(",allowReminders=").append(allowReminders)
+                .append(",allowEvents=").append(allowEvents)
                 .append(",allowCalls=").append(allowCalls)
                 .append(",allowRepeatCallers=").append(allowRepeatCallers)
                 .append(",allowMessages=").append(allowMessages)
                 .append(",allowCallsFrom=").append(sourceToString(allowCallsFrom))
                 .append(",allowMessagesFrom=").append(sourceToString(allowMessagesFrom))
-                .append(",allowReminders=").append(allowReminders)
-                .append(",allowEvents=").append(allowEvents)
                 .append(",allowWhenScreenOff=").append(allowWhenScreenOff)
                 .append(",allowWhenScreenOn=").append(allowWhenScreenOn)
                 .append(",automaticRules=").append(automaticRules)
@@ -218,9 +229,21 @@
         if (user != to.user) {
             d.addLine("user", user, to.user);
         }
+        if (allowAlarms != to.allowAlarms) {
+            d.addLine("allowAlarms", allowAlarms, to.allowAlarms);
+        }
+        if (allowMediaSystemOther != to.allowMediaSystemOther) {
+            d.addLine("allowMediaSystemOther", allowMediaSystemOther, to.allowMediaSystemOther);
+        }
         if (allowCalls != to.allowCalls) {
             d.addLine("allowCalls", allowCalls, to.allowCalls);
         }
+        if (allowReminders != to.allowReminders) {
+            d.addLine("allowReminders", allowReminders, to.allowReminders);
+        }
+        if (allowEvents != to.allowEvents) {
+            d.addLine("allowEvents", allowEvents, to.allowEvents);
+        }
         if (allowRepeatCallers != to.allowRepeatCallers) {
             d.addLine("allowRepeatCallers", allowRepeatCallers, to.allowRepeatCallers);
         }
@@ -233,12 +256,6 @@
         if (allowMessagesFrom != to.allowMessagesFrom) {
             d.addLine("allowMessagesFrom", allowMessagesFrom, to.allowMessagesFrom);
         }
-        if (allowReminders != to.allowReminders) {
-            d.addLine("allowReminders", allowReminders, to.allowReminders);
-        }
-        if (allowEvents != to.allowEvents) {
-            d.addLine("allowEvents", allowEvents, to.allowEvents);
-        }
         if (allowWhenScreenOff != to.allowWhenScreenOff) {
             d.addLine("allowWhenScreenOff", allowWhenScreenOff, to.allowWhenScreenOff);
         }
@@ -335,7 +352,9 @@
         if (!(o instanceof ZenModeConfig)) return false;
         if (o == this) return true;
         final ZenModeConfig other = (ZenModeConfig) o;
-        return other.allowCalls == allowCalls
+        return other.allowAlarms == allowAlarms
+                && other.allowMediaSystemOther == allowMediaSystemOther
+                && other.allowCalls == allowCalls
                 && other.allowRepeatCallers == allowRepeatCallers
                 && other.allowMessages == allowMessages
                 && other.allowCallsFrom == allowCallsFrom
@@ -351,10 +370,10 @@
 
     @Override
     public int hashCode() {
-        return Objects.hash(allowCalls, allowRepeatCallers, allowMessages, allowCallsFrom,
-                allowMessagesFrom, allowReminders, allowEvents, allowWhenScreenOff,
-                allowWhenScreenOn,
-                user, automaticRules, manualRule);
+        return Objects.hash(allowAlarms, allowMediaSystemOther, allowCalls,
+                allowRepeatCallers, allowMessages,
+                allowCallsFrom, allowMessagesFrom, allowReminders, allowEvents,
+                allowWhenScreenOff, allowWhenScreenOn, user, automaticRules, manualRule);
     }
 
     private static String toDayList(int[] days) {
@@ -413,10 +432,12 @@
             }
             if (type == XmlPullParser.START_TAG) {
                 if (ALLOW_TAG.equals(tag)) {
-                    rt.allowCalls = safeBoolean(parser, ALLOW_ATT_CALLS, false);
+                    rt.allowCalls = safeBoolean(parser, ALLOW_ATT_CALLS,
+                            DEFAULT_ALLOW_CALLS);
                     rt.allowRepeatCallers = safeBoolean(parser, ALLOW_ATT_REPEAT_CALLERS,
                             DEFAULT_ALLOW_REPEAT_CALLERS);
-                    rt.allowMessages = safeBoolean(parser, ALLOW_ATT_MESSAGES, false);
+                    rt.allowMessages = safeBoolean(parser, ALLOW_ATT_MESSAGES,
+                            DEFAULT_ALLOW_MESSAGES);
                     rt.allowReminders = safeBoolean(parser, ALLOW_ATT_REMINDERS,
                             DEFAULT_ALLOW_REMINDERS);
                     rt.allowEvents = safeBoolean(parser, ALLOW_ATT_EVENTS, DEFAULT_ALLOW_EVENTS);
@@ -438,6 +459,9 @@
                             safeBoolean(parser, ALLOW_ATT_SCREEN_OFF, DEFAULT_ALLOW_SCREEN_OFF);
                     rt.allowWhenScreenOn =
                             safeBoolean(parser, ALLOW_ATT_SCREEN_ON, DEFAULT_ALLOW_SCREEN_ON);
+                    rt.allowAlarms = safeBoolean(parser, ALLOW_ATT_ALARMS, DEFAULT_ALLOW_ALARMS);
+                    rt.allowMediaSystemOther = safeBoolean(parser, ALLOW_ATT_MEDIA_SYSTEM_OTHER,
+                            DEFAULT_ALLOW_MEDIA_SYSTEM_OTHER);
                 } else if (MANUAL_TAG.equals(tag)) {
                     rt.manualRule = readRuleXml(parser);
                 } else if (AUTOMATIC_TAG.equals(tag)) {
@@ -468,6 +492,8 @@
         out.attribute(null, ALLOW_ATT_MESSAGES_FROM, Integer.toString(allowMessagesFrom));
         out.attribute(null, ALLOW_ATT_SCREEN_OFF, Boolean.toString(allowWhenScreenOff));
         out.attribute(null, ALLOW_ATT_SCREEN_ON, Boolean.toString(allowWhenScreenOn));
+        out.attribute(null, ALLOW_ATT_ALARMS, Boolean.toString(allowAlarms));
+        out.attribute(null, ALLOW_ATT_MEDIA_SYSTEM_OTHER, Boolean.toString(allowMediaSystemOther));
         out.endTag(null, ALLOW_TAG);
 
         if (manualRule != null) {
@@ -654,6 +680,12 @@
         if (!allowWhenScreenOn) {
             suppressedVisualEffects |= Policy.SUPPRESSED_EFFECT_SCREEN_ON;
         }
+        if (allowAlarms) {
+            priorityCategories |= Policy.PRIORITY_CATEGORY_ALARMS;
+        }
+        if (allowMediaSystemOther) {
+            priorityCategories |= Policy.PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER;
+        }
         priorityCallSenders = sourceToPrioritySenders(allowCallsFrom, priorityCallSenders);
         priorityMessageSenders = sourceToPrioritySenders(allowMessagesFrom, priorityMessageSenders);
         return new Policy(priorityCategories, priorityCallSenders, priorityMessageSenders,
@@ -680,10 +712,13 @@
 
     public void applyNotificationPolicy(Policy policy) {
         if (policy == null) return;
-        allowCalls = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_CALLS) != 0;
-        allowMessages = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_MESSAGES) != 0;
+        allowAlarms = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_ALARMS) != 0;
+        allowMediaSystemOther = (policy.priorityCategories
+                & Policy.PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER) != 0;
         allowEvents = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_EVENTS) != 0;
         allowReminders = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_REMINDERS) != 0;
+        allowCalls = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_CALLS) != 0;
+        allowMessages = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_MESSAGES) != 0;
         allowRepeatCallers = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_REPEAT_CALLERS)
                 != 0;
         allowCallsFrom = prioritySendersToSource(policy.priorityCallSenders, allowCallsFrom);
diff --git a/android/service/settings/suggestions/SuggestionService.java b/android/service/settings/suggestions/SuggestionService.java
index 2a4c84c..ce9501d 100644
--- a/android/service/settings/suggestions/SuggestionService.java
+++ b/android/service/settings/suggestions/SuggestionService.java
@@ -48,12 +48,20 @@
             }
 
             @Override
-            public  void dismissSuggestion(Suggestion suggestion) {
+            public void dismissSuggestion(Suggestion suggestion) {
                 if (DEBUG) {
                     Log.d(TAG, "dismissSuggestion() " + getPackageName());
                 }
                 onSuggestionDismissed(suggestion);
             }
+
+            @Override
+            public void launchSuggestion(Suggestion suggestion) {
+                if (DEBUG) {
+                    Log.d(TAG, "launchSuggestion() " + getPackageName());
+                }
+                onSuggestionLaunched(suggestion);
+            }
         };
     }
 
@@ -65,7 +73,12 @@
     /**
      * Dismiss a suggestion. The suggestion will not be included in future
      * {@link #onGetSuggestions()} calls.
-     * @param suggestion
      */
     public abstract void onSuggestionDismissed(Suggestion suggestion);
+
+    /**
+     * This is the opposite signal to {@link #onSuggestionDismissed}, indicating a suggestion has
+     * been launched.
+     */
+    public abstract void onSuggestionLaunched(Suggestion suggestion);
 }
diff --git a/android/support/LibraryVersions.java b/android/support/LibraryVersions.java
index a046d95..2f5730a 100644
--- a/android/support/LibraryVersions.java
+++ b/android/support/LibraryVersions.java
@@ -28,7 +28,7 @@
     /**
      * Version code for flatfoot 1.0 projects (room, lifecycles)
      */
-    private static final Version FLATFOOT_1_0_BATCH = new Version("1.0.0-beta2");
+    private static final Version FLATFOOT_1_0_BATCH = new Version("1.0.0-rc1");
 
     /**
      * Version code for Room
@@ -45,15 +45,17 @@
      */
     public static final Version PAGING = new Version("1.0.0-alpha3");
 
+    private static final Version LIFECYCLES = new Version("1.0.3");
+
     /**
      * Version code for Lifecycle libs that are required by the support library
      */
-    public static final Version LIFECYCLES_CORE = new Version("1.0.2");
+    public static final Version LIFECYCLES_CORE = LIFECYCLES;
 
     /**
      * Version code for Lifecycle runtime libs that are required by the support library
      */
-    public static final Version LIFECYCLES_RUNTIME = new Version("1.0.0");
+    public static final Version LIFECYCLES_RUNTIME = LIFECYCLES;
 
     /**
      * Version code for shared code of flatfoot
diff --git a/android/support/car/drawer/CarDrawerActivity.java b/android/support/car/drawer/CarDrawerActivity.java
new file mode 100644
index 0000000..7100218
--- /dev/null
+++ b/android/support/car/drawer/CarDrawerActivity.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2017 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.support.car.drawer;
+
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.support.annotation.LayoutRes;
+import android.support.annotation.Nullable;
+import android.support.car.R;
+import android.support.v4.widget.DrawerLayout;
+import android.support.v7.app.ActionBarDrawerToggle;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.Toolbar;
+import android.view.LayoutInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * Common base Activity for car apps that need to present a Drawer.
+ *
+ * <p>This Activity manages the overall layout. To use it, sub-classes need to:
+ *
+ * <ul>
+ *   <li>Provide the root-items for the Drawer by implementing {@link #getRootAdapter()}.
+ *   <li>Add their main content using {@link #setMainContent(int)} or {@link #setMainContent(View)}.
+ *       They can also add fragments to the main-content container by obtaining its id using
+ *       {@link #getContentContainerId()}
+ * </ul>
+ *
+ * <p>This class will take care of drawer toggling and display.
+ *
+ * <p>The rootAdapter can implement nested-navigation, in its click-handling, by passing the
+ * CarDrawerAdapter for the next level to
+ * {@link CarDrawerController#switchToAdapter(CarDrawerAdapter)}.
+ *
+ * <p>Any Activity's based on this class need to set their theme to CarDrawerActivityTheme or a
+ * derivative.
+ */
+public abstract class CarDrawerActivity extends AppCompatActivity {
+    private CarDrawerController mDrawerController;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.car_drawer_activity);
+
+        DrawerLayout drawerLayout = findViewById(R.id.drawer_layout);
+        ActionBarDrawerToggle drawerToggle = new ActionBarDrawerToggle(
+                this /* activity */,
+                drawerLayout, /* DrawerLayout object */
+                R.string.car_drawer_open,
+                R.string.car_drawer_close);
+
+        Toolbar toolbar = findViewById(R.id.car_toolbar);
+        setSupportActionBar(toolbar);
+
+        mDrawerController = new CarDrawerController(toolbar, drawerLayout, drawerToggle);
+        mDrawerController.setRootAdapter(getRootAdapter());
+
+        getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+        getSupportActionBar().setHomeButtonEnabled(true);
+    }
+
+    /**
+     * Returns the {@link CarDrawerController} that is responsible for handling events relating
+     * to the drawer in this Activity.
+     *
+     * @return The {@link CarDrawerController} linked to this Activity. This value will be
+     * {@code null} if this method is called before {@code onCreate()} has been called.
+     */
+    @Nullable
+    protected CarDrawerController getDrawerController() {
+        return mDrawerController;
+    }
+
+    @Override
+    protected void onPostCreate(Bundle savedInstanceState) {
+        super.onPostCreate(savedInstanceState);
+        mDrawerController.syncState();
+    }
+
+    /**
+     * @return Adapter for root content of the Drawer.
+     */
+    protected abstract CarDrawerAdapter getRootAdapter();
+
+    /**
+     * Set main content to display in this Activity. It will be added to R.id.content_frame in
+     * car_drawer_activity.xml. NOTE: Do not use {@link #setContentView(View)}.
+     *
+     * @param view View to display as main content.
+     */
+    public void setMainContent(View view) {
+        ViewGroup parent = findViewById(getContentContainerId());
+        parent.addView(view);
+    }
+
+    /**
+     * Set main content to display in this Activity. It will be added to R.id.content_frame in
+     * car_drawer_activity.xml. NOTE: Do not use {@link #setContentView(int)}.
+     *
+     * @param resourceId Layout to display as main content.
+     */
+    public void setMainContent(@LayoutRes int resourceId) {
+        ViewGroup parent = findViewById(getContentContainerId());
+        LayoutInflater inflater = getLayoutInflater();
+        inflater.inflate(resourceId, parent, true);
+    }
+
+    /**
+     * Get the id of the main content Container which is a FrameLayout. Subclasses can add their own
+     * content/fragments inside here.
+     *
+     * @return Id of FrameLayout where main content of the subclass Activity can be added.
+     */
+    protected int getContentContainerId() {
+        return R.id.content_frame;
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        mDrawerController.closeDrawer();
+    }
+
+    @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        mDrawerController.onConfigurationChanged(newConfig);
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        return mDrawerController.onOptionsItemSelected(item) || super.onOptionsItemSelected(item);
+    }
+}
diff --git a/android/support/car/drawer/CarDrawerAdapter.java b/android/support/car/drawer/CarDrawerAdapter.java
new file mode 100644
index 0000000..b0fd965
--- /dev/null
+++ b/android/support/car/drawer/CarDrawerAdapter.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2017 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.support.car.drawer;
+
+import android.content.Context;
+import android.graphics.PorterDuff;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.car.R;
+import android.support.car.widget.PagedListView;
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * Base adapter for displaying items in the car navigation drawer, which uses a
+ * {@link PagedListView}.
+ *
+ * <p>Subclasses must set the title that will be displayed when displaying the contents of the
+ * drawer via {@link #setTitle(CharSequence)}. The title can be updated at any point later on. The
+ * title of the root adapter will also be the main title showed in the toolbar when the drawer is
+ * closed. See {@link CarDrawerController#setRootAdapter(CarDrawerAdapter)} for more information.
+ *
+ * <p>This class also takes care of implementing the PageListView.ItemCamp contract and subclasses
+ * should implement {@link #getActualItemCount()}.
+ */
+public abstract class CarDrawerAdapter extends RecyclerView.Adapter<DrawerItemViewHolder>
+        implements PagedListView.ItemCap, DrawerItemClickListener {
+    private final boolean mShowDisabledListOnEmpty;
+    private final Drawable mEmptyListDrawable;
+    private int mMaxItems = PagedListView.ItemCap.UNLIMITED;
+    private CharSequence mTitle;
+    private TitleChangeListener mTitleChangeListener;
+
+    /**
+     * Interface for a class that will be notified a new title has been set on this adapter.
+     */
+    interface TitleChangeListener {
+        /**
+         * Called when {@link #setTitle(CharSequence)} has been called and the title has been
+         * changed.
+         */
+        void onTitleChanged(CharSequence newTitle);
+    }
+
+    protected CarDrawerAdapter(Context context, boolean showDisabledListOnEmpty) {
+        mShowDisabledListOnEmpty = showDisabledListOnEmpty;
+
+        mEmptyListDrawable = context.getDrawable(R.drawable.ic_list_view_disable);
+        mEmptyListDrawable.setColorFilter(context.getColor(R.color.car_tint),
+                PorterDuff.Mode.SRC_IN);
+    }
+
+    /** Returns the title set via {@link #setTitle(CharSequence)}. */
+    CharSequence getTitle() {
+        return mTitle;
+    }
+
+    /** Updates the title to display in the toolbar for this Adapter. */
+    public final void setTitle(@NonNull CharSequence title) {
+        if (title == null) {
+            throw new IllegalArgumentException("setTitle() cannot be passed a null title!");
+        }
+
+        mTitle = title;
+
+        if (mTitleChangeListener != null) {
+            mTitleChangeListener.onTitleChanged(mTitle);
+        }
+    }
+
+    /** Sets a listener to be notified whenever the title of this adapter has been changed. */
+    void setTitleChangeListener(@Nullable TitleChangeListener listener) {
+        mTitleChangeListener = listener;
+    }
+
+    @Override
+    public final void setMaxItems(int maxItems) {
+        mMaxItems = maxItems;
+    }
+
+    @Override
+    public final int getItemCount() {
+        if (shouldShowDisabledListItem()) {
+            return 1;
+        }
+        return mMaxItems >= 0 ? Math.min(mMaxItems, getActualItemCount()) : getActualItemCount();
+    }
+
+    /**
+     * Returns the absolute number of items that can be displayed in the list.
+     *
+     * <p>A class should implement this method to supply the number of items to be displayed.
+     * Returning 0 from this method will cause an empty list icon to be displayed in the drawer.
+     *
+     * <p>A class should override this method rather than {@link #getItemCount()} because that
+     * method is handling the logic of when to display the empty list icon. It will return 1 when
+     * {@link #getActualItemCount()} returns 0.
+     *
+     * @return The number of items to be displayed in the list.
+     */
+    protected abstract int getActualItemCount();
+
+    @Override
+    public final int getItemViewType(int position) {
+        if (shouldShowDisabledListItem()) {
+            return R.layout.car_drawer_list_item_empty;
+        }
+
+        return usesSmallLayout(position)
+                ? R.layout.car_drawer_list_item_small
+                : R.layout.car_drawer_list_item_normal;
+    }
+
+    /**
+     * Used to indicate the layout used for the Drawer item at given position. Subclasses can
+     * override this to use normal layout which includes text element below title.
+     *
+     * <p>A small layout is presented by the layout {@code R.layout.car_drawer_list_item_small}.
+     * Otherwise, the layout {@code R.layout.car_drawer_list_item_normal} will be used.
+     *
+     * @param position Adapter position of item.
+     * @return Whether the item at this position will use a small layout (default) or normal layout.
+     */
+    protected boolean usesSmallLayout(int position) {
+        return true;
+    }
+
+    @Override
+    public final DrawerItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+        View view = LayoutInflater.from(parent.getContext()).inflate(viewType, parent, false);
+        return new DrawerItemViewHolder(view);
+    }
+
+    @Override
+    public final void onBindViewHolder(DrawerItemViewHolder holder, int position) {
+        if (shouldShowDisabledListItem()) {
+            holder.getTitle().setText(null);
+            holder.getIcon().setImageDrawable(mEmptyListDrawable);
+            holder.setItemClickListener(null);
+        } else {
+            holder.setItemClickListener(this);
+            populateViewHolder(holder, position);
+        }
+    }
+
+    /**
+     * Whether or not this adapter should be displaying an empty list icon. The icon is shown if it
+     * has been configured to show and there are no items to be displayed.
+     */
+    private boolean shouldShowDisabledListItem() {
+        return mShowDisabledListOnEmpty && getActualItemCount() == 0;
+    }
+
+    /**
+     * Subclasses should set all elements in {@code holder} to populate the drawer-item. If some
+     * element is not used, it should be nulled out since these ViewHolder/View's are recycled.
+     */
+    protected abstract void populateViewHolder(DrawerItemViewHolder holder, int position);
+
+    /**
+     * Called when this adapter has been popped off the stack and is no longer needed. Subclasses
+     * can override to do any necessary cleanup.
+     */
+    public void cleanup() {}
+}
diff --git a/android/support/car/drawer/CarDrawerController.java b/android/support/car/drawer/CarDrawerController.java
new file mode 100644
index 0000000..4d9f4e9
--- /dev/null
+++ b/android/support/car/drawer/CarDrawerController.java
@@ -0,0 +1,306 @@
+/*
+ * Copyright (C) 2017 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.support.car.drawer;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.car.R;
+import android.support.car.widget.PagedListView;
+import android.support.v4.widget.DrawerLayout;
+import android.support.v7.app.ActionBarDrawerToggle;
+import android.support.v7.widget.Toolbar;
+import android.view.Gravity;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.ProgressBar;
+
+import java.util.Stack;
+
+/**
+ * A controller that will handle the set up of the navigation drawer. It will hook up the
+ * necessary buttons for up navigation, as well as expose methods to allow for a drill down
+ * navigation.
+ */
+public class CarDrawerController {
+    /** The amount that the drawer has been opened before its color should be switched. */
+    private static final float COLOR_SWITCH_SLIDE_OFFSET = 0.25f;
+
+    /**
+     * A representation of the hierarchy of navigation being displayed in the list. The ordering of
+     * this stack is the order that the user has visited each level. When the user navigates up,
+     * the adapters are poopped from this list.
+     */
+    private final Stack<CarDrawerAdapter> mAdapterStack = new Stack<>();
+
+    private final Context mContext;
+
+    private final Toolbar mToolbar;
+    private final DrawerLayout mDrawerLayout;
+    private final ActionBarDrawerToggle mDrawerToggle;
+
+    private final PagedListView mDrawerList;
+    private final ProgressBar mProgressBar;
+    private final View mDrawerContent;
+
+    /**
+     * Creates a {@link CarDrawerController} that will control the navigation of the drawer given by
+     * {@code drawerLayout}.
+     *
+     * <p>The given {@code drawerLayout} should either have a child View that is inflated from
+     * {@code R.layout.car_drawer} or ensure that it three children that have the IDs found in that
+     * layout.
+     *
+     * @param toolbar The {@link Toolbar} that will serve as the action bar for an Activity.
+     * @param drawerLayout The top-level container for the window content that shows the
+     * interactive drawer.
+     * @param drawerToggle The {@link ActionBarDrawerToggle} that bridges the given {@code toolbar}
+     * and {@code drawerLayout}.
+     */
+    public CarDrawerController(Toolbar toolbar,
+            DrawerLayout drawerLayout,
+            ActionBarDrawerToggle drawerToggle) {
+        mToolbar = toolbar;
+        mContext = drawerLayout.getContext();
+
+        mDrawerLayout = drawerLayout;
+
+        mDrawerContent = drawerLayout.findViewById(R.id.drawer_content);
+        mDrawerList = drawerLayout.findViewById(R.id.drawer_list);
+        mDrawerList.setMaxPages(PagedListView.ItemCap.UNLIMITED);
+
+        mProgressBar = drawerLayout.findViewById(R.id.drawer_progress);
+
+        mDrawerToggle = drawerToggle;
+        setupDrawerToggling();
+    }
+
+    /**
+     * Sets the {@link CarDrawerAdapter} that will function as the root adapter. The contents of
+     * this root adapter are shown when the drawer is first opened. It is also the top-most level of
+     * navigation in the drawer.
+     *
+     * @param rootAdapter The adapter that will act as the root. If this value is {@code null}, then
+     *                    this method will do nothing.
+     */
+    public void setRootAdapter(@Nullable CarDrawerAdapter rootAdapter) {
+        if (rootAdapter == null) {
+            return;
+        }
+
+        mAdapterStack.push(rootAdapter);
+        setToolbarTitleFrom(rootAdapter);
+        mDrawerList.setAdapter(rootAdapter);
+    }
+
+    /**
+     * Switches to use the given {@link CarDrawerAdapter} as the one to supply the list to display
+     * in the navigation drawer. The title will also be updated from the adapter.
+     *
+     * <p>This switch is treated as a navigation to the next level in the drawer. Navigation away
+     * from this level will pop the given adapter off and surface contents of the previous adapter
+     * that was set via this method. If no such adapter exists, then the root adapter set by
+     * {@link #setRootAdapter(CarDrawerAdapter)} will be used instead.
+     *
+     * @param adapter Adapter for next level of content in the drawer.
+     */
+    public final void switchToAdapter(CarDrawerAdapter adapter) {
+        mAdapterStack.peek().setTitleChangeListener(null);
+        mAdapterStack.push(adapter);
+        switchToAdapterInternal(adapter);
+    }
+
+    /** Close the drawer. */
+    public void closeDrawer() {
+        if (mDrawerLayout.isDrawerOpen(Gravity.LEFT)) {
+            mDrawerLayout.closeDrawer(Gravity.LEFT);
+        }
+    }
+
+    /** Opens the drawer. */
+    public void openDrawer() {
+        if (!mDrawerLayout.isDrawerOpen(Gravity.LEFT)) {
+            mDrawerLayout.openDrawer(Gravity.LEFT);
+        }
+    }
+
+    /** Sets a listener to be notified of Drawer events. */
+    public void addDrawerListener(@NonNull DrawerLayout.DrawerListener listener) {
+        mDrawerLayout.addDrawerListener(listener);
+    }
+
+    /** Removes a listener to be notified of Drawer events. */
+    public void removeDrawerListener(@NonNull DrawerLayout.DrawerListener listener) {
+        mDrawerLayout.removeDrawerListener(listener);
+    }
+
+    /**
+     * Sets whether the loading progress bar is displayed in the navigation drawer. If {@code true},
+     * the progress bar is displayed and the navigation list is hidden and vice versa.
+     */
+    public void showLoadingProgressBar(boolean show) {
+        mDrawerList.setVisibility(show ? View.INVISIBLE : View.VISIBLE);
+        mProgressBar.setVisibility(show ? View.VISIBLE : View.GONE);
+    }
+
+    /** Scroll to given position in the list. */
+    public void scrollToPosition(int position) {
+        mDrawerList.getRecyclerView().smoothScrollToPosition(position);
+    }
+
+    /**
+     * Retrieves the title from the given {@link CarDrawerAdapter} and set its as the title of this
+     * controller's internal Toolbar.
+     */
+    private void setToolbarTitleFrom(CarDrawerAdapter adapter) {
+        if (adapter.getTitle() == null) {
+            throw new RuntimeException("CarDrawerAdapter must supply a title via setTitle()");
+        }
+
+        mToolbar.setTitle(adapter.getTitle());
+        adapter.setTitleChangeListener(mToolbar::setTitle);
+    }
+
+    /**
+     * Sets up the necessary listeners for {@link DrawerLayout} so that the navigation drawer
+     * hierarchy is properly displayed.
+     */
+    private void setupDrawerToggling() {
+        mDrawerLayout.addDrawerListener(mDrawerToggle);
+        mDrawerLayout.addDrawerListener(
+                new DrawerLayout.DrawerListener() {
+                    @Override
+                    public void onDrawerSlide(View drawerView, float slideOffset) {
+                        // Correctly set the title and arrow colors as they are different between
+                        // the open and close states.
+                        updateTitleAndArrowColor(slideOffset >= COLOR_SWITCH_SLIDE_OFFSET);
+                    }
+
+                    @Override
+                    public void onDrawerClosed(View drawerView) {
+                        // If drawer is closed, revert stack/drawer to initial root state.
+                        cleanupStackAndShowRoot();
+                        scrollToPosition(0);
+                    }
+
+                    @Override
+                    public void onDrawerOpened(View drawerView) {}
+
+                    @Override
+                    public void onDrawerStateChanged(int newState) {}
+                });
+    }
+
+    /** Sets the title and arrow color of the drawer depending on if it is open or not. */
+    private void updateTitleAndArrowColor(boolean drawerOpen) {
+        // When the drawer is open, use car_title, which resolves to appropriate color depending on
+        // day-night mode. When drawer is closed, we always use light color.
+        int titleColorResId = drawerOpen ? R.color.car_title : R.color.car_title_light;
+        int titleColor = mContext.getColor(titleColorResId);
+        mToolbar.setTitleTextColor(titleColor);
+        mDrawerToggle.getDrawerArrowDrawable().setColor(titleColor);
+    }
+
+    /**
+     * Synchronizes the display of the drawer with its linked {@link DrawerLayout}.
+     *
+     * <p>This should be called from the associated Activity's
+     * {@link android.support.v7.app.AppCompatActivity#onPostCreate(Bundle)} method to synchronize
+     * after teh DRawerLayout's instance state has been restored, and any other time when the
+     * state may have diverged in such a way that this controller's associated
+     * {@link ActionBarDrawerToggle} had not been notified.
+     */
+    public void syncState() {
+        mDrawerToggle.syncState();
+
+        // In case we're restarting after a config change (e.g. day, night switch), set colors
+        // again. Doing it here so that Drawer state is fully synced and we know if its open or not.
+        // NOTE: isDrawerOpen must be passed the second child of the DrawerLayout.
+        updateTitleAndArrowColor(mDrawerLayout.isDrawerOpen(mDrawerContent));
+    }
+
+    /**
+     * Notify this controller that device configurations may have changed.
+     *
+     * <p>This method should be called from the associated Activity's
+     * {@code onConfigurationChanged()} method.
+     */
+    public void onConfigurationChanged(Configuration newConfig) {
+        // Pass any configuration change to the drawer toggle.
+        mDrawerToggle.onConfigurationChanged(newConfig);
+    }
+
+    /**
+     * An analog to an Activity's {@code onOptionsItemSelected()}. This method should be called
+     * when the Activity's method is called and will return {@code true} if the selection has
+     * been handled.
+     *
+     * @return {@code true} if the item processing was handled by this class.
+     */
+    public boolean onOptionsItemSelected(MenuItem item) {
+        // Handle home-click and see if we can navigate up in the drawer.
+        if (item != null && item.getItemId() == android.R.id.home && maybeHandleUpClick()) {
+            return true;
+        }
+
+        // DrawerToggle gets next chance to handle up-clicks (and any other clicks).
+        return mDrawerToggle.onOptionsItemSelected(item);
+    }
+
+    /**
+     * Sets the navigation drawer's title to be the one supplied by the given adapter and updates
+     * the navigation drawer list with the adapter's contents.
+     */
+    private void switchToAdapterInternal(CarDrawerAdapter adapter) {
+        setToolbarTitleFrom(adapter);
+        // NOTE: We don't use swapAdapter() since different levels in the Drawer may switch between
+        // car_drawer_list_item_normal, car_drawer_list_item_small and car_list_empty layouts.
+        mDrawerList.getRecyclerView().setAdapter(adapter);
+        scrollToPosition(0);
+    }
+
+    /**
+     * Switches to the previous level in the drawer hierarchy if the current list being displayed
+     * is not the root adapter. This is analogous to a navigate up.
+     *
+     * @return {@code true} if a navigate up was possible and executed. {@code false} otherwise.
+     */
+    private boolean maybeHandleUpClick() {
+        // Check if already at the root level.
+        if (mAdapterStack.size() <= 1) {
+            return false;
+        }
+
+        CarDrawerAdapter adapter = mAdapterStack.pop();
+        adapter.setTitleChangeListener(null);
+        adapter.cleanup();
+        switchToAdapterInternal(mAdapterStack.peek());
+        return true;
+    }
+
+    /** Clears stack down to root adapter and switches to root adapter. */
+    private void cleanupStackAndShowRoot() {
+        while (mAdapterStack.size() > 1) {
+            CarDrawerAdapter adapter = mAdapterStack.pop();
+            adapter.setTitleChangeListener(null);
+            adapter.cleanup();
+        }
+        switchToAdapterInternal(mAdapterStack.peek());
+    }
+}
diff --git a/android/arch/paging/StringPagedList.java b/android/support/car/drawer/DrawerItemClickListener.java
similarity index 64%
rename from android/arch/paging/StringPagedList.java
rename to android/support/car/drawer/DrawerItemClickListener.java
index 5318d38..d707dbd 100644
--- a/android/arch/paging/StringPagedList.java
+++ b/android/support/car/drawer/DrawerItemClickListener.java
@@ -14,12 +14,16 @@
  * limitations under the License.
  */
 
-package android.arch.paging;
+package android.support.car.drawer;
 
-import java.util.Arrays;
-
-public class StringPagedList extends NullPaddedList<String> {
-    public StringPagedList(int leadingNulls, int trailingNulls, String... items) {
-        super(leadingNulls, Arrays.asList(items), trailingNulls);
-    }
+/**
+ * Listener for handling clicks on items/views managed by {@link DrawerItemViewHolder}.
+ */
+public interface DrawerItemClickListener {
+    /**
+     * Callback when item is clicked.
+     *
+     * @param position Adapter position of the clicked item.
+     */
+    void onItemClick(int position);
 }
diff --git a/android/support/car/drawer/DrawerItemViewHolder.java b/android/support/car/drawer/DrawerItemViewHolder.java
new file mode 100644
index 0000000..d016b2d
--- /dev/null
+++ b/android/support/car/drawer/DrawerItemViewHolder.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2017 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.support.car.drawer;
+
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.car.R;
+import android.support.v7.widget.RecyclerView;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+/**
+ * Re-usable {@link RecyclerView.ViewHolder} for displaying items in the
+ * {@link android.support.car.drawer.CarDrawerAdapter}.
+ */
+public class DrawerItemViewHolder extends RecyclerView.ViewHolder {
+    private final ImageView mIcon;
+    private final TextView mTitle;
+    private final TextView mText;
+    private final ImageView mEndIcon;
+
+    DrawerItemViewHolder(View view) {
+        super(view);
+        mIcon = view.findViewById(R.id.icon);
+        if (mIcon == null) {
+            throw new IllegalArgumentException("Icon view cannot be null!");
+        }
+
+        mTitle = view.findViewById(R.id.title);
+        if (mTitle == null) {
+            throw new IllegalArgumentException("Title view cannot be null!");
+        }
+
+        // Next two are optional and may be null.
+        mText = view.findViewById(R.id.text);
+        mEndIcon = view.findViewById(R.id.end_icon);
+    }
+
+    /** Returns the view that should be used to display the main icon. */
+    @NonNull
+    public ImageView getIcon() {
+        return mIcon;
+    }
+
+    /** Returns the view that will display the main title. */
+    @NonNull
+    public TextView getTitle() {
+        return mTitle;
+    }
+
+    /** Returns the view that is used for text that is smaller than the title text. */
+    @Nullable
+    public TextView getText() {
+        return mText;
+    }
+
+    /** Returns the icon that is displayed at the end of the view. */
+    @Nullable
+    public ImageView getEndIcon() {
+        return mEndIcon;
+    }
+
+    /**
+     * Sets the listener that will be notified when the view held by this ViewHolder has been
+     * clicked. Passing {@code null} will clear any previously set listeners.
+     */
+    void setItemClickListener(@Nullable DrawerItemClickListener listener) {
+        itemView.setOnClickListener(listener != null
+                ? v -> listener.onItemClick(getAdapterPosition())
+                : null);
+    }
+}
diff --git a/android/support/car/widget/PagedListView.java b/android/support/car/widget/PagedListView.java
index 8527c65..4652700 100644
--- a/android/support/car/widget/PagedListView.java
+++ b/android/support/car/widget/PagedListView.java
@@ -27,7 +27,6 @@
 import android.support.annotation.IdRes;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
-import android.support.annotation.Px;
 import android.support.annotation.RestrictTo;
 import android.support.annotation.UiThread;
 import android.support.car.R;
@@ -61,7 +60,6 @@
     protected final CarLayoutManager mLayoutManager;
     protected final Handler mHandler = new Handler();
     private final boolean mScrollBarEnabled;
-    private final boolean mRightGutterEnabled;
     private final PagedScrollBarView mScrollBarView;
 
     private int mRowsPerPage = -1;
@@ -98,6 +96,11 @@
      */
     public interface ItemCap {
         /**
+         * A value to pass to {@link #setMaxItems(int)} that indicates there should be no limit.
+         */
+        int UNLIMITED = -1;
+
+        /**
          * Sets the maximum number of items available in the adapter. A value less than '0' means
          * the list should not be capped.
          */
@@ -139,7 +142,6 @@
         }
         LayoutInflater.from(context).inflate(layoutId, this /*root*/, true /*attachToRoot*/);
 
-        FrameLayout maxWidthLayout = (FrameLayout) findViewById(R.id.recycler_view_container);
         TypedArray a = context.obtainStyledAttributes(
                 attrs, R.styleable.PagedListView, defStyleAttrs, defStyleRes);
         mRecyclerView = (CarRecyclerView) findViewById(R.id.recycler_view);
@@ -156,6 +158,16 @@
         mRecyclerView.getRecycledViewPool().setMaxRecycledViews(0, 12);
         mRecyclerView.setItemAnimator(new CarItemAnimator(mLayoutManager));
 
+        boolean offsetScrollBar = a.getBoolean(R.styleable.PagedListView_offsetScrollBar, false);
+        if (offsetScrollBar) {
+            MarginLayoutParams params = (MarginLayoutParams) mRecyclerView.getLayoutParams();
+            params.setMarginStart(getResources().getDimensionPixelSize(
+                    R.dimen.car_screen_margin_size));
+            params.setMarginEnd(
+                    a.getDimensionPixelSize(R.styleable.PagedListView_listEndMargin, 0));
+            mRecyclerView.setLayoutParams(params);
+        }
+
         if (a.getBoolean(R.styleable.PagedListView_showPagedListViewDivider, true)) {
             int dividerStartMargin = a.getDimensionPixelSize(
                     R.styleable.PagedListView_dividerStartMargin, 0);
@@ -199,47 +211,20 @@
                         }
                     }
                 });
+
         mScrollBarView.setVisibility(mScrollBarEnabled ? VISIBLE : GONE);
 
-        // Modify the layout if the Gutter or the Scroll Bar are not visible.
-        mRightGutterEnabled = a.getBoolean(R.styleable.PagedListView_rightGutterEnabled, false);
-        if (mRightGutterEnabled || !mScrollBarEnabled) {
-            FrameLayout.LayoutParams maxWidthLayoutLayoutParams =
-                    (FrameLayout.LayoutParams) maxWidthLayout.getLayoutParams();
-            if (mRightGutterEnabled) {
-                maxWidthLayoutLayoutParams.rightMargin =
-                        getResources().getDimensionPixelSize(R.dimen.car_card_margin);
-            }
-            if (!mScrollBarEnabled) {
-                maxWidthLayoutLayoutParams.setMarginStart(0);
-            }
-            maxWidthLayout.setLayoutParams(maxWidthLayoutLayoutParams);
+        // Modify the layout the Scroll Bar is not visible.
+        if (!mScrollBarEnabled) {
+            MarginLayoutParams params = (MarginLayoutParams) mRecyclerView.getLayoutParams();
+            params.setMarginStart(0);
+            mRecyclerView.setLayoutParams(params);
         }
 
         setDayNightStyle(DayNightStyle.AUTO);
         a.recycle();
     }
 
-    /**
-     * Sets the starting and ending padding for each view in the list.
-     *
-     * @param start The start padding.
-     * @param end The end padding.
-     */
-    public void setListViewStartEndPadding(@Px int start, @Px int end) {
-        int carCardMargin = getResources().getDimensionPixelSize(R.dimen.car_card_margin);
-        int startGutter = mScrollBarEnabled ? carCardMargin : 0;
-        int startPadding = Math.max(start - startGutter, 0);
-        int endGutter = mRightGutterEnabled ? carCardMargin : 0;
-        int endPadding = Math.max(end - endGutter, 0);
-        mRecyclerView.setPaddingRelative(startPadding, mRecyclerView.getPaddingTop(),
-                endPadding, mRecyclerView.getPaddingBottom());
-
-        // Since we're setting padding we'll need to set the clip to padding to the same
-        // value as clip children to ensure that the cards fly off the screen.
-        mRecyclerView.setClipToPadding(mRecyclerView.getClipChildren());
-    }
-
     @Override
     protected void onDetachedFromWindow() {
         super.onDetachedFromWindow();
diff --git a/android/support/media/tv/BasePreviewProgram.java b/android/support/media/tv/BasePreviewProgram.java
index 1423d9d..39c3014 100644
--- a/android/support/media/tv/BasePreviewProgram.java
+++ b/android/support/media/tv/BasePreviewProgram.java
@@ -23,14 +23,13 @@
 import android.database.Cursor;
 import android.net.Uri;
 import android.os.Build;
+import android.support.annotation.IntDef;
 import android.support.annotation.RestrictTo;
 import android.support.media.tv.TvContractCompat.PreviewProgramColumns;
-import android.support.media.tv.TvContractCompat.PreviewProgramColumns.AspectRatio;
-import android.support.media.tv.TvContractCompat.PreviewProgramColumns.Availability;
-import android.support.media.tv.TvContractCompat.PreviewProgramColumns.InteractionType;
-import android.support.media.tv.TvContractCompat.PreviewProgramColumns.Type;
 import android.support.media.tv.TvContractCompat.PreviewPrograms;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.net.URISyntaxException;
 import java.text.SimpleDateFormat;
 import java.util.Date;
@@ -55,6 +54,89 @@
     private static final int IS_LIVE = 1;
     private static final int IS_BROWSABLE = 1;
 
+    /** @hide */
+    @IntDef({
+            TYPE_UNKNOWN,
+            PreviewProgramColumns.TYPE_MOVIE,
+            PreviewProgramColumns.TYPE_TV_SERIES,
+            PreviewProgramColumns.TYPE_TV_SEASON,
+            PreviewProgramColumns.TYPE_TV_EPISODE,
+            PreviewProgramColumns.TYPE_CLIP,
+            PreviewProgramColumns.TYPE_EVENT,
+            PreviewProgramColumns.TYPE_CHANNEL,
+            PreviewProgramColumns.TYPE_TRACK,
+            PreviewProgramColumns.TYPE_ALBUM,
+            PreviewProgramColumns.TYPE_ARTIST,
+            PreviewProgramColumns.TYPE_PLAYLIST,
+            PreviewProgramColumns.TYPE_STATION,
+            PreviewProgramColumns.TYPE_GAME
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @RestrictTo(LIBRARY_GROUP)
+    public @interface Type {}
+
+    /**
+     * The unknown program type.
+     */
+    private static final int TYPE_UNKNOWN = -1;
+
+    /** @hide */
+    @IntDef({
+            ASPECT_RATIO_UNKNOWN,
+            PreviewProgramColumns.ASPECT_RATIO_16_9,
+            PreviewProgramColumns.ASPECT_RATIO_3_2,
+            PreviewProgramColumns.ASPECT_RATIO_4_3,
+            PreviewProgramColumns.ASPECT_RATIO_1_1,
+            PreviewProgramColumns.ASPECT_RATIO_2_3,
+            PreviewProgramColumns.ASPECT_RATIO_MOVIE_POSTER
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @RestrictTo(LIBRARY_GROUP)
+    public @interface AspectRatio {}
+
+    /**
+     * The aspect ratio for unknown aspect ratios.
+     */
+    private static final int ASPECT_RATIO_UNKNOWN = -1;
+
+    /** @hide */
+    @IntDef({
+            AVAILABILITY_UNKNOWN,
+            PreviewProgramColumns.AVAILABILITY_AVAILABLE,
+            PreviewProgramColumns.AVAILABILITY_FREE_WITH_SUBSCRIPTION,
+            PreviewProgramColumns.AVAILABILITY_PAID_CONTENT,
+            PreviewProgramColumns.AVAILABILITY_PURCHASED,
+            PreviewProgramColumns.AVAILABILITY_FREE
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @RestrictTo(LIBRARY_GROUP)
+    public @interface Availability {}
+
+    /**
+     * The unknown availability.
+     */
+    private static final int AVAILABILITY_UNKNOWN = -1;
+
+    /** @hide */
+    @IntDef({
+            INTERACTION_TYPE_UNKNOWN,
+            PreviewProgramColumns.INTERACTION_TYPE_VIEWS,
+            PreviewProgramColumns.INTERACTION_TYPE_LISTENS,
+            PreviewProgramColumns.INTERACTION_TYPE_FOLLOWERS,
+            PreviewProgramColumns.INTERACTION_TYPE_FANS,
+            PreviewProgramColumns.INTERACTION_TYPE_LIKES,
+            PreviewProgramColumns.INTERACTION_TYPE_THUMBS,
+            PreviewProgramColumns.INTERACTION_TYPE_VIEWERS,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @RestrictTo(LIBRARY_GROUP)
+    public @interface InteractionType {}
+
+    /**
+     * The unknown interaction type.
+     */
+    private static final int INTERACTION_TYPE_UNKNOWN = -1;
+
     BasePreviewProgram(Builder builder) {
         super(builder);
     }
@@ -127,7 +209,7 @@
      */
     public @Type int getType() {
         Integer i = mValues.getAsInteger(PreviewPrograms.COLUMN_TYPE);
-        return i == null ? INVALID_INT_VALUE : i;
+        return i == null ? TYPE_UNKNOWN : i;
     }
 
     /**
@@ -137,7 +219,7 @@
      */
     public @AspectRatio int getPosterArtAspectRatio() {
         Integer i = mValues.getAsInteger(PreviewPrograms.COLUMN_POSTER_ART_ASPECT_RATIO);
-        return i == null ? INVALID_INT_VALUE : i;
+        return i == null ? ASPECT_RATIO_UNKNOWN : i;
     }
 
     /**
@@ -147,7 +229,7 @@
      */
     public @AspectRatio int getThumbnailAspectRatio() {
         Integer i = mValues.getAsInteger(PreviewPrograms.COLUMN_THUMBNAIL_ASPECT_RATIO);
-        return i == null ? INVALID_INT_VALUE : i;
+        return i == null ? ASPECT_RATIO_UNKNOWN : i;
     }
 
     /**
@@ -165,7 +247,7 @@
      */
     public @Availability int getAvailability() {
         Integer i = mValues.getAsInteger(PreviewPrograms.COLUMN_AVAILABILITY);
-        return i == null ? INVALID_INT_VALUE : i;
+        return i == null ? AVAILABILITY_UNKNOWN : i;
     }
 
     /**
@@ -216,7 +298,7 @@
      */
     public @InteractionType int getInteractionType() {
         Integer i = mValues.getAsInteger(PreviewPrograms.COLUMN_INTERACTION_TYPE);
-        return i == null ? INVALID_INT_VALUE : i;
+        return i == null ? INTERACTION_TYPE_UNKNOWN : i;
     }
 
     /**
diff --git a/android/support/media/tv/BaseProgram.java b/android/support/media/tv/BaseProgram.java
index e4ce9d1..23b5cf9 100644
--- a/android/support/media/tv/BaseProgram.java
+++ b/android/support/media/tv/BaseProgram.java
@@ -22,13 +22,16 @@
 import android.media.tv.TvContentRating;
 import android.net.Uri;
 import android.os.Build;
+import android.support.annotation.IntDef;
 import android.support.annotation.RestrictTo;
 import android.support.media.tv.TvContractCompat.BaseTvColumns;
 import android.support.media.tv.TvContractCompat.ProgramColumns;
-import android.support.media.tv.TvContractCompat.ProgramColumns.ReviewRatingStyle;
 import android.support.media.tv.TvContractCompat.Programs;
 import android.support.media.tv.TvContractCompat.Programs.Genres.Genre;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * Base class for derived classes that want to have common fields for programs defined in
  * {@link TvContractCompat}.
@@ -46,6 +49,22 @@
     private static final int IS_SEARCHABLE = 1;
 
     /** @hide */
+    @IntDef({
+            REVIEW_RATING_STYLE_UNKNOWN,
+            ProgramColumns.REVIEW_RATING_STYLE_STARS,
+            ProgramColumns.REVIEW_RATING_STYLE_THUMBS_UP_DOWN,
+            ProgramColumns.REVIEW_RATING_STYLE_PERCENTAGE,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @RestrictTo(LIBRARY_GROUP)
+    @interface ReviewRatingStyle {}
+
+    /**
+     * The unknown review rating style.
+     */
+    private static final int REVIEW_RATING_STYLE_UNKNOWN = -1;
+
+    /** @hide */
     @RestrictTo(LIBRARY_GROUP)
     protected ContentValues mValues;
 
@@ -254,7 +273,7 @@
      */
     public @ReviewRatingStyle int getReviewRatingStyle() {
         Integer i = mValues.getAsInteger(Programs.COLUMN_REVIEW_RATING_STYLE);
-        return i == null ? INVALID_INT_VALUE : i;
+        return i == null ? REVIEW_RATING_STYLE_UNKNOWN : i;
     }
 
     /**
diff --git a/android/support/media/tv/Program.java b/android/support/media/tv/Program.java
index 4e3bd7a..233f1ba 100644
--- a/android/support/media/tv/Program.java
+++ b/android/support/media/tv/Program.java
@@ -25,6 +25,7 @@
 import android.support.annotation.NonNull;
 import android.support.annotation.RestrictTo;
 import android.support.media.tv.TvContractCompat.Programs;
+import android.support.media.tv.TvContractCompat.Programs.Genres.Genre;
 
 /**
  * A convenience class to access {@link TvContractCompat.Programs} entries in the system content
@@ -282,7 +283,7 @@
          * @return This Builder object to allow for chaining of calls to builder methods.
          * @see Programs#COLUMN_BROADCAST_GENRE
          */
-        public Builder setBroadcastGenres(String[] genres) {
+        public Builder setBroadcastGenres(@Genre String[] genres) {
             mValues.put(Programs.COLUMN_BROADCAST_GENRE, Programs.Genres.encode(genres));
             return this;
         }
diff --git a/android/support/media/tv/TvContractCompat.java b/android/support/media/tv/TvContractCompat.java
index 5a46e79..de4fd04 100644
--- a/android/support/media/tv/TvContractCompat.java
+++ b/android/support/media/tv/TvContractCompat.java
@@ -30,7 +30,6 @@
 import android.os.Build;
 import android.os.Bundle;
 import android.provider.BaseColumns;
-import android.support.annotation.IntDef;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.annotation.RequiresApi;
@@ -606,16 +605,6 @@
      */
     @RestrictTo(LIBRARY_GROUP)
     interface ProgramColumns {
-        /** @hide */
-        @IntDef({
-                REVIEW_RATING_STYLE_STARS,
-                REVIEW_RATING_STYLE_THUMBS_UP_DOWN,
-                REVIEW_RATING_STYLE_PERCENTAGE,
-        })
-        @Retention(RetentionPolicy.SOURCE)
-        @RestrictTo(LIBRARY_GROUP)
-        @interface ReviewRatingStyle {}
-
         /**
          * The review rating style for five star rating.
          *
@@ -934,27 +923,6 @@
      */
     @RestrictTo(LIBRARY_GROUP)
     public interface PreviewProgramColumns {
-
-        /** @hide */
-        @IntDef({
-                TYPE_MOVIE,
-                TYPE_TV_SERIES,
-                TYPE_TV_SEASON,
-                TYPE_TV_EPISODE,
-                TYPE_CLIP,
-                TYPE_EVENT,
-                TYPE_CHANNEL,
-                TYPE_TRACK,
-                TYPE_ALBUM,
-                TYPE_ARTIST,
-                TYPE_PLAYLIST,
-                TYPE_STATION,
-                TYPE_GAME
-        })
-        @Retention(RetentionPolicy.SOURCE)
-        @RestrictTo(LIBRARY_GROUP)
-        public @interface Type {}
-
         /**
          * The program type for movie.
          *
@@ -1046,19 +1014,6 @@
          */
         int TYPE_GAME = 12;
 
-        /** @hide */
-        @IntDef({
-                ASPECT_RATIO_16_9,
-                ASPECT_RATIO_3_2,
-                ASPECT_RATIO_4_3,
-                ASPECT_RATIO_1_1,
-                ASPECT_RATIO_2_3,
-                ASPECT_RATIO_MOVIE_POSTER,
-        })
-        @Retention(RetentionPolicy.SOURCE)
-        @RestrictTo(LIBRARY_GROUP)
-        public @interface AspectRatio {}
-
         /**
          * The aspect ratio for 16:9.
          *
@@ -1107,18 +1062,6 @@
          */
         int ASPECT_RATIO_MOVIE_POSTER = 5;
 
-        /** @hide */
-        @IntDef({
-                AVAILABILITY_AVAILABLE,
-                AVAILABILITY_FREE_WITH_SUBSCRIPTION,
-                AVAILABILITY_PAID_CONTENT,
-                AVAILABILITY_PURCHASED,
-                AVAILABILITY_FREE,
-        })
-        @Retention(RetentionPolicy.SOURCE)
-        @RestrictTo(LIBRARY_GROUP)
-        public @interface Availability {}
-
         /**
          * The availability for "available to this user".
          *
@@ -1155,20 +1098,6 @@
          */
         int AVAILABILITY_FREE = 4;
 
-        /** @hide */
-        @IntDef({
-                INTERACTION_TYPE_VIEWS,
-                INTERACTION_TYPE_LISTENS,
-                INTERACTION_TYPE_FOLLOWERS,
-                INTERACTION_TYPE_FANS,
-                INTERACTION_TYPE_LIKES,
-                INTERACTION_TYPE_THUMBS,
-                INTERACTION_TYPE_VIEWERS,
-        })
-        @Retention(RetentionPolicy.SOURCE)
-        @RestrictTo(LIBRARY_GROUP)
-        public @interface InteractionType {}
-
         /**
          * The interaction type for "views".
          *
@@ -2895,17 +2824,6 @@
         /** The MIME type of a single preview TV program. */
         public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/watch_next_program";
 
-        /** @hide */
-        @IntDef({
-                WATCH_NEXT_TYPE_CONTINUE,
-                WATCH_NEXT_TYPE_NEXT,
-                WATCH_NEXT_TYPE_NEW,
-                WATCH_NEXT_TYPE_WATCHLIST,
-        })
-        @Retention(RetentionPolicy.SOURCE)
-        @RestrictTo(LIBRARY_GROUP)
-        public @interface WatchNextType {}
-
         /**
          * The watch next type for CONTINUE. Use this type when the user has already watched more
          * than 1 minute of this content.
diff --git a/android/support/media/tv/WatchNextProgram.java b/android/support/media/tv/WatchNextProgram.java
index f466584..c192745 100644
--- a/android/support/media/tv/WatchNextProgram.java
+++ b/android/support/media/tv/WatchNextProgram.java
@@ -22,12 +22,15 @@
 import android.database.Cursor;
 import android.media.tv.TvContentRating;  // For javadoc gen of super class
 import android.os.Build;
+import android.support.annotation.IntDef;
 import android.support.annotation.RestrictTo;
 import android.support.media.tv.TvContractCompat.PreviewPrograms;  // For javadoc gen of super class
 import android.support.media.tv.TvContractCompat.Programs;  // For javadoc gen of super class
 import android.support.media.tv.TvContractCompat.Programs.Genres;  // For javadoc gen of super class
 import android.support.media.tv.TvContractCompat.WatchNextPrograms;
-import android.support.media.tv.TvContractCompat.WatchNextPrograms.WatchNextType;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 
 /**
  * A convenience class to access {@link WatchNextPrograms} entries in the system content
@@ -87,16 +90,34 @@
     private static final long INVALID_LONG_VALUE = -1;
     private static final int INVALID_INT_VALUE = -1;
 
+    /** @hide */
+    @IntDef({
+            WATCH_NEXT_TYPE_UNKNOWN,
+            WatchNextPrograms.WATCH_NEXT_TYPE_CONTINUE,
+            WatchNextPrograms.WATCH_NEXT_TYPE_NEXT,
+            WatchNextPrograms.WATCH_NEXT_TYPE_NEW,
+            WatchNextPrograms.WATCH_NEXT_TYPE_WATCHLIST,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @RestrictTo(LIBRARY_GROUP)
+    public @interface WatchNextType {}
+
+    /**
+     * The unknown watch next type. Use this type when the actual type is not known.
+     */
+    public static final int WATCH_NEXT_TYPE_UNKNOWN = -1;
+
     private WatchNextProgram(Builder builder) {
         super(builder);
     }
 
     /**
-     * @return The value of {@link WatchNextPrograms#COLUMN_WATCH_NEXT_TYPE} for the program.
+     * @return The value of {@link WatchNextPrograms#COLUMN_WATCH_NEXT_TYPE} for the program,
+     * or {@link #WATCH_NEXT_TYPE_UNKNOWN} if it's unknown.
      */
     public @WatchNextType int getWatchNextType() {
         Integer i = mValues.getAsInteger(WatchNextPrograms.COLUMN_WATCH_NEXT_TYPE);
-        return i == null ? INVALID_INT_VALUE : i;
+        return i == null ? WATCH_NEXT_TYPE_UNKNOWN : i;
     }
 
     /**
diff --git a/android/support/mediacompat/testlib/IntentConstants.java b/android/support/mediacompat/testlib/IntentConstants.java
index bc35935..b0e3d5f 100644
--- a/android/support/mediacompat/testlib/IntentConstants.java
+++ b/android/support/mediacompat/testlib/IntentConstants.java
@@ -17,11 +17,18 @@
 package android.support.mediacompat.testlib;
 
 /**
- * Constants used for sending intent between client and service apks.
+ * Constants used for sending intent between client and service apps.
  */
 public class IntentConstants {
     public static final String ACTION_CALL_MEDIA_BROWSER_SERVICE_METHOD =
             "android.support.mediacompat.service.action.CALL_MEDIA_BROWSER_SERVICE_METHOD";
+    public static final String ACTION_CALL_MEDIA_SESSION_METHOD =
+            "android.support.mediacompat.service.action.CALL_MEDIA_SESSION_METHOD";
+    public static final String ACTION_CALL_MEDIA_CONTROLLER_METHOD =
+            "android.support.mediacompat.client.action.CALL_MEDIA_CONTROLLER_METHOD";
+    public static final String ACTION_CALL_TRANSPORT_CONTROLS_METHOD =
+            "android.support.mediacompat.client.action.CALL_TRANSPORT_CONTROLS_METHOD";
     public static final String KEY_METHOD_ID = "method_id";
     public static final String KEY_ARGUMENT = "argument";
+    public static final String KEY_SESSION_TOKEN = "session_token";
 }
diff --git a/android/support/mediacompat/testlib/MediaControllerConstants.java b/android/support/mediacompat/testlib/MediaControllerConstants.java
new file mode 100644
index 0000000..1de00ef
--- /dev/null
+++ b/android/support/mediacompat/testlib/MediaControllerConstants.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2017 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.support.mediacompat.testlib;
+
+/**
+ * Constants for testing the media controller.
+ */
+public class MediaControllerConstants {
+
+    // MediaControllerCompat methods.
+    public static final int SEND_COMMAND = 201;
+    public static final int ADD_QUEUE_ITEM = 202;
+    public static final int ADD_QUEUE_ITEM_WITH_INDEX = 203;
+    public static final int REMOVE_QUEUE_ITEM = 204;
+
+    // TransportControls methods.
+    public static final int PLAY = 301;
+    public static final int PAUSE = 302;
+    public static final int STOP = 303;
+    public static final int FAST_FORWARD = 304;
+    public static final int REWIND = 305;
+    public static final int SKIP_TO_PREVIOUS = 306;
+    public static final int SKIP_TO_NEXT = 307;
+    public static final int SEEK_TO = 308;
+    public static final int SET_RATING = 309;
+    public static final int PLAY_FROM_MEDIA_ID = 310;
+    public static final int PLAY_FROM_SEARCH = 311;
+    public static final int PLAY_FROM_URI = 312;
+    public static final int SEND_CUSTOM_ACTION = 313;
+    public static final int SEND_CUSTOM_ACTION_PARCELABLE = 314;
+    public static final int SKIP_TO_QUEUE_ITEM = 315;
+    public static final int PREPARE = 316;
+    public static final int PREPARE_FROM_MEDIA_ID = 317;
+    public static final int PREPARE_FROM_SEARCH = 318;
+    public static final int PREPARE_FROM_URI = 319;
+    public static final int SET_CAPTIONING_ENABLED = 320;
+    public static final int SET_REPEAT_MODE = 321;
+    public static final int SET_SHUFFLE_MODE = 322;
+}
diff --git a/android/support/mediacompat/testlib/MediaSessionConstants.java b/android/support/mediacompat/testlib/MediaSessionConstants.java
new file mode 100644
index 0000000..79381e5
--- /dev/null
+++ b/android/support/mediacompat/testlib/MediaSessionConstants.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2017 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.support.mediacompat.testlib;
+
+/**
+ * Constants for testing the media session.
+ */
+public class MediaSessionConstants {
+
+    // MediaSessionCompat methods.
+    public static final int SET_EXTRAS = 101;
+    public static final int SET_FLAGS = 102;
+    public static final int SET_METADATA = 103;
+    public static final int SET_PLAYBACK_STATE = 104;
+    public static final int SET_QUEUE = 105;
+    public static final int SET_QUEUE_TITLE = 106;
+    public static final int SET_SESSION_ACTIVITY = 107;
+    public static final int SET_CAPTIONING_ENABLED = 108;
+    public static final int SET_REPEAT_MODE = 109;
+    public static final int SET_SHUFFLE_MODE = 110;
+    public static final int SEND_SESSION_EVENT = 112;
+    public static final int SET_ACTIVE = 113;
+    public static final int RELEASE = 114;
+    public static final int SET_PLAYBACK_TO_LOCAL = 115;
+    public static final int SET_PLAYBACK_TO_REMOTE = 116;
+    public static final int SET_RATING_TYPE = 117;
+
+    public static final String TEST_SESSION_TAG = "test-session-tag";
+    public static final String SERVICE_PACKAGE_NAME = "android.support.mediacompat.service.test";
+    public static final String TEST_KEY = "test-key";
+    public static final String TEST_VALUE = "test-val";
+    public static final String TEST_SESSION_EVENT = "test-session-event";
+    public static final String TEST_COMMAND = "test-command";
+    public static final int TEST_FLAGS = 5;
+    public static final int TEST_CURRENT_VOLUME = 10;
+    public static final int TEST_MAX_VOLUME = 11;
+    public static final long TEST_QUEUE_ID_1 = 10L;
+    public static final long TEST_QUEUE_ID_2 = 20L;
+    public static final String TEST_MEDIA_ID_1 = "media_id_1";
+    public static final String TEST_MEDIA_ID_2 = "media_id_2";
+    public static final long TEST_ACTION = 55L;
+
+    public static final int TEST_ERROR_CODE = 0x3;
+    public static final String TEST_ERROR_MSG = "test-error-msg";
+}
diff --git a/android/support/transition/AutoTransition.java b/android/support/transition/AutoTransition.java
index 02b49e2..bf39c3c 100644
--- a/android/support/transition/AutoTransition.java
+++ b/android/support/transition/AutoTransition.java
@@ -45,9 +45,9 @@
 
     private void init() {
         setOrdering(ORDERING_SEQUENTIAL);
-        addTransition(new Fade(Fade.OUT)).
-                addTransition(new ChangeBounds()).
-                addTransition(new Fade(Fade.IN));
+        addTransition(new Fade(Fade.OUT))
+                .addTransition(new ChangeBounds())
+                .addTransition(new Fade(Fade.IN));
     }
 
 }
diff --git a/android/support/v17/leanback/widget/GridLayoutManager.java b/android/support/v17/leanback/widget/GridLayoutManager.java
index 8143197..af37f77 100644
--- a/android/support/v17/leanback/widget/GridLayoutManager.java
+++ b/android/support/v17/leanback/widget/GridLayoutManager.java
@@ -2240,10 +2240,24 @@
             focusToViewInLayout(hadFocus, scrollToFocus, -deltaPrimary, -deltaSecondary);
             appendVisibleItems();
             prependVisibleItems();
-            removeInvisibleViewsAtFront();
-            removeInvisibleViewsAtEnd();
+            // b/67370222: do not removeInvisibleViewsAtFront/End() in the loop, otherwise
+            // loop may bounce between scroll forward and scroll backward forever. Example:
+            // Assuming there are 19 items, child#18 and child#19 are both in RV, we are
+            // trying to focus to child#18 and there are 200px remaining scroll distance.
+            //   1  focusToViewInLayout() tries scroll forward 50 px to align focused child#18 on
+            //      right edge, but there to compensate remaining scroll 200px, also scroll
+            //      backward 200px, 150px pushes last child#19 out side of right edge.
+            //   2  removeInvisibleViewsAtEnd() remove last child#19, updateScrollLimits()
+            //      invalidates scroll max
+            //   3  In next iteration, when scroll max/min is unknown, focusToViewInLayout() will
+            //      align focused child#18 at center of screen.
+            //   4  Because #18 is aligned at center, appendVisibleItems() will fill child#19 to
+            //      the right.
+            //   5  (back to 1 and loop forever)
         } while (mGrid.getFirstVisibleIndex() != oldFirstVisible
                 || mGrid.getLastVisibleIndex() != oldLastVisible);
+        removeInvisibleViewsAtFront();
+        removeInvisibleViewsAtEnd();
 
         if (state.willRunPredictiveAnimations()) {
             fillScrapViewsInPostLayout();
diff --git a/android/support/v4/content/res/ResourcesCompat.java b/android/support/v4/content/res/ResourcesCompat.java
index 4c70ce9..15b8ce9 100644
--- a/android/support/v4/content/res/ResourcesCompat.java
+++ b/android/support/v4/content/res/ResourcesCompat.java
@@ -307,11 +307,11 @@
      */
     @RestrictTo(LIBRARY_GROUP)
     public static Typeface getFont(@NonNull Context context, @FontRes int id, TypedValue value,
-            int style) throws NotFoundException {
+            int style, @Nullable FontCallback fontCallback) throws NotFoundException {
         if (context.isRestricted()) {
             return null;
         }
-        return loadFont(context, id, value, style, null /* callback */, null /* handler */,
+        return loadFont(context, id, value, style, fontCallback, null /* handler */,
                 true /* isXmlRequest */);
     }
 
diff --git a/android/support/v4/media/RatingCompat.java b/android/support/v4/media/RatingCompat.java
index b538cac..e70243f 100644
--- a/android/support/v4/media/RatingCompat.java
+++ b/android/support/v4/media/RatingCompat.java
@@ -18,6 +18,7 @@
 
 import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
 
+import android.media.Rating;
 import android.os.Build;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -326,25 +327,25 @@
      */
     public static RatingCompat fromRating(Object ratingObj) {
         if (ratingObj != null && Build.VERSION.SDK_INT >= 19) {
-            final int ratingStyle = RatingCompatKitkat.getRatingStyle(ratingObj);
+            final int ratingStyle = ((Rating) ratingObj).getRatingStyle();
             final RatingCompat rating;
-            if (RatingCompatKitkat.isRated(ratingObj)) {
+            if (((Rating) ratingObj).isRated()) {
                 switch (ratingStyle) {
                     case RATING_HEART:
-                        rating = newHeartRating(RatingCompatKitkat.hasHeart(ratingObj));
+                        rating = newHeartRating(((Rating) ratingObj).hasHeart());
                         break;
                     case RATING_THUMB_UP_DOWN:
-                        rating = newThumbRating(RatingCompatKitkat.isThumbUp(ratingObj));
+                        rating = newThumbRating(((Rating) ratingObj).isThumbUp());
                         break;
                     case RATING_3_STARS:
                     case RATING_4_STARS:
                     case RATING_5_STARS:
                         rating = newStarRating(ratingStyle,
-                                RatingCompatKitkat.getStarRating(ratingObj));
+                                ((Rating) ratingObj).getStarRating());
                         break;
                     case RATING_PERCENTAGE:
                         rating = newPercentageRating(
-                                RatingCompatKitkat.getPercentRating(ratingObj));
+                                ((Rating) ratingObj).getPercentRating());
                         break;
                     default:
                         return null;
@@ -372,25 +373,25 @@
             if (isRated()) {
                 switch (mRatingStyle) {
                     case RATING_HEART:
-                        mRatingObj = RatingCompatKitkat.newHeartRating(hasHeart());
+                        mRatingObj = Rating.newHeartRating(hasHeart());
                         break;
                     case RATING_THUMB_UP_DOWN:
-                        mRatingObj = RatingCompatKitkat.newThumbRating(isThumbUp());
+                        mRatingObj = Rating.newThumbRating(isThumbUp());
                         break;
                     case RATING_3_STARS:
                     case RATING_4_STARS:
                     case RATING_5_STARS:
-                        mRatingObj = RatingCompatKitkat.newStarRating(mRatingStyle,
+                        mRatingObj = Rating.newStarRating(mRatingStyle,
                                 getStarRating());
                         break;
                     case RATING_PERCENTAGE:
-                        mRatingObj = RatingCompatKitkat.newPercentageRating(getPercentRating());
+                        mRatingObj = Rating.newPercentageRating(getPercentRating());
                         break;
                     default:
                         return null;
                 }
             } else {
-                mRatingObj = RatingCompatKitkat.newUnratedRating(mRatingStyle);
+                mRatingObj = Rating.newUnratedRating(mRatingStyle);
             }
         }
         return mRatingObj;
diff --git a/android/support/v4/media/RatingCompatKitkat.java b/android/support/v4/media/RatingCompatKitkat.java
deleted file mode 100644
index 1d3fa50..0000000
--- a/android/support/v4/media/RatingCompatKitkat.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2014 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.support.v4.media;
-
-import android.media.Rating;
-import android.support.annotation.RequiresApi;
-
-@RequiresApi(19)
-class RatingCompatKitkat {
-    public static Object newUnratedRating(int ratingStyle) {
-        return Rating.newUnratedRating(ratingStyle);
-    }
-
-    public static Object newHeartRating(boolean hasHeart) {
-        return Rating.newHeartRating(hasHeart);
-    }
-
-    public static Object newThumbRating(boolean thumbIsUp) {
-        return Rating.newThumbRating(thumbIsUp);
-    }
-
-    public static Object newStarRating(int starRatingStyle, float starRating) {
-        return Rating.newStarRating(starRatingStyle, starRating);
-    }
-
-    public static Object newPercentageRating(float percent) {
-        return Rating.newPercentageRating(percent);
-    }
-
-    public static boolean isRated(Object ratingObj) {
-        return ((Rating)ratingObj).isRated();
-    }
-
-    public static int getRatingStyle(Object ratingObj) {
-        return ((Rating)ratingObj).getRatingStyle();
-    }
-
-    public static boolean hasHeart(Object ratingObj) {
-        return ((Rating)ratingObj).hasHeart();
-    }
-
-    public static boolean isThumbUp(Object ratingObj) {
-        return ((Rating)ratingObj).isThumbUp();
-    }
-
-    public static float getStarRating(Object ratingObj) {
-        return ((Rating)ratingObj).getStarRating();
-    }
-
-    public static float getPercentRating(Object ratingObj) {
-        return ((Rating)ratingObj).getPercentRating();
-    }
-}
diff --git a/android/support/v4/provider/FontsContractCompat.java b/android/support/v4/provider/FontsContractCompat.java
index 9ef1b0b..0926186 100644
--- a/android/support/v4/provider/FontsContractCompat.java
+++ b/android/support/v4/provider/FontsContractCompat.java
@@ -303,6 +303,9 @@
                     final ArrayList<ReplyCallback<TypefaceResult>> replies;
                     synchronized (sLock) {
                         replies = sPendingReplies.get(id);
+                        if (replies == null) {
+                            return;  // Nobody requested replies. Do nothing.
+                        }
                         sPendingReplies.remove(id);
                     }
                     for (int i = 0; i < replies.size(); ++i) {
diff --git a/android/support/v7/app/MediaRouteButton.java b/android/support/v7/app/MediaRouteButton.java
index d3f7020..fdbcf9a 100644
--- a/android/support/v7/app/MediaRouteButton.java
+++ b/android/support/v7/app/MediaRouteButton.java
@@ -121,8 +121,7 @@
     }
 
     public MediaRouteButton(Context context, AttributeSet attrs, int defStyleAttr) {
-        super(MediaRouterThemeHelper.createThemedContext(context, defStyleAttr), attrs,
-                defStyleAttr);
+        super(MediaRouterThemeHelper.createThemedButtonContext(context), attrs, defStyleAttr);
         context = getContext();
 
         mRouter = MediaRouter.getInstance(context);
diff --git a/android/support/v7/app/MediaRouteChooserDialog.java b/android/support/v7/app/MediaRouteChooserDialog.java
index 0ab2eb1..17364ef 100644
--- a/android/support/v7/app/MediaRouteChooserDialog.java
+++ b/android/support/v7/app/MediaRouteChooserDialog.java
@@ -92,10 +92,8 @@
     }
 
     public MediaRouteChooserDialog(Context context, int theme) {
-        // If we pass theme ID of 0 to AppCompatDialog, it will apply dialogTheme on the context,
-        // which may override our style settings. Passes our uppermost theme ID to prevent this.
-        super(MediaRouterThemeHelper.createThemedContext(context, theme),
-                theme == 0 ? MediaRouterThemeHelper.createThemeForDialog(context, theme) : theme);
+        super(context = MediaRouterThemeHelper.createThemedDialogContext(context, theme, false),
+                MediaRouterThemeHelper.createThemedDialogStyle(context));
         context = getContext();
 
         mRouter = MediaRouter.getInstance(context);
diff --git a/android/support/v7/app/MediaRouteControllerDialog.java b/android/support/v7/app/MediaRouteControllerDialog.java
index 4b9a17a..d89bf21 100644
--- a/android/support/v7/app/MediaRouteControllerDialog.java
+++ b/android/support/v7/app/MediaRouteControllerDialog.java
@@ -201,12 +201,8 @@
     }
 
     public MediaRouteControllerDialog(Context context, int theme) {
-        // If we pass theme ID of 0 to AppCompatDialog, it will apply dialogTheme on the context,
-        // which may override our style settings. Passes our uppermost theme ID to prevent this.
-        super(MediaRouterThemeHelper.createThemedContext(context,
-                MediaRouterThemeHelper.getAlertDialogResolvedTheme(context, theme)), theme == 0
-                ? MediaRouterThemeHelper.createThemeForDialog(context, MediaRouterThemeHelper
-                        .getAlertDialogResolvedTheme(context, theme)) : theme);
+        super(context = MediaRouterThemeHelper.createThemedDialogContext(context, theme, true),
+                MediaRouterThemeHelper.createThemedDialogStyle(context));
         mContext = getContext();
 
         mControllerCallback = new MediaControllerCallback();
diff --git a/android/support/v7/app/MediaRouterThemeHelper.java b/android/support/v7/app/MediaRouterThemeHelper.java
index 9ef218e..69e40ac 100644
--- a/android/support/v7/app/MediaRouterThemeHelper.java
+++ b/android/support/v7/app/MediaRouterThemeHelper.java
@@ -42,47 +42,76 @@
     private MediaRouterThemeHelper() {
     }
 
-    /**
-     * Creates a themed context based on the explicit style resource or the parent context's default
-     * theme.
-     * <p>
-     * The theme which will be applied on top of the parent {@code context}'s theme is determined
-     * by the primary color defined in the given {@code style}, or in the parent {@code context}.
+    static Context createThemedButtonContext(Context context) {
+        // Apply base Media Router theme.
+        context = new ContextThemeWrapper(context, getRouterThemeId(context));
+
+        // Apply custom Media Router theme.
+        int style = getThemeResource(context, R.attr.mediaRouteTheme);
+        if (style != 0) {
+            context = new ContextThemeWrapper(context, style);
+        }
+
+        return context;
+    }
+
+    /*
+     * The following two methods are to be used in conjunction. They should be used to prepare
+     * the context and theme for a super class constructor (the latter method relies on the
+     * former method to properly prepare the context):
+     *   super(context = createThemedDialogContext(context, theme),
+     *           createThemedDialogStyle(context));
      *
-     * @param context the parent context
-     * @param style the resource ID of the style against which to inflate this context, or
-     *              {@code 0} to use the parent {@code context}'s default theme.
-     * @return The themed context.
+     * It will apply theme in the following order (style lookups will be done in reverse):
+     *   1) Current theme
+     *   2) Supplied theme
+     *   3) Base Media Router theme
+     *   4) Custom Media Router theme, if provided
      */
-    static Context createThemedContext(Context context, int style) {
-        // First, apply dialog property overlay.
-        Context themedContext =
-                new ContextThemeWrapper(context, getStyledRouterThemeId(context, style));
-        int customizedThemeId = getThemeResource(context, R.attr.mediaRouteTheme);
-        return customizedThemeId == 0 ? themedContext
-                : new ContextThemeWrapper(themedContext, customizedThemeId);
-    }
+    static Context createThemedDialogContext(Context context, int theme, boolean alertDialog) {
+        // 1) Current theme is already applied to the context
 
-    /**
-     * Creates the theme resource ID intended to be used by dialogs.
-     */
-    static int createThemeForDialog(Context context, int style) {
-        int customizedThemeId = getThemeResource(context, R.attr.mediaRouteTheme);
-        return customizedThemeId != 0 ? customizedThemeId : getStyledRouterThemeId(context, style);
-    }
+        // 2) If no theme is supplied, look it up from the context (dialogTheme/alertDialogTheme)
+        if (theme == 0) {
+            theme = getThemeResource(context, !alertDialog
+                    ? android.support.v7.appcompat.R.attr.dialogTheme
+                    : android.support.v7.appcompat.R.attr.alertDialogTheme);
+        }
+        //    Apply it
+        context = new ContextThemeWrapper(context, theme);
 
-    public static int getThemeResource(Context context, int attr) {
+        // 3) If a custom Media Router theme is provided then apply the base theme
+        if (getThemeResource(context, R.attr.mediaRouteTheme) != 0) {
+            context = new ContextThemeWrapper(context, getRouterThemeId(context));
+        }
+
+        return context;
+    }
+    // This method should be used in conjunction with the previous method.
+    static int createThemedDialogStyle(Context context) {
+        // 4) Apply the custom Media Router theme
+        int theme = getThemeResource(context, R.attr.mediaRouteTheme);
+        if (theme == 0) {
+            // 3) No custom MediaRouther theme was provided so apply the base theme instead
+            theme = getRouterThemeId(context);
+        }
+
+        return theme;
+    }
+    // END. Previous two methods should be used in conjunction.
+
+    static int getThemeResource(Context context, int attr) {
         TypedValue value = new TypedValue();
         return context.getTheme().resolveAttribute(attr, value, true) ? value.resourceId : 0;
     }
 
-    public static float getDisabledAlpha(Context context) {
+    static float getDisabledAlpha(Context context) {
         TypedValue value = new TypedValue();
         return context.getTheme().resolveAttribute(android.R.attr.disabledAlpha, value, true)
                 ? value.getFloat() : 0.5f;
     }
 
-    public static @ControllerColorType int getControllerColor(Context context, int style) {
+    static @ControllerColorType int getControllerColor(Context context, int style) {
         int primaryColor = getThemeColor(context, style,
                 android.support.v7.appcompat.R.attr.colorPrimary);
         if (ColorUtils.calculateContrast(COLOR_WHITE_ON_DARK_BACKGROUND, primaryColor)
@@ -92,7 +121,7 @@
         return COLOR_DARK_ON_LIGHT_BACKGROUND;
     }
 
-    public static int getButtonTextColor(Context context) {
+    static int getButtonTextColor(Context context) {
         int primaryColor = getThemeColor(context, 0,
                 android.support.v7.appcompat.R.attr.colorPrimary);
         int backgroundColor = getThemeColor(context, 0, android.R.attr.colorBackground);
@@ -104,7 +133,7 @@
         return primaryColor;
     }
 
-    public static void setMediaControlsBackgroundColor(
+    static void setMediaControlsBackgroundColor(
             Context context, View mainControls, View groupControls, boolean hasGroup) {
         int primaryColor = getThemeColor(context, 0,
                 android.support.v7.appcompat.R.attr.colorPrimary);
@@ -124,7 +153,7 @@
         groupControls.setTag(primaryDarkColor);
     }
 
-    public static void setVolumeSliderColor(
+    static void setVolumeSliderColor(
             Context context, MediaRouteVolumeSlider volumeSlider, View backgroundView) {
         int controllerColor = getControllerColor(context, 0);
         if (Color.alpha(controllerColor) != 0xFF) {
@@ -136,23 +165,10 @@
         volumeSlider.setColor(controllerColor);
     }
 
-    // This is copied from {@link AlertDialog#resolveDialogTheme} to pre-evaluate theme in advance.
-    public static int getAlertDialogResolvedTheme(Context context, int themeResId) {
-        if (themeResId >= 0x01000000) {   // start of real resource IDs.
-            return themeResId;
-        } else {
-            TypedValue outValue = new TypedValue();
-            context.getTheme().resolveAttribute(
-                    android.support.v7.appcompat.R.attr.alertDialogTheme, outValue, true);
-            return outValue.resourceId;
-        }
-    }
-
     private static boolean isLightTheme(Context context) {
         TypedValue value = new TypedValue();
-        return context.getTheme().resolveAttribute(
-                android.support.v7.appcompat.R.attr.isLightTheme, value, true)
-                && value.data != 0;
+        return context.getTheme().resolveAttribute(android.support.v7.appcompat.R.attr.isLightTheme,
+                value, true) && value.data != 0;
     }
 
     private static int getThemeColor(Context context, int style, int attr) {
@@ -173,16 +189,16 @@
         return value.data;
     }
 
-    private static int getStyledRouterThemeId(Context context, int style) {
+    private static int getRouterThemeId(Context context) {
         int themeId;
         if (isLightTheme(context)) {
-            if (getControllerColor(context, style) == COLOR_DARK_ON_LIGHT_BACKGROUND) {
+            if (getControllerColor(context, 0) == COLOR_DARK_ON_LIGHT_BACKGROUND) {
                 themeId = R.style.Theme_MediaRouter_Light;
             } else {
                 themeId = R.style.Theme_MediaRouter_Light_DarkControlPanel;
             }
         } else {
-            if (getControllerColor(context, style) == COLOR_DARK_ON_LIGHT_BACKGROUND) {
+            if (getControllerColor(context, 0) == COLOR_DARK_ON_LIGHT_BACKGROUND) {
                 themeId = R.style.Theme_MediaRouter_LightControlPanel;
             } else {
                 themeId = R.style.Theme_MediaRouter;
diff --git a/android/support/v7/recyclerview/extensions/ListAdapterHelperTest.java b/android/support/v7/recyclerview/extensions/ListAdapterHelperTest.java
deleted file mode 100644
index aab7417..0000000
--- a/android/support/v7/recyclerview/extensions/ListAdapterHelperTest.java
+++ /dev/null
@@ -1,164 +0,0 @@
-/*
- * Copyright (C) 2017 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.support.v7.recyclerview.extensions;
-
-import static org.junit.Assert.assertEquals;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.verifyZeroInteractions;
-
-import android.arch.paging.TestExecutor;
-import android.support.annotation.NonNull;
-import android.support.test.filters.SmallTest;
-import android.support.v7.util.ListUpdateCallback;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import java.util.Arrays;
-
-@SmallTest
-@RunWith(JUnit4.class)
-public class ListAdapterHelperTest {
-    private TestExecutor mMainThread = new TestExecutor();
-    private TestExecutor mBackgroundThread = new TestExecutor();
-
-
-    private static final DiffCallback<String> STRING_DIFF_CALLBACK = new DiffCallback<String>() {
-        @Override
-        public boolean areItemsTheSame(@NonNull String oldItem, @NonNull String newItem) {
-            return oldItem.equals(newItem);
-        }
-
-        @Override
-        public boolean areContentsTheSame(@NonNull String oldItem, @NonNull String newItem) {
-            return oldItem.equals(newItem);
-        }
-    };
-
-    private static final ListUpdateCallback IGNORE_CALLBACK = new ListUpdateCallback() {
-        @Override
-        public void onInserted(int position, int count) {
-        }
-
-        @Override
-        public void onRemoved(int position, int count) {
-        }
-
-        @Override
-        public void onMoved(int fromPosition, int toPosition) {
-        }
-
-        @Override
-        public void onChanged(int position, int count, Object payload) {
-        }
-    };
-
-
-    private <T> ListAdapterHelper<T> createHelper(
-            ListUpdateCallback listUpdateCallback, DiffCallback<T> diffCallback) {
-        return new ListAdapterHelper<T>(listUpdateCallback,
-                new ListAdapterConfig.Builder<T>()
-                        .setDiffCallback(diffCallback)
-                        .setMainThreadExecutor(mMainThread)
-                        .setBackgroundThreadExecutor(mBackgroundThread)
-                        .build());
-    }
-
-    @Test
-    public void initialState() {
-        ListUpdateCallback callback = mock(ListUpdateCallback.class);
-        ListAdapterHelper<String> helper = createHelper(callback, STRING_DIFF_CALLBACK);
-        assertEquals(0, helper.getItemCount());
-        verifyZeroInteractions(callback);
-    }
-
-    @Test(expected = IndexOutOfBoundsException.class)
-    public void getEmpty() {
-        ListAdapterHelper<String> helper = createHelper(IGNORE_CALLBACK, STRING_DIFF_CALLBACK);
-        helper.getItem(0);
-    }
-
-    @Test(expected = IndexOutOfBoundsException.class)
-    public void getNegative() {
-        ListAdapterHelper<String> helper = createHelper(IGNORE_CALLBACK, STRING_DIFF_CALLBACK);
-        helper.setList(Arrays.asList("a", "b"));
-        helper.getItem(-1);
-    }
-
-    @Test(expected = IndexOutOfBoundsException.class)
-    public void getPastEnd() {
-        ListAdapterHelper<String> helper = createHelper(IGNORE_CALLBACK, STRING_DIFF_CALLBACK);
-        helper.setList(Arrays.asList("a", "b"));
-        helper.getItem(2);
-    }
-
-    @Test
-    public void setListSimple() {
-        ListUpdateCallback callback = mock(ListUpdateCallback.class);
-        ListAdapterHelper<String> helper = createHelper(callback, STRING_DIFF_CALLBACK);
-
-        helper.setList(Arrays.asList("a", "b"));
-
-        assertEquals(2, helper.getItemCount());
-        assertEquals("a", helper.getItem(0));
-        assertEquals("b", helper.getItem(1));
-
-        verify(callback).onInserted(0, 2);
-        verifyNoMoreInteractions(callback);
-        drain();
-        verifyNoMoreInteractions(callback);
-    }
-
-    @Test
-    public void setListUpdate() {
-        ListUpdateCallback callback = mock(ListUpdateCallback.class);
-        ListAdapterHelper<String> helper = createHelper(callback, STRING_DIFF_CALLBACK);
-
-        // initial list (immediate)
-        helper.setList(Arrays.asList("a", "b"));
-        verify(callback).onInserted(0, 2);
-        verifyNoMoreInteractions(callback);
-        drain();
-        verifyNoMoreInteractions(callback);
-
-        // update (deferred)
-        helper.setList(Arrays.asList("a", "b", "c"));
-        verifyNoMoreInteractions(callback);
-        drain();
-        verify(callback).onInserted(2, 1);
-        verifyNoMoreInteractions(callback);
-
-        // clear (immediate)
-        helper.setList(null);
-        verify(callback).onRemoved(0, 3);
-        verifyNoMoreInteractions(callback);
-        drain();
-        verifyNoMoreInteractions(callback);
-
-    }
-
-    private void drain() {
-        boolean executed;
-        do {
-            executed = mBackgroundThread.executeAll();
-            executed |= mMainThread.executeAll();
-        } while (executed);
-    }
-}
diff --git a/android/support/v7/util/DiffUtil.java b/android/support/v7/util/DiffUtil.java
index 6302666..ebc33f3 100644
--- a/android/support/v7/util/DiffUtil.java
+++ b/android/support/v7/util/DiffUtil.java
@@ -16,6 +16,7 @@
 
 package android.support.v7.util;
 
+import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.annotation.VisibleForTesting;
 import android.support.v7.widget.RecyclerView;
@@ -348,6 +349,72 @@
     }
 
     /**
+     * Callback for calculating the diff between two non-null items in a list.
+     * <p>
+     * {@link Callback} serves two roles - list indexing, and item diffing. ItemCallback handles
+     * just the second of these, which allows separation of code that indexes into an array or List
+     * from the presentation-layer and content specific diffing code.
+     *
+     * @param <T> Type of items to compare.
+     */
+    public abstract static class ItemCallback<T> {
+        /**
+         * Called to check whether two objects represent the same item.
+         * <p>
+         * For example, if your items have unique ids, this method should check their id equality.
+         *
+         * @param oldItem The item in the old list.
+         * @param newItem The item in the new list.
+         * @return True if the two items represent the same object or false if they are different.
+         *
+         * @see Callback#areItemsTheSame(int, int)
+         */
+        public abstract boolean areItemsTheSame(@NonNull T oldItem, @NonNull T newItem);
+
+        /**
+         * Called to check whether two items have the same data.
+         * <p>
+         * This information is used to detect if the contents of an item have changed.
+         * <p>
+         * This method to check equality instead of {@link Object#equals(Object)} so that you can
+         * change its behavior depending on your UI.
+         * <p>
+         * For example, if you are using DiffUtil with a
+         * {@link android.support.v7.widget.RecyclerView.Adapter RecyclerView.Adapter}, you should
+         * return whether the items' visual representations are the same.
+         * <p>
+         * This method is called only if {@link #areItemsTheSame(T, T)} returns {@code true} for
+         * these items.
+         *
+         * @param oldItem The item in the old list.
+         * @param newItem The item in the new list.
+         * @return True if the contents of the items are the same or false if they are different.
+         *
+         * @see Callback#areContentsTheSame(int, int)
+         */
+        public abstract boolean areContentsTheSame(@NonNull T oldItem, @NonNull T newItem);
+
+        /**
+         * When {@link #areItemsTheSame(T, T)} returns {@code true} for two items and
+         * {@link #areContentsTheSame(T, T)} returns false for them, this method is called to
+         * get a payload about the change.
+         * <p>
+         * For example, if you are using DiffUtil with {@link RecyclerView}, you can return the
+         * particular field that changed in the item and your
+         * {@link android.support.v7.widget.RecyclerView.ItemAnimator ItemAnimator} can use that
+         * information to run the correct animation.
+         * <p>
+         * Default implementation returns {@code null}.
+         *
+         * @see Callback#getChangePayload(int, int)
+         */
+        @SuppressWarnings({"WeakerAccess", "unused"})
+        public Object getChangePayload(@NonNull T oldItem, @NonNull T newItem) {
+            return null;
+        }
+    }
+
+    /**
      * Snakes represent a match between two lists. It is optionally prefixed or postfixed with an
      * add or remove operation. See the Myers' paper for details.
      */
diff --git a/android/support/v7/widget/AppCompatTextHelper.java b/android/support/v7/widget/AppCompatTextHelper.java
index 51510aa..fa6196f 100644
--- a/android/support/v7/widget/AppCompatTextHelper.java
+++ b/android/support/v7/widget/AppCompatTextHelper.java
@@ -29,6 +29,7 @@
 import android.support.annotation.NonNull;
 import android.support.annotation.RequiresApi;
 import android.support.annotation.RestrictTo;
+import android.support.v4.content.res.ResourcesCompat;
 import android.support.v4.widget.TextViewCompat;
 import android.support.v7.appcompat.R;
 import android.text.method.PasswordTransformationMethod;
@@ -36,6 +37,8 @@
 import android.util.TypedValue;
 import android.widget.TextView;
 
+import java.lang.ref.WeakReference;
+
 @RequiresApi(9)
 class AppCompatTextHelper {
 
@@ -63,6 +66,7 @@
 
     private int mStyle = Typeface.NORMAL;
     private Typeface mFontTypeface;
+    private boolean mAsyncFontPending;
 
     AppCompatTextHelper(TextView view) {
         mView = view;
@@ -213,8 +217,23 @@
                     ? R.styleable.TextAppearance_android_fontFamily
                     : R.styleable.TextAppearance_fontFamily;
             if (!context.isRestricted()) {
+                final WeakReference<TextView> textViewWeak = new WeakReference<>(mView);
+                ResourcesCompat.FontCallback replyCallback = new ResourcesCompat.FontCallback() {
+                    @Override
+                    public void onFontRetrieved(@NonNull Typeface typeface) {
+                        onAsyncTypefaceReceived(textViewWeak, typeface);
+                    }
+
+                    @Override
+                    public void onFontRetrievalFailed(int reason) {
+                        // Do nothing.
+                    }
+                };
                 try {
-                    mFontTypeface = a.getFont(fontFamilyId, mStyle);
+                    // Note the callback will be triggered on the UI thread.
+                    mFontTypeface = a.getFont(fontFamilyId, mStyle, replyCallback);
+                    // If this call gave us an immediate result, ignore any pending callbacks.
+                    mAsyncFontPending = mFontTypeface == null;
                 } catch (UnsupportedOperationException | Resources.NotFoundException e) {
                     // Expected if it is not a font resource.
                 }
@@ -222,12 +241,16 @@
             if (mFontTypeface == null) {
                 // Try with String. This is done by TextView JB+, but fails in ICS
                 String fontFamilyName = a.getString(fontFamilyId);
-                mFontTypeface = Typeface.create(fontFamilyName, mStyle);
+                if (fontFamilyName != null) {
+                    mFontTypeface = Typeface.create(fontFamilyName, mStyle);
+                }
             }
             return;
         }
 
         if (a.hasValue(R.styleable.TextAppearance_android_typeface)) {
+            // Ignore previous pending fonts
+            mAsyncFontPending = false;
             int typefaceIndex = a.getInt(R.styleable.TextAppearance_android_typeface, SANS);
             switch (typefaceIndex) {
                 case SANS:
@@ -245,6 +268,16 @@
         }
     }
 
+    private void onAsyncTypefaceReceived(WeakReference<TextView> textViewWeak, Typeface typeface) {
+        if (mAsyncFontPending) {
+            mFontTypeface = typeface;
+            final TextView textView = textViewWeak.get();
+            if (textView != null) {
+                textView.setTypeface(typeface, mStyle);
+            }
+        }
+    }
+
     void onSetTextAppearance(Context context, int resId) {
         final TintTypedArray a = TintTypedArray.obtainStyledAttributes(context,
                 resId, R.styleable.TextAppearance);
diff --git a/android/support/v7/widget/RecyclerView.java b/android/support/v7/widget/RecyclerView.java
index 7009733..4bc17a8 100644
--- a/android/support/v7/widget/RecyclerView.java
+++ b/android/support/v7/widget/RecyclerView.java
@@ -6408,16 +6408,16 @@
         }
 
         void markKnownViewsInvalid() {
-            if (mAdapter != null && mAdapter.hasStableIds()) {
-                final int cachedCount = mCachedViews.size();
-                for (int i = 0; i < cachedCount; i++) {
-                    final ViewHolder holder = mCachedViews.get(i);
-                    if (holder != null) {
-                        holder.addFlags(ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID);
-                        holder.addChangePayload(null);
-                    }
+            final int cachedCount = mCachedViews.size();
+            for (int i = 0; i < cachedCount; i++) {
+                final ViewHolder holder = mCachedViews.get(i);
+                if (holder != null) {
+                    holder.addFlags(ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID);
+                    holder.addChangePayload(null);
                 }
-            } else {
+            }
+
+            if (mAdapter == null || !mAdapter.hasStableIds()) {
                 // we cannot re-use cached views in this case. Recycle them all
                 recycleAndClearCachedViews();
             }
diff --git a/android/support/v7/widget/TintTypedArray.java b/android/support/v7/widget/TintTypedArray.java
index 2270955..384c461 100644
--- a/android/support/v7/widget/TintTypedArray.java
+++ b/android/support/v7/widget/TintTypedArray.java
@@ -106,7 +106,8 @@
      *         not a font resource.
      */
     @Nullable
-    public Typeface getFont(@StyleableRes int index, int style) {
+    public Typeface getFont(@StyleableRes int index, int style,
+            @Nullable ResourcesCompat.FontCallback fontCallback) {
         final int resourceId = mWrapped.getResourceId(index, 0);
         if (resourceId == 0) {
             return null;
@@ -114,7 +115,7 @@
         if (mTypedValue == null) {
             mTypedValue = new TypedValue();
         }
-        return ResourcesCompat.getFont(mContext, resourceId, mTypedValue, style);
+        return ResourcesCompat.getFont(mContext, resourceId, mTypedValue, style, fontCallback);
     }
 
     public int length() {
diff --git a/android/support/wear/ambient/AmbientMode.java b/android/support/wear/ambient/AmbientMode.java
index 7fbbbb3..db53dfc 100644
--- a/android/support/wear/ambient/AmbientMode.java
+++ b/android/support/wear/ambient/AmbientMode.java
@@ -22,7 +22,6 @@
 import android.os.Bundle;
 import android.support.annotation.CallSuper;
 import android.support.annotation.VisibleForTesting;
-import android.util.Log;
 
 import com.google.android.wearable.compat.WearableActivityController;
 
@@ -263,20 +262,6 @@
         AmbientController() {}
 
         /**
-         * Sets whether this activity's task should be moved to the front when the system exits
-         * ambient mode. If true, the activity's task may be moved to the front if it was the last
-         * activity to be running when ambient started, depending on how much time the system spent
-         * in ambient mode.
-         */
-        public void setAutoResumeEnabled(boolean enabled) {
-            if (mDelegate != null) {
-                mDelegate.setAutoResumeEnabled(enabled);
-            } else {
-                Log.w(TAG, "The fragment is not yet fully initialized, this call is a no-op");
-            }
-        }
-
-        /**
          * @return {@code true} if the activity is currently in ambient.
          */
         public boolean isAmbient() {
diff --git a/android/system/ErrnoException.java b/android/system/ErrnoException.java
index 90155c8..e60ac8f 100644
--- a/android/system/ErrnoException.java
+++ b/android/system/ErrnoException.java
@@ -26,57 +26,57 @@
  * callers need to adjust their behavior based on the exact failure.
  */
 public final class ErrnoException extends Exception {
-  private final String functionName;
+    private final String functionName;
 
-  /**
-   * The errno value, for comparison with the {@code E} constants in {@link OsConstants}.
-   */
-  public final int errno;
+    /**
+     * The errno value, for comparison with the {@code E} constants in {@link OsConstants}.
+     */
+    public final int errno;
 
-  /**
-   * Constructs an instance with the given function name and errno value.
-   */
-  public ErrnoException(String functionName, int errno) {
-    this.functionName = functionName;
-    this.errno = errno;
-  }
-
-  /**
-   * Constructs an instance with the given function name, errno value, and cause.
-   */
-  public ErrnoException(String functionName, int errno, Throwable cause) {
-    super(cause);
-    this.functionName = functionName;
-    this.errno = errno;
-  }
-
-  /**
-   * Converts the stashed function name and errno value to a human-readable string.
-   * We do this here rather than in the constructor so that callers only pay for
-   * this if they need it.
-   */
-  @Override public String getMessage() {
-    String errnoName = OsConstants.errnoName(errno);
-    if (errnoName == null) {
-      errnoName = "errno " + errno;
+    /**
+     * Constructs an instance with the given function name and errno value.
+     */
+    public ErrnoException(String functionName, int errno) {
+        this.functionName = functionName;
+        this.errno = errno;
     }
-    String description = Libcore.os.strerror(errno);
-    return functionName + " failed: " + errnoName + " (" + description + ")";
-  }
 
-  /**
-   * @hide - internal use only.
-   */
-  public IOException rethrowAsIOException() throws IOException {
-    IOException newException = new IOException(getMessage());
-    newException.initCause(this);
-    throw newException;
-  }
+    /**
+     * Constructs an instance with the given function name, errno value, and cause.
+     */
+    public ErrnoException(String functionName, int errno, Throwable cause) {
+        super(cause);
+        this.functionName = functionName;
+        this.errno = errno;
+    }
 
-  /**
-   * @hide - internal use only.
-   */
-  public SocketException rethrowAsSocketException() throws SocketException {
-    throw new SocketException(getMessage(), this);
-  }
+    /**
+     * Converts the stashed function name and errno value to a human-readable string.
+     * We do this here rather than in the constructor so that callers only pay for
+     * this if they need it.
+     */
+    @Override public String getMessage() {
+        String errnoName = OsConstants.errnoName(errno);
+        if (errnoName == null) {
+            errnoName = "errno " + errno;
+        }
+        String description = Libcore.os.strerror(errno);
+        return functionName + " failed: " + errnoName + " (" + description + ")";
+    }
+
+    /**
+     * @hide - internal use only.
+     */
+    public IOException rethrowAsIOException() throws IOException {
+        IOException newException = new IOException(getMessage());
+        newException.initCause(this);
+        throw newException;
+    }
+
+    /**
+     * @hide - internal use only.
+     */
+    public SocketException rethrowAsSocketException() throws SocketException {
+        throw new SocketException(getMessage(), this);
+    }
 }
diff --git a/android/system/GaiException.java b/android/system/GaiException.java
index dc10566..182cc3e 100644
--- a/android/system/GaiException.java
+++ b/android/system/GaiException.java
@@ -27,57 +27,57 @@
  * @hide
  */
 public final class GaiException extends RuntimeException {
-  private final String functionName;
+    private final String functionName;
 
-  /**
-   * The native error value, for comparison with the {@code GAI_} constants in {@link OsConstants}.
-   */
-  public final int error;
+    /**
+     * The native error value, for comparison with the {@code GAI_} constants in {@link OsConstants}.
+     */
+    public final int error;
 
-  /**
-   * Constructs an instance with the given function name and error value.
-   */
-  public GaiException(String functionName, int error) {
-    this.functionName = functionName;
-    this.error = error;
-  }
-
-  /**
-   * Constructs an instance with the given function name, error value, and cause.
-   */
-  public GaiException(String functionName, int error, Throwable cause) {
-    super(cause);
-    this.functionName = functionName;
-    this.error = error;
-  }
-
-  /**
-   * Converts the stashed function name and error value to a human-readable string.
-   * We do this here rather than in the constructor so that callers only pay for
-   * this if they need it.
-   */
-  @Override public String getMessage() {
-    String gaiName = OsConstants.gaiName(error);
-    if (gaiName == null) {
-      gaiName = "GAI_ error " + error;
+    /**
+     * Constructs an instance with the given function name and error value.
+     */
+    public GaiException(String functionName, int error) {
+        this.functionName = functionName;
+        this.error = error;
     }
-    String description = Libcore.os.gai_strerror(error);
-    return functionName + " failed: " + gaiName + " (" + description + ")";
-  }
 
-  /**
-   * @hide - internal use only.
-   */
-  public UnknownHostException rethrowAsUnknownHostException(String detailMessage) throws UnknownHostException {
-    UnknownHostException newException = new UnknownHostException(detailMessage);
-    newException.initCause(this);
-    throw newException;
-  }
+    /**
+     * Constructs an instance with the given function name, error value, and cause.
+     */
+    public GaiException(String functionName, int error, Throwable cause) {
+        super(cause);
+        this.functionName = functionName;
+        this.error = error;
+    }
 
-  /**
-   * @hide - internal use only.
-   */
-  public UnknownHostException rethrowAsUnknownHostException() throws UnknownHostException {
-    throw rethrowAsUnknownHostException(getMessage());
-  }
+    /**
+     * Converts the stashed function name and error value to a human-readable string.
+     * We do this here rather than in the constructor so that callers only pay for
+     * this if they need it.
+     */
+    @Override public String getMessage() {
+        String gaiName = OsConstants.gaiName(error);
+        if (gaiName == null) {
+            gaiName = "GAI_ error " + error;
+        }
+        String description = Libcore.os.gai_strerror(error);
+        return functionName + " failed: " + gaiName + " (" + description + ")";
+    }
+
+    /**
+     * @hide - internal use only.
+     */
+    public UnknownHostException rethrowAsUnknownHostException(String detailMessage) throws UnknownHostException {
+        UnknownHostException newException = new UnknownHostException(detailMessage);
+        newException.initCause(this);
+        throw newException;
+    }
+
+    /**
+     * @hide - internal use only.
+     */
+    public UnknownHostException rethrowAsUnknownHostException() throws UnknownHostException {
+        throw rethrowAsUnknownHostException(getMessage());
+    }
 }
diff --git a/android/system/Os.java b/android/system/Os.java
index 8e312dd..2dabae2 100644
--- a/android/system/Os.java
+++ b/android/system/Os.java
@@ -35,592 +35,597 @@
  * <p>The corresponding constants can be found in {@link OsConstants}.
  */
 public final class Os {
-  private Os() {}
+    private Os() {}
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man2/accept.2.html">accept(2)</a>.
-   */
-  public static FileDescriptor accept(FileDescriptor fd, InetSocketAddress peerAddress) throws ErrnoException, SocketException { return Libcore.os.accept(fd, peerAddress); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/accept.2.html">accept(2)</a>.
+     */
+    public static FileDescriptor accept(FileDescriptor fd, InetSocketAddress peerAddress) throws ErrnoException, SocketException { return Libcore.os.accept(fd, peerAddress); }
 
-  /**
-   * TODO Change the public API by removing the overload above and unhiding this version.
-   * @hide
-   */
-  public static FileDescriptor accept(FileDescriptor fd, SocketAddress peerAddress) throws ErrnoException, SocketException { return Libcore.os.accept(fd, peerAddress); }
+    /**
+     * TODO Change the public API by removing the overload above and unhiding this version.
+     * @hide
+     */
+    public static FileDescriptor accept(FileDescriptor fd, SocketAddress peerAddress) throws ErrnoException, SocketException { return Libcore.os.accept(fd, peerAddress); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man2/access.2.html">access(2)</a>.
-   */
-  public static boolean access(String path, int mode) throws ErrnoException { return Libcore.os.access(path, mode); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/access.2.html">access(2)</a>.
+     */
+    public static boolean access(String path, int mode) throws ErrnoException { return Libcore.os.access(path, mode); }
 
-  /** @hide */ public static InetAddress[] android_getaddrinfo(String node, StructAddrinfo hints, int netId) throws GaiException { return Libcore.os.android_getaddrinfo(node, hints, netId); }
+    /** @hide */ public static InetAddress[] android_getaddrinfo(String node, StructAddrinfo hints, int netId) throws GaiException { return Libcore.os.android_getaddrinfo(node, hints, netId); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man2/bind.2.html">bind(2)</a>.
-   */
-  public static void bind(FileDescriptor fd, InetAddress address, int port) throws ErrnoException, SocketException { Libcore.os.bind(fd, address, port); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/bind.2.html">bind(2)</a>.
+     */
+    public static void bind(FileDescriptor fd, InetAddress address, int port) throws ErrnoException, SocketException { Libcore.os.bind(fd, address, port); }
 
-  /** @hide */ public static void bind(FileDescriptor fd, SocketAddress address) throws ErrnoException, SocketException { Libcore.os.bind(fd, address); }
+    /** @hide */ public static void bind(FileDescriptor fd, SocketAddress address) throws ErrnoException, SocketException { Libcore.os.bind(fd, address); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man2/capget.2.html">capget(2)</a>.
-   *
-   * @hide
-   */
-  public static StructCapUserData[] capget(StructCapUserHeader hdr) throws ErrnoException {
-      return Libcore.os.capget(hdr);
-  }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/capget.2.html">capget(2)</a>.
+     *
+     * @hide
+     */
+    public static StructCapUserData[] capget(StructCapUserHeader hdr) throws ErrnoException {
+        return Libcore.os.capget(hdr);
+    }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man2/capset.2.html">capset(2)</a>.
-   *
-   * @hide
-   */
-  public static void capset(StructCapUserHeader hdr, StructCapUserData[] data)
-          throws ErrnoException {
-      Libcore.os.capset(hdr, data);
-  }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/capset.2.html">capset(2)</a>.
+     *
+     * @hide
+     */
+    public static void capset(StructCapUserHeader hdr, StructCapUserData[] data)
+            throws ErrnoException {
+        Libcore.os.capset(hdr, data);
+    }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man2/chmod.2.html">chmod(2)</a>.
-   */
-  public static void chmod(String path, int mode) throws ErrnoException { Libcore.os.chmod(path, mode); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/chmod.2.html">chmod(2)</a>.
+     */
+    public static void chmod(String path, int mode) throws ErrnoException { Libcore.os.chmod(path, mode); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man2/chown.2.html">chown(2)</a>.
-   */
-  public static void chown(String path, int uid, int gid) throws ErrnoException { Libcore.os.chown(path, uid, gid); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/chown.2.html">chown(2)</a>.
+     */
+    public static void chown(String path, int uid, int gid) throws ErrnoException { Libcore.os.chown(path, uid, gid); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man2/close.2.html">close(2)</a>.
-   */
-  public static void close(FileDescriptor fd) throws ErrnoException { Libcore.os.close(fd); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/close.2.html">close(2)</a>.
+     */
+    public static void close(FileDescriptor fd) throws ErrnoException { Libcore.os.close(fd); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man2/connect.2.html">connect(2)</a>.
-   */
-  public static void connect(FileDescriptor fd, InetAddress address, int port) throws ErrnoException, SocketException { Libcore.os.connect(fd, address, port); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/connect.2.html">connect(2)</a>.
+     */
+    public static void connect(FileDescriptor fd, InetAddress address, int port) throws ErrnoException, SocketException { Libcore.os.connect(fd, address, port); }
 
-  /** @hide */ public static void connect(FileDescriptor fd, SocketAddress address) throws ErrnoException, SocketException { Libcore.os.connect(fd, address); }
+    /** @hide */ public static void connect(FileDescriptor fd, SocketAddress address) throws ErrnoException, SocketException { Libcore.os.connect(fd, address); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man2/dup.2.html">dup(2)</a>.
-   */
-  public static FileDescriptor dup(FileDescriptor oldFd) throws ErrnoException { return Libcore.os.dup(oldFd); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/dup.2.html">dup(2)</a>.
+     */
+    public static FileDescriptor dup(FileDescriptor oldFd) throws ErrnoException { return Libcore.os.dup(oldFd); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man2/dup2.2.html">dup2(2)</a>.
-   */
-  public static FileDescriptor dup2(FileDescriptor oldFd, int newFd) throws ErrnoException { return Libcore.os.dup2(oldFd, newFd); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/dup2.2.html">dup2(2)</a>.
+     */
+    public static FileDescriptor dup2(FileDescriptor oldFd, int newFd) throws ErrnoException { return Libcore.os.dup2(oldFd, newFd); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man3/environ.3.html">environ(3)</a>.
-   */
-  public static String[] environ() { return Libcore.os.environ(); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man3/environ.3.html">environ(3)</a>.
+     */
+    public static String[] environ() { return Libcore.os.environ(); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man2/execv.2.html">execv(2)</a>.
-   */
-  public static void execv(String filename, String[] argv) throws ErrnoException { Libcore.os.execv(filename, argv); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/execv.2.html">execv(2)</a>.
+     */
+    public static void execv(String filename, String[] argv) throws ErrnoException { Libcore.os.execv(filename, argv); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man2/execve.2.html">execve(2)</a>.
-   */
-  public static void execve(String filename, String[] argv, String[] envp) throws ErrnoException { Libcore.os.execve(filename, argv, envp); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/execve.2.html">execve(2)</a>.
+     */
+    public static void execve(String filename, String[] argv, String[] envp) throws ErrnoException { Libcore.os.execve(filename, argv, envp); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man2/fchmod.2.html">fchmod(2)</a>.
-   */
-  public static void fchmod(FileDescriptor fd, int mode) throws ErrnoException { Libcore.os.fchmod(fd, mode); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/fchmod.2.html">fchmod(2)</a>.
+     */
+    public static void fchmod(FileDescriptor fd, int mode) throws ErrnoException { Libcore.os.fchmod(fd, mode); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man2/fchown.2.html">fchown(2)</a>.
-   */
-  public static void fchown(FileDescriptor fd, int uid, int gid) throws ErrnoException { Libcore.os.fchown(fd, uid, gid); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/fchown.2.html">fchown(2)</a>.
+     */
+    public static void fchown(FileDescriptor fd, int uid, int gid) throws ErrnoException { Libcore.os.fchown(fd, uid, gid); }
 
-  /** @hide */ public static int fcntlFlock(FileDescriptor fd, int cmd, StructFlock arg) throws ErrnoException, InterruptedIOException { return Libcore.os.fcntlFlock(fd, cmd, arg); }
-  /** @hide */ public static int fcntlInt(FileDescriptor fd, int cmd, int arg) throws ErrnoException { return Libcore.os.fcntlInt(fd, cmd, arg); }
-  /** @hide */ public static int fcntlVoid(FileDescriptor fd, int cmd) throws ErrnoException { return Libcore.os.fcntlVoid(fd, cmd); }
+    /** @hide */ public static int fcntlFlock(FileDescriptor fd, int cmd, StructFlock arg) throws ErrnoException, InterruptedIOException { return Libcore.os.fcntlFlock(fd, cmd, arg); }
+    /** @hide */ public static int fcntlInt(FileDescriptor fd, int cmd, int arg) throws ErrnoException { return Libcore.os.fcntlInt(fd, cmd, arg); }
+    /** @hide */ public static int fcntlVoid(FileDescriptor fd, int cmd) throws ErrnoException { return Libcore.os.fcntlVoid(fd, cmd); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man2/fdatasync.2.html">fdatasync(2)</a>.
-   */
-  public static void fdatasync(FileDescriptor fd) throws ErrnoException { Libcore.os.fdatasync(fd); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/fdatasync.2.html">fdatasync(2)</a>.
+     */
+    public static void fdatasync(FileDescriptor fd) throws ErrnoException { Libcore.os.fdatasync(fd); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man2/fstat.2.html">fstat(2)</a>.
-   */
-  public static StructStat fstat(FileDescriptor fd) throws ErrnoException { return Libcore.os.fstat(fd); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/fstat.2.html">fstat(2)</a>.
+     */
+    public static StructStat fstat(FileDescriptor fd) throws ErrnoException { return Libcore.os.fstat(fd); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man2/fstatvfs.2.html">fstatvfs(2)</a>.
-   */
-  public static StructStatVfs fstatvfs(FileDescriptor fd) throws ErrnoException { return Libcore.os.fstatvfs(fd); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/fstatvfs.2.html">fstatvfs(2)</a>.
+     */
+    public static StructStatVfs fstatvfs(FileDescriptor fd) throws ErrnoException { return Libcore.os.fstatvfs(fd); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man2/fsync.2.html">fsync(2)</a>.
-   */
-  public static void fsync(FileDescriptor fd) throws ErrnoException { Libcore.os.fsync(fd); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/fsync.2.html">fsync(2)</a>.
+     */
+    public static void fsync(FileDescriptor fd) throws ErrnoException { Libcore.os.fsync(fd); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man2/ftruncate.2.html">ftruncate(2)</a>.
-   */
-  public static void ftruncate(FileDescriptor fd, long length) throws ErrnoException { Libcore.os.ftruncate(fd, length); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/ftruncate.2.html">ftruncate(2)</a>.
+     */
+    public static void ftruncate(FileDescriptor fd, long length) throws ErrnoException { Libcore.os.ftruncate(fd, length); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man3/gai_strerror.3.html">gai_strerror(3)</a>.
-   */
-  public static String gai_strerror(int error) { return Libcore.os.gai_strerror(error); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man3/gai_strerror.3.html">gai_strerror(3)</a>.
+     */
+    public static String gai_strerror(int error) { return Libcore.os.gai_strerror(error); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man2/getegid.2.html">getegid(2)</a>.
-   */
-  public static int getegid() { return Libcore.os.getegid(); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/getegid.2.html">getegid(2)</a>.
+     */
+    public static int getegid() { return Libcore.os.getegid(); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man2/geteuid.2.html">geteuid(2)</a>.
-   */
-  public static int geteuid() { return Libcore.os.geteuid(); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/geteuid.2.html">geteuid(2)</a>.
+     */
+    public static int geteuid() { return Libcore.os.geteuid(); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man2/getgid.2.html">getgid(2)</a>.
-   */
-  public static int getgid() { return Libcore.os.getgid(); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/getgid.2.html">getgid(2)</a>.
+     */
+    public static int getgid() { return Libcore.os.getgid(); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man3/getenv.3.html">getenv(3)</a>.
-   */
-  public static String getenv(String name) { return Libcore.os.getenv(name); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man3/getenv.3.html">getenv(3)</a>.
+     */
+    public static String getenv(String name) { return Libcore.os.getenv(name); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man3/getifaddrs.3.html">getifaddrs(3)</a>.
-   */
-  /** @hide */ public static StructIfaddrs[] getifaddrs() throws ErrnoException { return Libcore.os.getifaddrs(); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man3/getifaddrs.3.html">getifaddrs(3)</a>.
+     */
+    /** @hide */ public static StructIfaddrs[] getifaddrs() throws ErrnoException { return Libcore.os.getifaddrs(); }
 
-  /** @hide */ public static String getnameinfo(InetAddress address, int flags) throws GaiException { return Libcore.os.getnameinfo(address, flags); }
+    /** @hide */ public static String getnameinfo(InetAddress address, int flags) throws GaiException { return Libcore.os.getnameinfo(address, flags); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man2/getpeername.2.html">getpeername(2)</a>.
-   */
-  public static SocketAddress getpeername(FileDescriptor fd) throws ErrnoException { return Libcore.os.getpeername(fd); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/getpeername.2.html">getpeername(2)</a>.
+     */
+    public static SocketAddress getpeername(FileDescriptor fd) throws ErrnoException { return Libcore.os.getpeername(fd); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man2/getpgid.2.html">getpgid(2)</a>.
-   */
-  /** @hide */ public static int getpgid(int pid) throws ErrnoException { return Libcore.os.getpgid(pid); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/getpgid.2.html">getpgid(2)</a>.
+     */
+    /** @hide */ public static int getpgid(int pid) throws ErrnoException { return Libcore.os.getpgid(pid); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man2/getpid.2.html">getpid(2)</a>.
-   */
-  public static int getpid() { return Libcore.os.getpid(); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/getpid.2.html">getpid(2)</a>.
+     */
+    public static int getpid() { return Libcore.os.getpid(); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man2/getppid.2.html">getppid(2)</a>.
-   */
-  public static int getppid() { return Libcore.os.getppid(); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/getppid.2.html">getppid(2)</a>.
+     */
+    public static int getppid() { return Libcore.os.getppid(); }
 
-  /** @hide */ public static StructPasswd getpwnam(String name) throws ErrnoException { return Libcore.os.getpwnam(name); }
+    /** @hide */ public static StructPasswd getpwnam(String name) throws ErrnoException { return Libcore.os.getpwnam(name); }
 
-  /** @hide */ public static StructPasswd getpwuid(int uid) throws ErrnoException { return Libcore.os.getpwuid(uid); }
+    /** @hide */ public static StructPasswd getpwuid(int uid) throws ErrnoException { return Libcore.os.getpwuid(uid); }
 
-  /** @hide */ public static StructRlimit getrlimit(int resource) throws ErrnoException { return Libcore.os.getrlimit(resource); }
+    /** @hide */ public static StructRlimit getrlimit(int resource) throws ErrnoException { return Libcore.os.getrlimit(resource); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man2/getsockname.2.html">getsockname(2)</a>.
-   */
-  public static SocketAddress getsockname(FileDescriptor fd) throws ErrnoException { return Libcore.os.getsockname(fd); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/getsockname.2.html">getsockname(2)</a>.
+     */
+    public static SocketAddress getsockname(FileDescriptor fd) throws ErrnoException { return Libcore.os.getsockname(fd); }
 
-  /** @hide */ public static int getsockoptByte(FileDescriptor fd, int level, int option) throws ErrnoException { return Libcore.os.getsockoptByte(fd, level, option); }
-  /** @hide */ public static InetAddress getsockoptInAddr(FileDescriptor fd, int level, int option) throws ErrnoException { return Libcore.os.getsockoptInAddr(fd, level, option); }
-  /** @hide */ public static int getsockoptInt(FileDescriptor fd, int level, int option) throws ErrnoException { return Libcore.os.getsockoptInt(fd, level, option); }
-  /** @hide */ public static StructLinger getsockoptLinger(FileDescriptor fd, int level, int option) throws ErrnoException { return Libcore.os.getsockoptLinger(fd, level, option); }
-  /** @hide */ public static StructTimeval getsockoptTimeval(FileDescriptor fd, int level, int option) throws ErrnoException { return Libcore.os.getsockoptTimeval(fd, level, option); }
-  /** @hide */ public static StructUcred getsockoptUcred(FileDescriptor fd, int level, int option) throws ErrnoException { return Libcore.os.getsockoptUcred(fd, level, option); }
+    /** @hide */ public static int getsockoptByte(FileDescriptor fd, int level, int option) throws ErrnoException { return Libcore.os.getsockoptByte(fd, level, option); }
+    /** @hide */ public static InetAddress getsockoptInAddr(FileDescriptor fd, int level, int option) throws ErrnoException { return Libcore.os.getsockoptInAddr(fd, level, option); }
+    /** @hide */ public static int getsockoptInt(FileDescriptor fd, int level, int option) throws ErrnoException { return Libcore.os.getsockoptInt(fd, level, option); }
+    /** @hide */ public static StructLinger getsockoptLinger(FileDescriptor fd, int level, int option) throws ErrnoException { return Libcore.os.getsockoptLinger(fd, level, option); }
+    /** @hide */ public static StructTimeval getsockoptTimeval(FileDescriptor fd, int level, int option) throws ErrnoException { return Libcore.os.getsockoptTimeval(fd, level, option); }
+    /** @hide */ public static StructUcred getsockoptUcred(FileDescriptor fd, int level, int option) throws ErrnoException { return Libcore.os.getsockoptUcred(fd, level, option); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man2/gettid.2.html">gettid(2)</a>.
-   */
-  public static int gettid() { return Libcore.os.gettid(); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/gettid.2.html">gettid(2)</a>.
+     */
+    public static int gettid() { return Libcore.os.gettid(); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man2/getuid.2.html">getuid(2)</a>.
-   */
-  public static int getuid() { return Libcore.os.getuid(); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/getuid.2.html">getuid(2)</a>.
+     */
+    public static int getuid() { return Libcore.os.getuid(); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man2/getxattr.2.html">getxattr(2)</a>
-   */
-  public static byte[] getxattr(String path, String name) throws ErrnoException { return Libcore.os.getxattr(path, name); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/getxattr.2.html">getxattr(2)</a>
+     */
+    public static byte[] getxattr(String path, String name) throws ErrnoException { return Libcore.os.getxattr(path, name); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man3/if_indextoname.3.html">if_indextoname(3)</a>.
-   */
-  public static String if_indextoname(int index) { return Libcore.os.if_indextoname(index); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man3/if_indextoname.3.html">if_indextoname(3)</a>.
+     */
+    public static String if_indextoname(int index) { return Libcore.os.if_indextoname(index); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man3/if_nametoindex.3.html">if_nametoindex(3)</a>.
-   */
-  public static int if_nametoindex(String name) { return Libcore.os.if_nametoindex(name); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man3/if_nametoindex.3.html">if_nametoindex(3)</a>.
+     */
+    public static int if_nametoindex(String name) { return Libcore.os.if_nametoindex(name); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man3/inet_pton.3.html">inet_pton(3)</a>.
-   */
-  public static InetAddress inet_pton(int family, String address) { return Libcore.os.inet_pton(family, address); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man3/inet_pton.3.html">inet_pton(3)</a>.
+     */
+    public static InetAddress inet_pton(int family, String address) { return Libcore.os.inet_pton(family, address); }
 
-  /** @hide */ public static InetAddress ioctlInetAddress(FileDescriptor fd, int cmd, String interfaceName) throws ErrnoException { return Libcore.os.ioctlInetAddress(fd, cmd, interfaceName); }
-  /** @hide */ public static int ioctlInt(FileDescriptor fd, int cmd, MutableInt arg) throws ErrnoException { return Libcore.os.ioctlInt(fd, cmd, arg); }
+    /** @hide */ public static InetAddress ioctlInetAddress(FileDescriptor fd, int cmd, String interfaceName) throws ErrnoException { return Libcore.os.ioctlInetAddress(fd, cmd, interfaceName); }
+    /** @hide */ public static int ioctlInt(FileDescriptor fd, int cmd, MutableInt arg) throws ErrnoException { return Libcore.os.ioctlInt(fd, cmd, arg); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man3/isatty.3.html">isatty(3)</a>.
-   */
-  public static boolean isatty(FileDescriptor fd) { return Libcore.os.isatty(fd); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man3/isatty.3.html">isatty(3)</a>.
+     */
+    public static boolean isatty(FileDescriptor fd) { return Libcore.os.isatty(fd); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man2/kill.2.html">kill(2)</a>.
-   */
-  public static void kill(int pid, int signal) throws ErrnoException { Libcore.os.kill(pid, signal); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/kill.2.html">kill(2)</a>.
+     */
+    public static void kill(int pid, int signal) throws ErrnoException { Libcore.os.kill(pid, signal); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man2/lchown.2.html">lchown(2)</a>.
-   */
-  public static void lchown(String path, int uid, int gid) throws ErrnoException { Libcore.os.lchown(path, uid, gid); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/lchown.2.html">lchown(2)</a>.
+     */
+    public static void lchown(String path, int uid, int gid) throws ErrnoException { Libcore.os.lchown(path, uid, gid); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man2/link.2.html">link(2)</a>.
-   */
-  public static void link(String oldPath, String newPath) throws ErrnoException { Libcore.os.link(oldPath, newPath); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/link.2.html">link(2)</a>.
+     */
+    public static void link(String oldPath, String newPath) throws ErrnoException { Libcore.os.link(oldPath, newPath); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man2/listen.2.html">listen(2)</a>.
-   */
-  public static void listen(FileDescriptor fd, int backlog) throws ErrnoException { Libcore.os.listen(fd, backlog); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/listen.2.html">listen(2)</a>.
+     */
+    public static void listen(FileDescriptor fd, int backlog) throws ErrnoException { Libcore.os.listen(fd, backlog); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man2/listxattr.2.html">listxattr(2)</a>
-   */
-  public static String[] listxattr(String path) throws ErrnoException { return Libcore.os.listxattr(path); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/listxattr.2.html">listxattr(2)</a>
+     */
+    public static String[] listxattr(String path) throws ErrnoException { return Libcore.os.listxattr(path); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man2/lseek.2.html">lseek(2)</a>.
-   */
-  public static long lseek(FileDescriptor fd, long offset, int whence) throws ErrnoException { return Libcore.os.lseek(fd, offset, whence); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/lseek.2.html">lseek(2)</a>.
+     */
+    public static long lseek(FileDescriptor fd, long offset, int whence) throws ErrnoException { return Libcore.os.lseek(fd, offset, whence); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man2/lstat.2.html">lstat(2)</a>.
-   */
-  public static StructStat lstat(String path) throws ErrnoException { return Libcore.os.lstat(path); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/lstat.2.html">lstat(2)</a>.
+     */
+    public static StructStat lstat(String path) throws ErrnoException { return Libcore.os.lstat(path); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man2/mincore.2.html">mincore(2)</a>.
-   */
-  public static void mincore(long address, long byteCount, byte[] vector) throws ErrnoException { Libcore.os.mincore(address, byteCount, vector); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/mincore.2.html">mincore(2)</a>.
+     */
+    public static void mincore(long address, long byteCount, byte[] vector) throws ErrnoException { Libcore.os.mincore(address, byteCount, vector); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man2/mkdir.2.html">mkdir(2)</a>.
-   */
-  public static void mkdir(String path, int mode) throws ErrnoException { Libcore.os.mkdir(path, mode); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/mkdir.2.html">mkdir(2)</a>.
+     */
+    public static void mkdir(String path, int mode) throws ErrnoException { Libcore.os.mkdir(path, mode); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man3/mkfifo.3.html">mkfifo(3)</a>.
-   */
-  public static void mkfifo(String path, int mode) throws ErrnoException { Libcore.os.mkfifo(path, mode); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man3/mkfifo.3.html">mkfifo(3)</a>.
+     */
+    public static void mkfifo(String path, int mode) throws ErrnoException { Libcore.os.mkfifo(path, mode); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man2/mlock.2.html">mlock(2)</a>.
-   */
-  public static void mlock(long address, long byteCount) throws ErrnoException { Libcore.os.mlock(address, byteCount); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/mlock.2.html">mlock(2)</a>.
+     */
+    public static void mlock(long address, long byteCount) throws ErrnoException { Libcore.os.mlock(address, byteCount); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man2/mmap.2.html">mmap(2)</a>.
-   */
-  public static long mmap(long address, long byteCount, int prot, int flags, FileDescriptor fd, long offset) throws ErrnoException { return Libcore.os.mmap(address, byteCount, prot, flags, fd, offset); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/mmap.2.html">mmap(2)</a>.
+     */
+    public static long mmap(long address, long byteCount, int prot, int flags, FileDescriptor fd, long offset) throws ErrnoException { return Libcore.os.mmap(address, byteCount, prot, flags, fd, offset); }
+
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/msync.2.html">msync(2)</a>.
+     */
+    public static void msync(long address, long byteCount, int flags) throws ErrnoException { Libcore.os.msync(address, byteCount, flags); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man2/msync.2.html">msync(2)</a>.
-   */
-  public static void msync(long address, long byteCount, int flags) throws ErrnoException { Libcore.os.msync(address, byteCount, flags); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/munlock.2.html">munlock(2)</a>.
+     */
+    public static void munlock(long address, long byteCount) throws ErrnoException { Libcore.os.munlock(address, byteCount); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man2/munlock.2.html">munlock(2)</a>.
-   */
-  public static void munlock(long address, long byteCount) throws ErrnoException { Libcore.os.munlock(address, byteCount); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/munmap.2.html">munmap(2)</a>.
+     */
+    public static void munmap(long address, long byteCount) throws ErrnoException { Libcore.os.munmap(address, byteCount); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man2/munmap.2.html">munmap(2)</a>.
-   */
-  public static void munmap(long address, long byteCount) throws ErrnoException { Libcore.os.munmap(address, byteCount); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/open.2.html">open(2)</a>.
+     */
+    public static FileDescriptor open(String path, int flags, int mode) throws ErrnoException { return Libcore.os.open(path, flags, mode); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man2/open.2.html">open(2)</a>.
-   */
-  public static FileDescriptor open(String path, int flags, int mode) throws ErrnoException { return Libcore.os.open(path, flags, mode); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/pipe.2.html">pipe(2)</a>.
+     */
+    public static FileDescriptor[] pipe() throws ErrnoException { return Libcore.os.pipe2(0); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man2/pipe.2.html">pipe(2)</a>.
-   */
-  public static FileDescriptor[] pipe() throws ErrnoException { return Libcore.os.pipe2(0); }
+    /** @hide */ public static FileDescriptor[] pipe2(int flags) throws ErrnoException { return Libcore.os.pipe2(flags); }
 
-  /** @hide */ public static FileDescriptor[] pipe2(int flags) throws ErrnoException { return Libcore.os.pipe2(flags); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/poll.2.html">poll(2)</a>.
+     *
+     * <p>Note that in Lollipop this could throw an {@code ErrnoException} with {@code EINTR}.
+     * In later releases, the implementation will automatically just restart the system call with
+     * an appropriately reduced timeout.
+     */
+    public static int poll(StructPollfd[] fds, int timeoutMs) throws ErrnoException { return Libcore.os.poll(fds, timeoutMs); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man2/poll.2.html">poll(2)</a>.
-   *
-   * <p>Note that in Lollipop this could throw an {@code ErrnoException} with {@code EINTR}.
-   * In later releases, the implementation will automatically just restart the system call with
-   * an appropriately reduced timeout.
-   */
-  public static int poll(StructPollfd[] fds, int timeoutMs) throws ErrnoException { return Libcore.os.poll(fds, timeoutMs); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man3/posix_fallocate.3.html">posix_fallocate(3)</a>.
+     */
+    public static void posix_fallocate(FileDescriptor fd, long offset, long length) throws ErrnoException { Libcore.os.posix_fallocate(fd, offset, length); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man3/posix_fallocate.3.html">posix_fallocate(3)</a>.
-   */
-  public static void posix_fallocate(FileDescriptor fd, long offset, long length) throws ErrnoException { Libcore.os.posix_fallocate(fd, offset, length); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/prctl.2.html">prctl(2)</a>.
+     */
+    public static int prctl(int option, long arg2, long arg3, long arg4, long arg5) throws ErrnoException { return Libcore.os.prctl(option, arg2, arg3, arg4, arg5); };
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man2/prctl.2.html">prctl(2)</a>.
-   */
-  public static int prctl(int option, long arg2, long arg3, long arg4, long arg5) throws ErrnoException { return Libcore.os.prctl(option, arg2, arg3, arg4, arg5); };
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/pread.2.html">pread(2)</a>.
+     */
+    public static int pread(FileDescriptor fd, ByteBuffer buffer, long offset) throws ErrnoException, InterruptedIOException { return Libcore.os.pread(fd, buffer, offset); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man2/pread.2.html">pread(2)</a>.
-   */
-  public static int pread(FileDescriptor fd, ByteBuffer buffer, long offset) throws ErrnoException, InterruptedIOException { return Libcore.os.pread(fd, buffer, offset); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/pread.2.html">pread(2)</a>.
+     */
+    public static int pread(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, long offset) throws ErrnoException, InterruptedIOException { return Libcore.os.pread(fd, bytes, byteOffset, byteCount, offset); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man2/pread.2.html">pread(2)</a>.
-   */
-  public static int pread(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, long offset) throws ErrnoException, InterruptedIOException { return Libcore.os.pread(fd, bytes, byteOffset, byteCount, offset); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/pwrite.2.html">pwrite(2)</a>.
+     */
+    public static int pwrite(FileDescriptor fd, ByteBuffer buffer, long offset) throws ErrnoException, InterruptedIOException { return Libcore.os.pwrite(fd, buffer, offset); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man2/pwrite.2.html">pwrite(2)</a>.
-   */
-  public static int pwrite(FileDescriptor fd, ByteBuffer buffer, long offset) throws ErrnoException, InterruptedIOException { return Libcore.os.pwrite(fd, buffer, offset); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/pwrite.2.html">pwrite(2)</a>.
+     */
+    public static int pwrite(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, long offset) throws ErrnoException, InterruptedIOException { return Libcore.os.pwrite(fd, bytes, byteOffset, byteCount, offset); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man2/pwrite.2.html">pwrite(2)</a>.
-   */
-  public static int pwrite(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, long offset) throws ErrnoException, InterruptedIOException { return Libcore.os.pwrite(fd, bytes, byteOffset, byteCount, offset); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/read.2.html">read(2)</a>.
+     */
+    public static int read(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException, InterruptedIOException { return Libcore.os.read(fd, buffer); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man2/read.2.html">read(2)</a>.
-   */
-  public static int read(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException, InterruptedIOException { return Libcore.os.read(fd, buffer); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/read.2.html">read(2)</a>.
+     */
+    public static int read(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException, InterruptedIOException { return Libcore.os.read(fd, bytes, byteOffset, byteCount); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man2/read.2.html">read(2)</a>.
-   */
-  public static int read(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException, InterruptedIOException { return Libcore.os.read(fd, bytes, byteOffset, byteCount); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/readlink.2.html">readlink(2)</a>.
+     */
+    public static String readlink(String path) throws ErrnoException { return Libcore.os.readlink(path); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man2/readlink.2.html">readlink(2)</a>.
-   */
-  public static String readlink(String path) throws ErrnoException { return Libcore.os.readlink(path); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man3/realpath.3.html">realpath(3)</a>.
+     */
+    /** @hide */ public static String realpath(String path) throws ErrnoException { return Libcore.os.realpath(path); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man2/readv.2.html">readv(2)</a>.
-   */
-  public static int readv(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException, InterruptedIOException { return Libcore.os.readv(fd, buffers, offsets, byteCounts); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/readv.2.html">readv(2)</a>.
+     */
+    public static int readv(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException, InterruptedIOException { return Libcore.os.readv(fd, buffers, offsets, byteCounts); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man2/recvfrom.2.html">recvfrom(2)</a>.
-   */
-  public static int recvfrom(FileDescriptor fd, ByteBuffer buffer, int flags, InetSocketAddress srcAddress) throws ErrnoException, SocketException { return Libcore.os.recvfrom(fd, buffer, flags, srcAddress); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/recvfrom.2.html">recvfrom(2)</a>.
+     */
+    public static int recvfrom(FileDescriptor fd, ByteBuffer buffer, int flags, InetSocketAddress srcAddress) throws ErrnoException, SocketException { return Libcore.os.recvfrom(fd, buffer, flags, srcAddress); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man2/recvfrom.2.html">recvfrom(2)</a>.
-   */
-  public static int recvfrom(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, int flags, InetSocketAddress srcAddress) throws ErrnoException, SocketException { return Libcore.os.recvfrom(fd, bytes, byteOffset, byteCount, flags, srcAddress); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/recvfrom.2.html">recvfrom(2)</a>.
+     */
+    public static int recvfrom(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, int flags, InetSocketAddress srcAddress) throws ErrnoException, SocketException { return Libcore.os.recvfrom(fd, bytes, byteOffset, byteCount, flags, srcAddress); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man3/remove.3.html">remove(3)</a>.
-   */
-  public static void remove(String path) throws ErrnoException { Libcore.os.remove(path); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man3/remove.3.html">remove(3)</a>.
+     */
+    public static void remove(String path) throws ErrnoException { Libcore.os.remove(path); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man2/removexattr.2.html">removexattr(2)</a>.
-   */
-  public static void removexattr(String path, String name) throws ErrnoException { Libcore.os.removexattr(path, name); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/removexattr.2.html">removexattr(2)</a>.
+     */
+    public static void removexattr(String path, String name) throws ErrnoException { Libcore.os.removexattr(path, name); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man2/rename.2.html">rename(2)</a>.
-   */
-  public static void rename(String oldPath, String newPath) throws ErrnoException { Libcore.os.rename(oldPath, newPath); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/rename.2.html">rename(2)</a>.
+     */
+    public static void rename(String oldPath, String newPath) throws ErrnoException { Libcore.os.rename(oldPath, newPath); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man2/sendfile.2.html">sendfile(2)</a>.
-   */
-  public static long sendfile(FileDescriptor outFd, FileDescriptor inFd, MutableLong inOffset, long byteCount) throws ErrnoException { return Libcore.os.sendfile(outFd, inFd, inOffset, byteCount); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/sendfile.2.html">sendfile(2)</a>.
+     */
+    public static long sendfile(FileDescriptor outFd, FileDescriptor inFd, MutableLong inOffset, long byteCount) throws ErrnoException { return Libcore.os.sendfile(outFd, inFd, inOffset, byteCount); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man2/sendto.2.html">sendto(2)</a>.
-   */
-  public static int sendto(FileDescriptor fd, ByteBuffer buffer, int flags, InetAddress inetAddress, int port) throws ErrnoException, SocketException { return Libcore.os.sendto(fd, buffer, flags, inetAddress, port); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/sendto.2.html">sendto(2)</a>.
+     */
+    public static int sendto(FileDescriptor fd, ByteBuffer buffer, int flags, InetAddress inetAddress, int port) throws ErrnoException, SocketException { return Libcore.os.sendto(fd, buffer, flags, inetAddress, port); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man2/sendto.2.html">sendto(2)</a>.
-   */
-  public static int sendto(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, int flags, InetAddress inetAddress, int port) throws ErrnoException, SocketException { return Libcore.os.sendto(fd, bytes, byteOffset, byteCount, flags, inetAddress, port); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/sendto.2.html">sendto(2)</a>.
+     */
+    public static int sendto(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, int flags, InetAddress inetAddress, int port) throws ErrnoException, SocketException { return Libcore.os.sendto(fd, bytes, byteOffset, byteCount, flags, inetAddress, port); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man2/sendto.2.html">sendto(2)</a>.
-   */
-  /** @hide */ public static int sendto(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, int flags, SocketAddress address) throws ErrnoException, SocketException { return Libcore.os.sendto(fd, bytes, byteOffset, byteCount, flags, address); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/sendto.2.html">sendto(2)</a>.
+     */
+    /** @hide */ public static int sendto(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, int flags, SocketAddress address) throws ErrnoException, SocketException { return Libcore.os.sendto(fd, bytes, byteOffset, byteCount, flags, address); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man2/setegid.2.html">setegid(2)</a>.
-   */
-  public static void setegid(int egid) throws ErrnoException { Libcore.os.setegid(egid); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/setegid.2.html">setegid(2)</a>.
+     */
+    public static void setegid(int egid) throws ErrnoException { Libcore.os.setegid(egid); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man3/setenv.3.html">setenv(3)</a>.
-   */
-  public static void setenv(String name, String value, boolean overwrite) throws ErrnoException { Libcore.os.setenv(name, value, overwrite); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man3/setenv.3.html">setenv(3)</a>.
+     */
+    public static void setenv(String name, String value, boolean overwrite) throws ErrnoException { Libcore.os.setenv(name, value, overwrite); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man2/seteuid.2.html">seteuid(2)</a>.
-   */
-  public static void seteuid(int euid) throws ErrnoException { Libcore.os.seteuid(euid); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/seteuid.2.html">seteuid(2)</a>.
+     */
+    public static void seteuid(int euid) throws ErrnoException { Libcore.os.seteuid(euid); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man2/setgid.2.html">setgid(2)</a>.
-   */
-  public static void setgid(int gid) throws ErrnoException { Libcore.os.setgid(gid); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/setgid.2.html">setgid(2)</a>.
+     */
+    public static void setgid(int gid) throws ErrnoException { Libcore.os.setgid(gid); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man2/setpgid.2.html">setpgid(2)</a>.
-   */
-  /** @hide */ public static void setpgid(int pid, int pgid) throws ErrnoException { Libcore.os.setpgid(pid, pgid); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/setpgid.2.html">setpgid(2)</a>.
+     */
+    /** @hide */ public static void setpgid(int pid, int pgid) throws ErrnoException { Libcore.os.setpgid(pid, pgid); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man2/setregid.2.html">setregid(2)</a>.
-   */
-  /** @hide */ public static void setregid(int rgid, int egid) throws ErrnoException { Libcore.os.setregid(rgid, egid); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/setregid.2.html">setregid(2)</a>.
+     */
+    /** @hide */ public static void setregid(int rgid, int egid) throws ErrnoException { Libcore.os.setregid(rgid, egid); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man2/setreuid.2.html">setreuid(2)</a>.
-   */
-  /** @hide */ public static void setreuid(int ruid, int euid) throws ErrnoException { Libcore.os.setreuid(ruid, euid); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/setreuid.2.html">setreuid(2)</a>.
+     */
+    /** @hide */ public static void setreuid(int ruid, int euid) throws ErrnoException { Libcore.os.setreuid(ruid, euid); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man2/setsid.2.html">setsid(2)</a>.
-   */
-  public static int setsid() throws ErrnoException { return Libcore.os.setsid(); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/setsid.2.html">setsid(2)</a>.
+     */
+    public static int setsid() throws ErrnoException { return Libcore.os.setsid(); }
 
-  /** @hide */ public static void setsockoptByte(FileDescriptor fd, int level, int option, int value) throws ErrnoException { Libcore.os.setsockoptByte(fd, level, option, value); }
-  /** @hide */ public static void setsockoptIfreq(FileDescriptor fd, int level, int option, String value) throws ErrnoException { Libcore.os.setsockoptIfreq(fd, level, option, value); }
+    /** @hide */ public static void setsockoptByte(FileDescriptor fd, int level, int option, int value) throws ErrnoException { Libcore.os.setsockoptByte(fd, level, option, value); }
+    /** @hide */ public static void setsockoptIfreq(FileDescriptor fd, int level, int option, String value) throws ErrnoException { Libcore.os.setsockoptIfreq(fd, level, option, value); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man2/setsockopt.2.html">setsockopt(2)</a>.
-   */
-  public static void setsockoptInt(FileDescriptor fd, int level, int option, int value) throws ErrnoException { Libcore.os.setsockoptInt(fd, level, option, value); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/setsockopt.2.html">setsockopt(2)</a>.
+     */
+    public static void setsockoptInt(FileDescriptor fd, int level, int option, int value) throws ErrnoException { Libcore.os.setsockoptInt(fd, level, option, value); }
 
-  /** @hide */ public static void setsockoptIpMreqn(FileDescriptor fd, int level, int option, int value) throws ErrnoException { Libcore.os.setsockoptIpMreqn(fd, level, option, value); }
-  /** @hide */ public static void setsockoptGroupReq(FileDescriptor fd, int level, int option, StructGroupReq value) throws ErrnoException { Libcore.os.setsockoptGroupReq(fd, level, option, value); }
-  /** @hide */ public static void setsockoptLinger(FileDescriptor fd, int level, int option, StructLinger value) throws ErrnoException { Libcore.os.setsockoptLinger(fd, level, option, value); }
-  /** @hide */ public static void setsockoptTimeval(FileDescriptor fd, int level, int option, StructTimeval value) throws ErrnoException { Libcore.os.setsockoptTimeval(fd, level, option, value); }
+    /** @hide */ public static void setsockoptIpMreqn(FileDescriptor fd, int level, int option, int value) throws ErrnoException { Libcore.os.setsockoptIpMreqn(fd, level, option, value); }
+    /** @hide */ public static void setsockoptGroupReq(FileDescriptor fd, int level, int option, StructGroupReq value) throws ErrnoException { Libcore.os.setsockoptGroupReq(fd, level, option, value); }
+    /** @hide */ public static void setsockoptLinger(FileDescriptor fd, int level, int option, StructLinger value) throws ErrnoException { Libcore.os.setsockoptLinger(fd, level, option, value); }
+    /** @hide */ public static void setsockoptTimeval(FileDescriptor fd, int level, int option, StructTimeval value) throws ErrnoException { Libcore.os.setsockoptTimeval(fd, level, option, value); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man2/setuid.2.html">setuid(2)</a>.
-   */
-  public static void setuid(int uid) throws ErrnoException { Libcore.os.setuid(uid); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/setuid.2.html">setuid(2)</a>.
+     */
+    public static void setuid(int uid) throws ErrnoException { Libcore.os.setuid(uid); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man2/setxattr.2.html">setxattr(2)</a>
-   */
-  public static void setxattr(String path, String name, byte[] value, int flags) throws ErrnoException { Libcore.os.setxattr(path, name, value, flags); };
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/setxattr.2.html">setxattr(2)</a>
+     */
+    public static void setxattr(String path, String name, byte[] value, int flags) throws ErrnoException { Libcore.os.setxattr(path, name, value, flags); };
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man2/shutdown.2.html">shutdown(2)</a>.
-   */
-  public static void shutdown(FileDescriptor fd, int how) throws ErrnoException { Libcore.os.shutdown(fd, how); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/shutdown.2.html">shutdown(2)</a>.
+     */
+    public static void shutdown(FileDescriptor fd, int how) throws ErrnoException { Libcore.os.shutdown(fd, how); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man2/socket.2.html">socket(2)</a>.
-   */
-  public static FileDescriptor socket(int domain, int type, int protocol) throws ErrnoException { return Libcore.os.socket(domain, type, protocol); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/socket.2.html">socket(2)</a>.
+     */
+    public static FileDescriptor socket(int domain, int type, int protocol) throws ErrnoException { return Libcore.os.socket(domain, type, protocol); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man2/socketpair.2.html">socketpair(2)</a>.
-   */
-  public static void socketpair(int domain, int type, int protocol, FileDescriptor fd1, FileDescriptor fd2) throws ErrnoException { Libcore.os.socketpair(domain, type, protocol, fd1, fd2); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/socketpair.2.html">socketpair(2)</a>.
+     */
+    public static void socketpair(int domain, int type, int protocol, FileDescriptor fd1, FileDescriptor fd2) throws ErrnoException { Libcore.os.socketpair(domain, type, protocol, fd1, fd2); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man2/stat.2.html">stat(2)</a>.
-   */
-  public static StructStat stat(String path) throws ErrnoException { return Libcore.os.stat(path); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/stat.2.html">stat(2)</a>.
+     */
+    public static StructStat stat(String path) throws ErrnoException { return Libcore.os.stat(path); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man2/statvfs.2.html">statvfs(2)</a>.
-   */
-  public static StructStatVfs statvfs(String path) throws ErrnoException { return Libcore.os.statvfs(path); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/statvfs.2.html">statvfs(2)</a>.
+     */
+    public static StructStatVfs statvfs(String path) throws ErrnoException { return Libcore.os.statvfs(path); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man3/strerror.3.html">strerror(2)</a>.
-   */
-  public static String strerror(int errno) { return Libcore.os.strerror(errno); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man3/strerror.3.html">strerror(2)</a>.
+     */
+    public static String strerror(int errno) { return Libcore.os.strerror(errno); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man3/strsignal.3.html">strsignal(3)</a>.
-   */
-  public static String strsignal(int signal) { return Libcore.os.strsignal(signal); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man3/strsignal.3.html">strsignal(3)</a>.
+     */
+    public static String strsignal(int signal) { return Libcore.os.strsignal(signal); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man2/symlink.2.html">symlink(2)</a>.
-   */
-  public static void symlink(String oldPath, String newPath) throws ErrnoException { Libcore.os.symlink(oldPath, newPath); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/symlink.2.html">symlink(2)</a>.
+     */
+    public static void symlink(String oldPath, String newPath) throws ErrnoException { Libcore.os.symlink(oldPath, newPath); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man3/sysconf.3.html">sysconf(3)</a>.
-   */
-  public static long sysconf(int name) { return Libcore.os.sysconf(name); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man3/sysconf.3.html">sysconf(3)</a>.
+     */
+    public static long sysconf(int name) { return Libcore.os.sysconf(name); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man3/tcdrain.3.html">tcdrain(3)</a>.
-   */
-  public static void tcdrain(FileDescriptor fd) throws ErrnoException { Libcore.os.tcdrain(fd); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man3/tcdrain.3.html">tcdrain(3)</a>.
+     */
+    public static void tcdrain(FileDescriptor fd) throws ErrnoException { Libcore.os.tcdrain(fd); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man3/tcsendbreak.3.html">tcsendbreak(3)</a>.
-   */
-  public static void tcsendbreak(FileDescriptor fd, int duration) throws ErrnoException { Libcore.os.tcsendbreak(fd, duration); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man3/tcsendbreak.3.html">tcsendbreak(3)</a>.
+     */
+    public static void tcsendbreak(FileDescriptor fd, int duration) throws ErrnoException { Libcore.os.tcsendbreak(fd, duration); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man2/umask.2.html">umask(2)</a>.
-   */
-  public static int umask(int mask) { return Libcore.os.umask(mask); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/umask.2.html">umask(2)</a>.
+     */
+    public static int umask(int mask) { return Libcore.os.umask(mask); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man2/uname.2.html">uname(2)</a>.
-   */
-  public static StructUtsname uname() { return Libcore.os.uname(); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/uname.2.html">uname(2)</a>.
+     */
+    public static StructUtsname uname() { return Libcore.os.uname(); }
 
-  /**
-   * @hide See <a href="http://man7.org/linux/man-pages/man2/unlink.2.html">unlink(2)</a>.
-   */
-  public static void unlink(String pathname) throws ErrnoException { Libcore.os.unlink(pathname); }
+    /**
+     * @hide See <a href="http://man7.org/linux/man-pages/man2/unlink.2.html">unlink(2)</a>.
+     */
+    public static void unlink(String pathname) throws ErrnoException { Libcore.os.unlink(pathname); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man3/unsetenv.3.html">unsetenv(3)</a>.
-   */
-  public static void unsetenv(String name) throws ErrnoException { Libcore.os.unsetenv(name); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man3/unsetenv.3.html">unsetenv(3)</a>.
+     */
+    public static void unsetenv(String name) throws ErrnoException { Libcore.os.unsetenv(name); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man2/waitpid.2.html">waitpid(2)</a>.
-   */
-  public static int waitpid(int pid, MutableInt status, int options) throws ErrnoException { return Libcore.os.waitpid(pid, status, options); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/waitpid.2.html">waitpid(2)</a>.
+     */
+    public static int waitpid(int pid, MutableInt status, int options) throws ErrnoException { return Libcore.os.waitpid(pid, status, options); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man2/write.2.html">write(2)</a>.
-   */
-  public static int write(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException, InterruptedIOException { return Libcore.os.write(fd, buffer); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/write.2.html">write(2)</a>.
+     */
+    public static int write(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException, InterruptedIOException { return Libcore.os.write(fd, buffer); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man2/write.2.html">write(2)</a>.
-   */
-  public static int write(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException, InterruptedIOException { return Libcore.os.write(fd, bytes, byteOffset, byteCount); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/write.2.html">write(2)</a>.
+     */
+    public static int write(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException, InterruptedIOException { return Libcore.os.write(fd, bytes, byteOffset, byteCount); }
 
-  /**
-   * See <a href="http://man7.org/linux/man-pages/man2/writev.2.html">writev(2)</a>.
-   */
-  public static int writev(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException, InterruptedIOException { return Libcore.os.writev(fd, buffers, offsets, byteCounts); }
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/writev.2.html">writev(2)</a>.
+     */
+    public static int writev(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException, InterruptedIOException { return Libcore.os.writev(fd, buffers, offsets, byteCounts); }
 }
diff --git a/android/system/StructAddrinfo.java b/android/system/StructAddrinfo.java
index 2425946..dab7590 100644
--- a/android/system/StructAddrinfo.java
+++ b/android/system/StructAddrinfo.java
@@ -28,31 +28,31 @@
  * @hide
  */
 public final class StructAddrinfo {
-  /** Flags describing the kind of lookup to be done. (Such as AI_ADDRCONFIG.) */
-  public int ai_flags;
+    /** Flags describing the kind of lookup to be done. (Such as AI_ADDRCONFIG.) */
+    public int ai_flags;
 
-  /** Desired address family for results. (Such as AF_INET6 for IPv6. AF_UNSPEC means "any".) */
-  public int ai_family;
+    /** Desired address family for results. (Such as AF_INET6 for IPv6. AF_UNSPEC means "any".) */
+    public int ai_family;
 
-  /** Socket type. (Such as SOCK_DGRAM. 0 means "any".) */
-  public int ai_socktype;
+    /** Socket type. (Such as SOCK_DGRAM. 0 means "any".) */
+    public int ai_socktype;
 
-  /** Protocol. (Such as IPPROTO_IPV6 IPv6. 0 means "any".) */
-  public int ai_protocol;
+    /** Protocol. (Such as IPPROTO_IPV6 IPv6. 0 means "any".) */
+    public int ai_protocol;
 
-  /** Address length. (Not useful in Java.) */
-  // public int ai_addrlen;
+    /** Address length. (Not useful in Java.) */
+    // public int ai_addrlen;
 
-  /** Address. */
-  public InetAddress ai_addr;
+    /** Address. */
+    public InetAddress ai_addr;
 
-  /** Canonical name of service location (if AI_CANONNAME provided in ai_flags). */
-  // public String ai_canonname;
+    /** Canonical name of service location (if AI_CANONNAME provided in ai_flags). */
+    // public String ai_canonname;
 
-  /** Next element in linked list. */
-  public StructAddrinfo ai_next;
+    /** Next element in linked list. */
+    public StructAddrinfo ai_next;
 
-  @Override public String toString() {
-    return Objects.toString(this);
-  }
+    @Override public String toString() {
+        return Objects.toString(this);
+    }
 }
diff --git a/android/system/StructCapUserData.java b/android/system/StructCapUserData.java
index af63caf..331e2ea 100644
--- a/android/system/StructCapUserData.java
+++ b/android/system/StructCapUserData.java
@@ -24,25 +24,25 @@
  * @hide
  */
 public final class StructCapUserData {
-  /** Effective capability mask. */
-  public final int effective; /* __u32 */
+    /** Effective capability mask. */
+    public final int effective; /* __u32 */
 
-  /** Permitted capability mask. */
-  public final int permitted; /* __u32 */
+    /** Permitted capability mask. */
+    public final int permitted; /* __u32 */
 
-  /** Inheritable capability mask. */
-  public final int inheritable; /* __u32 */
+    /** Inheritable capability mask. */
+    public final int inheritable; /* __u32 */
 
-  /**
-   * Constructs an instance with the given field values.
-   */
-  public StructCapUserData(int effective, int permitted, int inheritable) {
-    this.effective = effective;
-    this.permitted = permitted;
-    this.inheritable = inheritable;
-  }
+    /**
+     * Constructs an instance with the given field values.
+     */
+    public StructCapUserData(int effective, int permitted, int inheritable) {
+        this.effective = effective;
+        this.permitted = permitted;
+        this.inheritable = inheritable;
+    }
 
-  @Override public String toString() {
-    return Objects.toString(this);
-  }
+    @Override public String toString() {
+        return Objects.toString(this);
+    }
 }
diff --git a/android/system/StructCapUserHeader.java b/android/system/StructCapUserHeader.java
index abbb395..fb03aa8 100644
--- a/android/system/StructCapUserHeader.java
+++ b/android/system/StructCapUserHeader.java
@@ -24,25 +24,25 @@
  * @hide
  */
 public final class StructCapUserHeader {
-  /**
-   * Version of the header. Note this is not final as capget() may mutate the field when an
-   * invalid version is provided. See
-   * <a href="http://man7.org/linux/man-pages/man2/capget.2.html">capget(2)</a>.
-   */
-  public int version; /* __u32 */
+    /**
+     * Version of the header. Note this is not final as capget() may mutate the field when an
+     * invalid version is provided. See
+     * <a href="http://man7.org/linux/man-pages/man2/capget.2.html">capget(2)</a>.
+     */
+    public int version; /* __u32 */
 
-  /** Pid of the header. The pid a call applies to. */
-  public final int pid;
+    /** Pid of the header. The pid a call applies to. */
+    public final int pid;
 
-  /**
-   * Constructs an instance with the given field values.
-   */
-  public StructCapUserHeader(int version, int pid) {
-    this.version = version;
-    this.pid = pid;
-  }
+    /**
+     * Constructs an instance with the given field values.
+     */
+    public StructCapUserHeader(int version, int pid) {
+        this.version = version;
+        this.pid = pid;
+    }
 
-  @Override public String toString() {
-    return Objects.toString(this);
-  }
+    @Override public String toString() {
+        return Objects.toString(this);
+    }
 }
diff --git a/android/system/StructFlock.java b/android/system/StructFlock.java
index 92cd95a..0d93425 100644
--- a/android/system/StructFlock.java
+++ b/android/system/StructFlock.java
@@ -26,22 +26,22 @@
  * @hide
  */
 public final class StructFlock {
-  /** The operation type, one of F_RDLCK, F_WRLCK, or F_UNLCK. */
-  public short l_type;
+    /** The operation type, one of F_RDLCK, F_WRLCK, or F_UNLCK. */
+    public short l_type;
 
-  /** How to interpret l_start, one of SEEK_CUR, SEEK_END, SEEK_SET. */
-  public short l_whence;
+    /** How to interpret l_start, one of SEEK_CUR, SEEK_END, SEEK_SET. */
+    public short l_whence;
 
-  /** Start offset. */
-  public long l_start; /*off_t*/
+    /** Start offset. */
+    public long l_start; /*off_t*/
 
-  /** Byte count to operate on. */
-  public long l_len; /*off_t*/
+    /** Byte count to operate on. */
+    public long l_len; /*off_t*/
 
-  /** Process blocking our lock (filled in by F_GETLK, otherwise unused). */
-  public int l_pid; /*pid_t*/
+    /** Process blocking our lock (filled in by F_GETLK, otherwise unused). */
+    public int l_pid; /*pid_t*/
 
-  @Override public String toString() {
-    return Objects.toString(this);
-  }
+    @Override public String toString() {
+        return Objects.toString(this);
+    }
 }
diff --git a/android/system/StructGroupReq.java b/android/system/StructGroupReq.java
index 8ed5950..78db5f5 100644
--- a/android/system/StructGroupReq.java
+++ b/android/system/StructGroupReq.java
@@ -25,15 +25,15 @@
  * @hide
  */
 public final class StructGroupReq {
-  public final int gr_interface;
-  public final InetAddress gr_group;
+    public final int gr_interface;
+    public final InetAddress gr_group;
 
-  public StructGroupReq(int gr_interface, InetAddress gr_group) {
-    this.gr_interface = gr_interface;
-    this.gr_group = gr_group;
-  }
+    public StructGroupReq(int gr_interface, InetAddress gr_group) {
+        this.gr_interface = gr_interface;
+        this.gr_group = gr_group;
+    }
 
-  @Override public String toString() {
-    return Objects.toString(this);
-  }
+    @Override public String toString() {
+        return Objects.toString(this);
+    }
 }
diff --git a/android/system/StructLinger.java b/android/system/StructLinger.java
index 55ffc5c..46d4132 100644
--- a/android/system/StructLinger.java
+++ b/android/system/StructLinger.java
@@ -25,22 +25,22 @@
  * @hide
  */
 public final class StructLinger {
-  /** Whether or not linger is enabled. Non-zero is on. */
-  public final int l_onoff;
+    /** Whether or not linger is enabled. Non-zero is on. */
+    public final int l_onoff;
 
-  /** Linger time in seconds. */
-  public final int l_linger;
+    /** Linger time in seconds. */
+    public final int l_linger;
 
-  public StructLinger(int l_onoff, int l_linger) {
-    this.l_onoff = l_onoff;
-    this.l_linger = l_linger;
-  }
+    public StructLinger(int l_onoff, int l_linger) {
+        this.l_onoff = l_onoff;
+        this.l_linger = l_linger;
+    }
 
-  public boolean isOn() {
-    return l_onoff != 0;
-  }
+    public boolean isOn() {
+        return l_onoff != 0;
+    }
 
-  @Override public String toString() {
-    return Objects.toString(this);
-  }
+    @Override public String toString() {
+        return Objects.toString(this);
+    }
 }
diff --git a/android/system/StructPasswd.java b/android/system/StructPasswd.java
index 93655c1..633a607 100644
--- a/android/system/StructPasswd.java
+++ b/android/system/StructPasswd.java
@@ -25,24 +25,24 @@
  * @hide
  */
 public final class StructPasswd {
-  public final String pw_name;
-  public final int pw_uid;
-  public final int pw_gid;
-  public final String pw_dir;
-  public final String pw_shell;
+    public final String pw_name;
+    public final int pw_uid;
+    public final int pw_gid;
+    public final String pw_dir;
+    public final String pw_shell;
 
-  /**
-   * Constructs an instance with the given field values.
-   */
-  public StructPasswd(String pw_name, int pw_uid, int pw_gid, String pw_dir, String pw_shell) {
-    this.pw_name = pw_name;
-    this.pw_uid = pw_uid;
-    this.pw_gid = pw_gid;
-    this.pw_dir = pw_dir;
-    this.pw_shell = pw_shell;
-  }
+    /**
+     * Constructs an instance with the given field values.
+     */
+    public StructPasswd(String pw_name, int pw_uid, int pw_gid, String pw_dir, String pw_shell) {
+        this.pw_name = pw_name;
+        this.pw_uid = pw_uid;
+        this.pw_gid = pw_gid;
+        this.pw_dir = pw_dir;
+        this.pw_shell = pw_shell;
+    }
 
-  @Override public String toString() {
-    return Objects.toString(this);
-  }
+    @Override public String toString() {
+        return Objects.toString(this);
+    }
 }
diff --git a/android/system/StructPollfd.java b/android/system/StructPollfd.java
index 2ca117e..2f40b8b 100644
--- a/android/system/StructPollfd.java
+++ b/android/system/StructPollfd.java
@@ -24,26 +24,26 @@
  * Corresponds to C's {@code struct pollfd} from {@code <poll.h>}.
  */
 public final class StructPollfd {
-  /** The file descriptor to poll. */
-  public FileDescriptor fd;
+    /** The file descriptor to poll. */
+    public FileDescriptor fd;
 
-  /**
-   * The events we're interested in. POLLIN corresponds to being in select(2)'s read fd set,
-   * POLLOUT to the write fd set.
-   */
-  public short events;
+    /**
+     * The events we're interested in. POLLIN corresponds to being in select(2)'s read fd set,
+     * POLLOUT to the write fd set.
+     */
+    public short events;
 
-  /** The events that actually happened. */
-  public short revents;
+    /** The events that actually happened. */
+    public short revents;
 
-  /**
-   * A non-standard extension that lets callers conveniently map back to the object
-   * their fd belongs to. This is used by Selector, for example, to associate each
-   * FileDescriptor with the corresponding SelectionKey.
-   */
-  public Object userData;
+    /**
+     * A non-standard extension that lets callers conveniently map back to the object
+     * their fd belongs to. This is used by Selector, for example, to associate each
+     * FileDescriptor with the corresponding SelectionKey.
+     */
+    public Object userData;
 
-  @Override public String toString() {
-    return Objects.toString(this);
-  }
+    @Override public String toString() {
+        return Objects.toString(this);
+    }
 }
diff --git a/android/system/StructRlimit.java b/android/system/StructRlimit.java
index 6bb05a9..007757f 100644
--- a/android/system/StructRlimit.java
+++ b/android/system/StructRlimit.java
@@ -25,15 +25,15 @@
  * @hide
  */
 public final class StructRlimit {
-  public final long rlim_cur;
-  public final long rlim_max;
+    public final long rlim_cur;
+    public final long rlim_max;
 
-  public StructRlimit(long rlim_cur, long rlim_max) {
-    this.rlim_cur = rlim_cur;
-    this.rlim_max = rlim_max;
-  }
+    public StructRlimit(long rlim_cur, long rlim_max) {
+        this.rlim_cur = rlim_cur;
+        this.rlim_max = rlim_max;
+    }
 
-  @Override public String toString() {
-    return Objects.toString(this);
-  }
+    @Override public String toString() {
+        return Objects.toString(this);
+    }
 }
diff --git a/android/system/StructStat.java b/android/system/StructStat.java
index a1e8729..a8b1fca 100644
--- a/android/system/StructStat.java
+++ b/android/system/StructStat.java
@@ -23,99 +23,99 @@
  * Corresponds to C's {@code struct stat} from {@code <stat.h>}.
  */
 public final class StructStat {
-  /** Device ID of device containing file. */
-  public final long st_dev; /*dev_t*/
+    /** Device ID of device containing file. */
+    public final long st_dev; /*dev_t*/
 
-  /** File serial number (inode). */
-  public final long st_ino; /*ino_t*/
+    /** File serial number (inode). */
+    public final long st_ino; /*ino_t*/
 
-  /** Mode (permissions) of file. */
-  public final int st_mode; /*mode_t*/
+    /** Mode (permissions) of file. */
+    public final int st_mode; /*mode_t*/
 
-  /** Number of hard links to the file. */
-  public final long st_nlink; /*nlink_t*/
+    /** Number of hard links to the file. */
+    public final long st_nlink; /*nlink_t*/
 
-  /** User ID of file. */
-  public final int st_uid; /*uid_t*/
+    /** User ID of file. */
+    public final int st_uid; /*uid_t*/
 
-  /** Group ID of file. */
-  public final int st_gid; /*gid_t*/
+    /** Group ID of file. */
+    public final int st_gid; /*gid_t*/
 
-  /** Device ID (if file is character or block special). */
-  public final long st_rdev; /*dev_t*/
+    /** Device ID (if file is character or block special). */
+    public final long st_rdev; /*dev_t*/
 
-  /**
-   * For regular files, the file size in bytes.
-   * For symbolic links, the length in bytes of the pathname contained in the symbolic link.
-   * For a shared memory object, the length in bytes.
-   * For a typed memory object, the length in bytes.
-   * For other file types, the use of this field is unspecified.
-   */
-  public final long st_size; /*off_t*/
+    /**
+     * For regular files, the file size in bytes.
+     * For symbolic links, the length in bytes of the pathname contained in the symbolic link.
+     * For a shared memory object, the length in bytes.
+     * For a typed memory object, the length in bytes.
+     * For other file types, the use of this field is unspecified.
+     */
+    public final long st_size; /*off_t*/
 
-  /** Seconds part of time of last access. */
-  public final long st_atime; /*time_t*/
+    /** Seconds part of time of last access. */
+    public final long st_atime; /*time_t*/
 
-  /** StructTimespec with time of last access. */
-  public final StructTimespec st_atim;
+    /** StructTimespec with time of last access. */
+    public final StructTimespec st_atim;
 
-  /** Seconds part of time of last data modification. */
-  public final long st_mtime; /*time_t*/
+    /** Seconds part of time of last data modification. */
+    public final long st_mtime; /*time_t*/
 
-  /** StructTimespec with time of last modification. */
-  public final StructTimespec st_mtim;
+    /** StructTimespec with time of last modification. */
+    public final StructTimespec st_mtim;
 
-  /** Seconds part of time of last status change */
-  public final long st_ctime; /*time_t*/
+    /** Seconds part of time of last status change */
+    public final long st_ctime; /*time_t*/
 
-  /** StructTimespec with time of last status change. */
-  public final StructTimespec st_ctim;
+    /** StructTimespec with time of last status change. */
+    public final StructTimespec st_ctim;
 
-  /**
-   * A file system-specific preferred I/O block size for this object.
-   * For some file system types, this may vary from file to file.
-   */
-  public final long st_blksize; /*blksize_t*/
+    /**
+     * A file system-specific preferred I/O block size for this object.
+     * For some file system types, this may vary from file to file.
+     */
+    public final long st_blksize; /*blksize_t*/
 
-  /** Number of blocks allocated for this object. */
-  public final long st_blocks; /*blkcnt_t*/
+    /** Number of blocks allocated for this object. */
+    public final long st_blocks; /*blkcnt_t*/
 
-  /**
-   * Constructs an instance with the given field values.
-   */
-  public StructStat(long st_dev, long st_ino, int st_mode, long st_nlink, int st_uid, int st_gid,
-                    long st_rdev, long st_size, long st_atime, long st_mtime, long st_ctime,
-                    long st_blksize, long st_blocks) {
-    this(st_dev, st_ino, st_mode, st_nlink, st_uid, st_gid,
-        st_rdev, st_size, new StructTimespec(st_atime, 0L), new StructTimespec(st_mtime, 0L),
-        new StructTimespec(st_ctime, 0L), st_blksize, st_blocks);
-  }
+    /**
+     * Constructs an instance with the given field values.
+     */
+    public StructStat(long st_dev, long st_ino, int st_mode, long st_nlink, int st_uid, int st_gid,
+            long st_rdev, long st_size, long st_atime, long st_mtime, long st_ctime,
+            long st_blksize, long st_blocks) {
+        this(st_dev, st_ino, st_mode, st_nlink, st_uid, st_gid,
+                st_rdev, st_size, new StructTimespec(st_atime, 0L), new StructTimespec(st_mtime, 0L),
+                new StructTimespec(st_ctime, 0L), st_blksize, st_blocks);
+    }
 
-  /**
-   * Constructs an instance with the given field values.
-   */
-  public StructStat(long st_dev, long st_ino, int st_mode, long st_nlink, int st_uid, int st_gid,
-                    long st_rdev, long st_size, StructTimespec st_atim, StructTimespec st_mtim,
-                    StructTimespec st_ctim, long st_blksize, long st_blocks) {
-    this.st_dev = st_dev;
-    this.st_ino = st_ino;
-    this.st_mode = st_mode;
-    this.st_nlink = st_nlink;
-    this.st_uid = st_uid;
-    this.st_gid = st_gid;
-    this.st_rdev = st_rdev;
-    this.st_size = st_size;
-    this.st_atime = st_atim.tv_sec;
-    this.st_mtime = st_mtim.tv_sec;
-    this.st_ctime = st_ctim.tv_sec;
-    this.st_atim = st_atim;
-    this.st_mtim = st_mtim;
-    this.st_ctim = st_ctim;
-    this.st_blksize = st_blksize;
-    this.st_blocks = st_blocks;
-  }
+    /**
+     * Constructs an instance with the given field values.
+     */
+    public StructStat(long st_dev, long st_ino, int st_mode, long st_nlink, int st_uid, int st_gid,
+            long st_rdev, long st_size, StructTimespec st_atim, StructTimespec st_mtim,
+            StructTimespec st_ctim, long st_blksize, long st_blocks) {
+        this.st_dev = st_dev;
+        this.st_ino = st_ino;
+        this.st_mode = st_mode;
+        this.st_nlink = st_nlink;
+        this.st_uid = st_uid;
+        this.st_gid = st_gid;
+        this.st_rdev = st_rdev;
+        this.st_size = st_size;
+        this.st_atime = st_atim.tv_sec;
+        this.st_mtime = st_mtim.tv_sec;
+        this.st_ctime = st_ctim.tv_sec;
+        this.st_atim = st_atim;
+        this.st_mtim = st_mtim;
+        this.st_ctim = st_ctim;
+        this.st_blksize = st_blksize;
+        this.st_blocks = st_blocks;
+    }
 
-  @Override public String toString() {
-    return Objects.toString(this);
-  }
+    @Override public String toString() {
+        return Objects.toString(this);
+    }
 }
diff --git a/android/system/StructStatVfs.java b/android/system/StructStatVfs.java
index 942a39a..3b192e7 100644
--- a/android/system/StructStatVfs.java
+++ b/android/system/StructStatVfs.java
@@ -22,59 +22,59 @@
  * File information returned by {@link Os#fstatvfs} and {@link Os#statvfs}.
  */
 public final class StructStatVfs {
-  /** File system block size (used for block counts). */
-  public final long f_bsize; /*unsigned long*/
+    /** File system block size (used for block counts). */
+    public final long f_bsize; /*unsigned long*/
 
-  /** Fundamental file system block size. */
-  public final long f_frsize; /*unsigned long*/
+    /** Fundamental file system block size. */
+    public final long f_frsize; /*unsigned long*/
 
-  /** Total block count. */
-  public final long f_blocks; /*fsblkcnt_t*/
+    /** Total block count. */
+    public final long f_blocks; /*fsblkcnt_t*/
 
-  /** Free block count. */
-  public final long f_bfree; /*fsblkcnt_t*/
+    /** Free block count. */
+    public final long f_bfree; /*fsblkcnt_t*/
 
-  /** Free block count available to non-root. */
-  public final long f_bavail; /*fsblkcnt_t*/
+    /** Free block count available to non-root. */
+    public final long f_bavail; /*fsblkcnt_t*/
 
-  /** Total file (inode) count. */
-  public final long f_files; /*fsfilcnt_t*/
+    /** Total file (inode) count. */
+    public final long f_files; /*fsfilcnt_t*/
 
-  /** Free file (inode) count. */
-  public final long f_ffree; /*fsfilcnt_t*/
+    /** Free file (inode) count. */
+    public final long f_ffree; /*fsfilcnt_t*/
 
-  /** Free file (inode) count available to non-root. */
-  public final long f_favail; /*fsfilcnt_t*/
+    /** Free file (inode) count available to non-root. */
+    public final long f_favail; /*fsfilcnt_t*/
 
-  /** File system id. */
-  public final long f_fsid; /*unsigned long*/
+    /** File system id. */
+    public final long f_fsid; /*unsigned long*/
 
-  /** Bit mask of ST_* flags. */
-  public final long f_flag; /*unsigned long*/
+    /** Bit mask of ST_* flags. */
+    public final long f_flag; /*unsigned long*/
 
-  /** Maximum filename length. */
-  public final long f_namemax; /*unsigned long*/
+    /** Maximum filename length. */
+    public final long f_namemax; /*unsigned long*/
 
-  /**
-   * Constructs an instance with the given field values.
-   */
-  public StructStatVfs(long f_bsize, long f_frsize, long f_blocks, long f_bfree, long f_bavail,
-                long f_files, long f_ffree, long f_favail,
-                long f_fsid, long f_flag, long f_namemax) {
-    this.f_bsize = f_bsize;
-    this.f_frsize = f_frsize;
-    this.f_blocks = f_blocks;
-    this.f_bfree = f_bfree;
-    this.f_bavail = f_bavail;
-    this.f_files = f_files;
-    this.f_ffree = f_ffree;
-    this.f_favail = f_favail;
-    this.f_fsid = f_fsid;
-    this.f_flag = f_flag;
-    this.f_namemax = f_namemax;
-  }
+    /**
+     * Constructs an instance with the given field values.
+     */
+    public StructStatVfs(long f_bsize, long f_frsize, long f_blocks, long f_bfree, long f_bavail,
+            long f_files, long f_ffree, long f_favail,
+            long f_fsid, long f_flag, long f_namemax) {
+        this.f_bsize = f_bsize;
+        this.f_frsize = f_frsize;
+        this.f_blocks = f_blocks;
+        this.f_bfree = f_bfree;
+        this.f_bavail = f_bavail;
+        this.f_files = f_files;
+        this.f_ffree = f_ffree;
+        this.f_favail = f_favail;
+        this.f_fsid = f_fsid;
+        this.f_flag = f_flag;
+        this.f_namemax = f_namemax;
+    }
 
-  @Override public String toString() {
-    return Objects.toString(this);
-  }
+    @Override public String toString() {
+        return Objects.toString(this);
+    }
 }
diff --git a/android/system/StructTimespec.java b/android/system/StructTimespec.java
index b5e192e..c106780 100644
--- a/android/system/StructTimespec.java
+++ b/android/system/StructTimespec.java
@@ -22,58 +22,58 @@
  * Corresponds to C's {@code struct timespec} from {@code <time.h>}.
  */
 public final class StructTimespec implements Comparable<StructTimespec> {
-  /** Seconds part of time of last data modification. */
-  public final long tv_sec; /*time_t*/
+    /** Seconds part of time of last data modification. */
+    public final long tv_sec; /*time_t*/
 
-  /** Nanoseconds (values are [0, 999999999]). */
-  public final long tv_nsec;
+    /** Nanoseconds (values are [0, 999999999]). */
+    public final long tv_nsec;
 
-  public StructTimespec(long tv_sec, long tv_nsec) {
-      this.tv_sec = tv_sec;
-      this.tv_nsec = tv_nsec;
-      if (tv_nsec < 0 || tv_nsec > 999_999_999) {
-          throw new IllegalArgumentException(
-                  "tv_nsec value " + tv_nsec + " is not in [0, 999999999]");
-      }
-  }
+    public StructTimespec(long tv_sec, long tv_nsec) {
+        this.tv_sec = tv_sec;
+        this.tv_nsec = tv_nsec;
+        if (tv_nsec < 0 || tv_nsec > 999_999_999) {
+            throw new IllegalArgumentException(
+                    "tv_nsec value " + tv_nsec + " is not in [0, 999999999]");
+        }
+    }
 
-  @Override
-  public int compareTo(StructTimespec other) {
-      if (tv_sec > other.tv_sec) {
-          return 1;
-      }
-      if (tv_sec < other.tv_sec) {
-          return -1;
-      }
-      if (tv_nsec > other.tv_nsec) {
-          return 1;
-      }
-      if (tv_nsec < other.tv_nsec) {
-          return -1;
-      }
-      return 0;
-  }
+    @Override
+    public int compareTo(StructTimespec other) {
+        if (tv_sec > other.tv_sec) {
+            return 1;
+        }
+        if (tv_sec < other.tv_sec) {
+            return -1;
+        }
+        if (tv_nsec > other.tv_nsec) {
+            return 1;
+        }
+        if (tv_nsec < other.tv_nsec) {
+            return -1;
+        }
+        return 0;
+    }
 
-  @Override
-  public boolean equals(Object o) {
-      if (this == o) return true;
-      if (o == null || getClass() != o.getClass()) return false;
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
 
-      StructTimespec that = (StructTimespec) o;
+        StructTimespec that = (StructTimespec) o;
 
-      if (tv_sec != that.tv_sec) return false;
-      return tv_nsec == that.tv_nsec;
-  }
+        if (tv_sec != that.tv_sec) return false;
+        return tv_nsec == that.tv_nsec;
+    }
 
-  @Override
-  public int hashCode() {
-      int result = (int) (tv_sec ^ (tv_sec >>> 32));
-      result = 31 * result + (int) (tv_nsec ^ (tv_nsec >>> 32));
-      return result;
-  }
+    @Override
+    public int hashCode() {
+        int result = (int) (tv_sec ^ (tv_sec >>> 32));
+        result = 31 * result + (int) (tv_nsec ^ (tv_nsec >>> 32));
+        return result;
+    }
 
-  @Override
-  public String toString() {
-      return Objects.toString(this);
-  }
+    @Override
+    public String toString() {
+        return Objects.toString(this);
+    }
 }
diff --git a/android/system/StructTimeval.java b/android/system/StructTimeval.java
index 8a155b4..91a6f2a 100644
--- a/android/system/StructTimeval.java
+++ b/android/system/StructTimeval.java
@@ -25,28 +25,28 @@
  * @hide
  */
 public final class StructTimeval {
-  /** Seconds. */
-  public final long tv_sec;
+    /** Seconds. */
+    public final long tv_sec;
 
-  /** Microseconds. */
-  public final long tv_usec;
+    /** Microseconds. */
+    public final long tv_usec;
 
-  private StructTimeval(long tv_sec, long tv_usec) {
-    this.tv_sec = tv_sec;
-    this.tv_usec = tv_usec;
-  }
+    private StructTimeval(long tv_sec, long tv_usec) {
+        this.tv_sec = tv_sec;
+        this.tv_usec = tv_usec;
+    }
 
-  public static StructTimeval fromMillis(long millis) {
-    long tv_sec = millis / 1000;
-    long tv_usec = (millis - (tv_sec * 1000)) * 1000;
-    return new StructTimeval(tv_sec, tv_usec);
-  }
+    public static StructTimeval fromMillis(long millis) {
+        long tv_sec = millis / 1000;
+        long tv_usec = (millis - (tv_sec * 1000)) * 1000;
+        return new StructTimeval(tv_sec, tv_usec);
+    }
 
-  public long toMillis() {
-    return (tv_sec * 1000) + (tv_usec / 1000);
-  }
+    public long toMillis() {
+        return (tv_sec * 1000) + (tv_usec / 1000);
+    }
 
-  @Override public String toString() {
-    return Objects.toString(this);
-  }
+    @Override public String toString() {
+        return Objects.toString(this);
+    }
 }
diff --git a/android/system/StructUcred.java b/android/system/StructUcred.java
index a1e3cd6..e6e1479 100644
--- a/android/system/StructUcred.java
+++ b/android/system/StructUcred.java
@@ -24,22 +24,22 @@
  * @hide
  */
 public final class StructUcred {
-  /** The peer's process id. */
-  public final int pid;
+    /** The peer's process id. */
+    public final int pid;
 
-  /** The peer process' uid. */
-  public final int uid;
+    /** The peer process' uid. */
+    public final int uid;
 
-  /** The peer process' gid. */
-  public final int gid;
+    /** The peer process' gid. */
+    public final int gid;
 
-  public StructUcred(int pid, int uid, int gid) {
-    this.pid = pid;
-    this.uid = uid;
-    this.gid = gid;
-  }
+    public StructUcred(int pid, int uid, int gid) {
+        this.pid = pid;
+        this.uid = uid;
+        this.gid = gid;
+    }
 
-  @Override public String toString() {
-    return Objects.toString(this);
-  }
+    @Override public String toString() {
+        return Objects.toString(this);
+    }
 }
diff --git a/android/system/StructUtsname.java b/android/system/StructUtsname.java
index 3777838..9cfe21b 100644
--- a/android/system/StructUtsname.java
+++ b/android/system/StructUtsname.java
@@ -23,33 +23,33 @@
  * Corresponds to C's {@code struct utsname} from {@code <sys/utsname.h>}.
  */
 public final class StructUtsname {
-  /** The OS name, such as "Linux". */
-  public final String sysname;
+    /** The OS name, such as "Linux". */
+    public final String sysname;
 
-  /** The machine's unqualified name on some implementation-defined network. */
-  public final String nodename;
+    /** The machine's unqualified name on some implementation-defined network. */
+    public final String nodename;
 
-  /** The OS release, such as "2.6.35-27-generic". */
-  public final String release;
+    /** The OS release, such as "2.6.35-27-generic". */
+    public final String release;
 
-  /** The OS version, such as "#48-Ubuntu SMP Tue Feb 22 20:25:29 UTC 2011". */
-  public final String version;
+    /** The OS version, such as "#48-Ubuntu SMP Tue Feb 22 20:25:29 UTC 2011". */
+    public final String version;
 
-  /** The machine architecture, such as "armv7l" or "x86_64". */
-  public final String machine;
+    /** The machine architecture, such as "armv7l" or "x86_64". */
+    public final String machine;
 
-  /**
-   * Constructs an instance with the given field values.
-   */
-  public StructUtsname(String sysname, String nodename, String release, String version, String machine) {
-    this.sysname = sysname;
-    this.nodename = nodename;
-    this.release = release;
-    this.version = version;
-    this.machine = machine;
-  }
+    /**
+     * Constructs an instance with the given field values.
+     */
+    public StructUtsname(String sysname, String nodename, String release, String version, String machine) {
+        this.sysname = sysname;
+        this.nodename = nodename;
+        this.release = release;
+        this.version = version;
+        this.machine = machine;
+    }
 
-  @Override public String toString() {
-    return Objects.toString(this);
-  }
+    @Override public String toString() {
+        return Objects.toString(this);
+    }
 }
diff --git a/android/telephony/CarrierConfigManager.java b/android/telephony/CarrierConfigManager.java
index 689ce95..99f8cfb 100644
--- a/android/telephony/CarrierConfigManager.java
+++ b/android/telephony/CarrierConfigManager.java
@@ -763,6 +763,18 @@
     public static final String KEY_CDMA_DTMF_TONE_DELAY_INT = "cdma_dtmf_tone_delay_int";
 
     /**
+     * Some carriers will send call forwarding responses for voicemail in a format that is not 3gpp
+     * compliant, which causes issues during parsing. This causes the
+     * {@link com.android.internal.telephony.CallForwardInfo#number} to contain non-numerical
+     * characters instead of a number.
+     *
+     * If true, we will detect the non-numerical characters and replace them with "Voicemail".
+     * @hide
+     */
+    public static final String KEY_CALL_FORWARDING_MAP_NON_NUMBER_TO_VOICEMAIL_BOOL =
+            "call_forwarding_map_non_number_to_voicemail_bool";
+
+    /**
      * Determines whether conference calls are supported by a carrier.  When {@code true},
      * conference calling is supported, {@code false otherwise}.
      */
@@ -1407,6 +1419,17 @@
         "support_3gpp_call_forwarding_while_roaming_bool";
 
     /**
+     * Boolean indicating whether to display voicemail number as default call forwarding number in
+     * call forwarding settings.
+     * If true, display vm number when cf number is null.
+     * If false, display the cf number from network.
+     * By default this value is false.
+     * @hide
+     */
+    public static final String KEY_DISPLAY_VOICEMAIL_NUMBER_AS_DEFAULT_CALL_FORWARDING_NUMBER_BOOL =
+            "display_voicemail_number_as_default_call_forwarding_number";
+
+    /**
      * When {@code true}, the user will be notified when they attempt to place an international call
      * when the call is placed using wifi calling.
      * @hide
@@ -1573,6 +1596,25 @@
     public static final String KEY_SHOW_IMS_REGISTRATION_STATUS_BOOL =
             "show_ims_registration_status_bool";
 
+    /**
+     * The flag to disable the popup dialog which warns the user of data charges.
+     * @hide
+     */
+    public static final String KEY_DISABLE_CHARGE_INDICATION_BOOL =
+            "disable_charge_indication_bool";
+
+    /**
+     * Boolean indicating whether to skip the call forwarding (CF) fail-to-disable dialog.
+     * The logic used to determine whether we succeeded in disabling is carrier specific,
+     * so the dialog may not always be accurate.
+     * {@code false} - show CF fail-to-disable dialog.
+     * {@code true}  - skip showing CF fail-to-disable dialog.
+     *
+     * @hide
+     */
+    public static final String KEY_SKIP_CF_FAIL_TO_DISABLE_DIALOG_BOOL =
+            "skip_cf_fail_to_disable_dialog_bool";
+
     /** The default value for every variable. */
     private final static PersistableBundle sDefaults;
 
@@ -1703,6 +1745,7 @@
         sDefaults.putInt(KEY_GSM_DTMF_TONE_DELAY_INT, 0);
         sDefaults.putInt(KEY_IMS_DTMF_TONE_DELAY_INT, 0);
         sDefaults.putInt(KEY_CDMA_DTMF_TONE_DELAY_INT, 100);
+        sDefaults.putBoolean(KEY_CALL_FORWARDING_MAP_NON_NUMBER_TO_VOICEMAIL_BOOL, false);
         sDefaults.putInt(KEY_CDMA_3WAYCALL_FLASH_DELAY_INT , 0);
         sDefaults.putBoolean(KEY_SUPPORT_CONFERENCE_CALL_BOOL, true);
         sDefaults.putBoolean(KEY_SUPPORT_IMS_CONFERENCE_CALL_BOOL, true);
@@ -1726,6 +1769,7 @@
         sDefaults.putString(KEY_CARRIER_NAME_STRING, "");
         sDefaults.putBoolean(KEY_SUPPORT_DIRECT_FDN_DIALING_BOOL, false);
         sDefaults.putBoolean(KEY_CARRIER_DEFAULT_DATA_ROAMING_ENABLED_BOOL, false);
+        sDefaults.putBoolean(KEY_SKIP_CF_FAIL_TO_DISABLE_DIALOG_BOOL, false);
 
         // MMS defaults
         sDefaults.putBoolean(KEY_MMS_ALIAS_ENABLED_BOOL, false);
@@ -1827,6 +1871,8 @@
         sDefaults.putInt(KEY_EMERGENCY_NOTIFICATION_DELAY_INT, -1);
         sDefaults.putBoolean(KEY_ALLOW_USSD_REQUESTS_VIA_TELEPHONY_MANAGER_BOOL, true);
         sDefaults.putBoolean(KEY_SUPPORT_3GPP_CALL_FORWARDING_WHILE_ROAMING_BOOL, true);
+        sDefaults.putBoolean(KEY_DISPLAY_VOICEMAIL_NUMBER_AS_DEFAULT_CALL_FORWARDING_NUMBER_BOOL,
+                false);
         sDefaults.putBoolean(KEY_NOTIFY_INTERNATIONAL_CALL_ON_WFC_BOOL, false);
         sDefaults.putStringArray(KEY_CALL_FORWARDING_BLOCKS_WHILE_ROAMING_STRING_ARRAY,
                 null);
@@ -1840,6 +1886,7 @@
         sDefaults.putStringArray(KEY_NON_ROAMING_OPERATOR_STRING_ARRAY, null);
         sDefaults.putStringArray(KEY_ROAMING_OPERATOR_STRING_ARRAY, null);
         sDefaults.putBoolean(KEY_SHOW_IMS_REGISTRATION_STATUS_BOOL, false);
+        sDefaults.putBoolean(KEY_DISABLE_CHARGE_INDICATION_BOOL, false);
     }
 
     /**
diff --git a/android/telephony/MbmsDownloadSession.java b/android/telephony/MbmsDownloadSession.java
index 764b7b2..9a9877a 100644
--- a/android/telephony/MbmsDownloadSession.java
+++ b/android/telephony/MbmsDownloadSession.java
@@ -77,8 +77,9 @@
      * Integer extra that Android will attach to the intent supplied via
      * {@link android.telephony.mbms.DownloadRequest.Builder#setAppIntent(Intent)}
      * Indicates the result code of the download. One of
-     * {@link #RESULT_SUCCESSFUL}, {@link #RESULT_EXPIRED}, {@link #RESULT_CANCELLED}, or
-     * {@link #RESULT_IO_ERROR}.
+     * {@link #RESULT_SUCCESSFUL}, {@link #RESULT_EXPIRED}, {@link #RESULT_CANCELLED},
+     * {@link #RESULT_IO_ERROR}, {@link #RESULT_DOWNLOAD_FAILURE}, {@link #RESULT_OUT_OF_STORAGE},
+     * {@link #RESULT_SERVICE_ID_NOT_DEFINED}, or {@link #RESULT_FILE_ROOT_UNREACHABLE}.
      *
      * This extra may also be used by the middleware when it is sending intents to the app.
      */
@@ -142,11 +143,41 @@
 
     /**
      * Indicates that the download will not be completed due to an I/O error incurred while
-     * writing to temp files. This commonly indicates that the device is out of storage space,
-     * but may indicate other conditions as well (such as an SD card being removed).
+     * writing to temp files.
+     *
+     * This is likely a transient error and another {@link DownloadRequest} should be sent to try
+     * the download again.
      */
     public static final int RESULT_IO_ERROR = 4;
-    // TODO - more results!
+
+    /**
+     * Indicates that the Service ID specified in the {@link DownloadRequest} is incorrect due to
+     * the Id being incorrect, stale, expired, or similar.
+     */
+    public static final int RESULT_SERVICE_ID_NOT_DEFINED = 5;
+
+    /**
+     * Indicates that there was an error while processing downloaded files, such as a file repair or
+     * file decoding error and is not due to a file I/O error.
+     *
+     * This is likely a transient error and another {@link DownloadRequest} should be sent to try
+     * the download again.
+     */
+    public static final int RESULT_DOWNLOAD_FAILURE = 6;
+
+    /**
+     * Indicates that the file system is full and the {@link DownloadRequest} can not complete.
+     * Either space must be made on the current file system or the temp file root location must be
+     * changed to a location that is not full to download the temp files.
+     */
+    public static final int RESULT_OUT_OF_STORAGE = 7;
+
+    /**
+     * Indicates that the file root that was set is currently unreachable. This can happen if the
+     * temp files are set to be stored on external storage and the SD card was removed, for example.
+     * The temp file root should be changed before sending another DownloadRequest.
+     */
+    public static final int RESULT_FILE_ROOT_UNREACHABLE = 8;
 
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
diff --git a/android/telephony/NetworkScanRequest.java b/android/telephony/NetworkScanRequest.java
index d2aef20..9674c93 100644
--- a/android/telephony/NetworkScanRequest.java
+++ b/android/telephony/NetworkScanRequest.java
@@ -19,6 +19,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import java.util.ArrayList;
 import java.util.Arrays;
 
 /**
@@ -38,6 +39,20 @@
     public static final int MAX_BANDS = 8;
     /** @hide */
     public static final int MAX_CHANNELS = 32;
+    /** @hide */
+    public static final int MAX_MCC_MNC_LIST_SIZE = 20;
+    /** @hide */
+    public static final int MIN_SEARCH_PERIODICITY_SEC = 5;
+    /** @hide */
+    public static final int MAX_SEARCH_PERIODICITY_SEC = 300;
+    /** @hide */
+    public static final int MIN_SEARCH_MAX_SEC = 60;
+    /** @hide */
+    public static final int MAX_SEARCH_MAX_SEC = 3600;
+    /** @hide */
+    public static final int MIN_INCREMENTAL_PERIODICITY_SEC = 1;
+    /** @hide */
+    public static final int MAX_INCREMENTAL_PERIODICITY_SEC = 10;
 
     /** Performs the scan only once */
     public static final int SCAN_TYPE_ONE_SHOT = 0;
@@ -46,24 +61,84 @@
      *
      * The modem will start new scans periodically, and the interval between two scans is usually
      * multiple minutes.
-     * */
+     */
     public static final int SCAN_TYPE_PERIODIC = 1;
 
     /** Defines the type of the scan. */
     public int scanType;
 
+    /**
+     * Search periodicity (in seconds).
+     * Expected range for the input is [5s - 300s]
+     * This value must be less than or equal to maxSearchTime
+     */
+    public int searchPeriodicity;
+
+    /**
+     * Maximum duration of the periodic search (in seconds).
+     * Expected range for the input is [60s - 3600s]
+     * If the search lasts this long, it will be terminated.
+     */
+    public int maxSearchTime;
+
+    /**
+     * Indicates whether the modem should report incremental
+     * results of the network scan to the client.
+     * FALSE – Incremental results are not reported.
+     * TRUE (default) – Incremental results are reported
+     */
+    public boolean incrementalResults;
+
+    /**
+     * Indicates the periodicity with which the modem should
+     * report incremental results to the client (in seconds).
+     * Expected range for the input is [1s - 10s]
+     * This value must be less than or equal to maxSearchTime
+     */
+    public int incrementalResultsPeriodicity;
+
     /** Describes the radio access technologies with bands or channels that need to be scanned. */
     public RadioAccessSpecifier[] specifiers;
 
     /**
+     * Describes the List of PLMN ids (MCC-MNC)
+     * If any PLMN of this list is found, search should end at that point and
+     * results with all PLMN found till that point should be sent as response.
+     * If list not sent, search to be completed till end and all PLMNs found to be reported.
+     * Max size of array is MAX_MCC_MNC_LIST_SIZE
+     */
+    public ArrayList<String> mccMncs;
+
+    /**
      * Creates a new NetworkScanRequest with scanType and network specifiers
      *
      * @param scanType The type of the scan
      * @param specifiers the radio network with bands / channels to be scanned
+     * @param searchPeriodicity Search periodicity (in seconds)
+     * @param maxSearchTime Maximum duration of the periodic search (in seconds)
+     * @param incrementalResults Indicates whether the modem should report incremental
+     *                           results of the network scan to the client
+     * @param incrementalResultsPeriodicity Indicates the periodicity with which the modem should
+     *                                      report incremental results to the client (in seconds)
+     * @param mccMncs Describes the List of PLMN ids (MCC-MNC)
      */
-    public NetworkScanRequest(int scanType, RadioAccessSpecifier[] specifiers) {
+    public NetworkScanRequest(int scanType, RadioAccessSpecifier[] specifiers,
+                    int searchPeriodicity,
+                    int maxSearchTime,
+                    boolean incrementalResults,
+                    int incrementalResultsPeriodicity,
+                    ArrayList<String> mccMncs) {
         this.scanType = scanType;
         this.specifiers = specifiers;
+        this.searchPeriodicity = searchPeriodicity;
+        this.maxSearchTime = maxSearchTime;
+        this.incrementalResults = incrementalResults;
+        this.incrementalResultsPeriodicity = incrementalResultsPeriodicity;
+        if (mccMncs != null) {
+            this.mccMncs = mccMncs;
+        } else {
+            this.mccMncs = new ArrayList<>();
+        }
     }
 
     @Override
@@ -75,6 +150,11 @@
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeInt(scanType);
         dest.writeParcelableArray(specifiers, flags);
+        dest.writeInt(searchPeriodicity);
+        dest.writeInt(maxSearchTime);
+        dest.writeBoolean(incrementalResults);
+        dest.writeInt(incrementalResultsPeriodicity);
+        dest.writeStringList(mccMncs);
     }
 
     private NetworkScanRequest(Parcel in) {
@@ -82,6 +162,12 @@
         specifiers = (RadioAccessSpecifier[]) in.readParcelableArray(
                 Object.class.getClassLoader(),
                 RadioAccessSpecifier.class);
+        searchPeriodicity = in.readInt();
+        maxSearchTime = in.readInt();
+        incrementalResults = in.readBoolean();
+        incrementalResultsPeriodicity = in.readInt();
+        mccMncs = new ArrayList<>();
+        in.readStringList(mccMncs);
     }
 
     @Override
@@ -99,13 +185,24 @@
         }
 
         return (scanType == nsr.scanType
-                && Arrays.equals(specifiers, nsr.specifiers));
+                && Arrays.equals(specifiers, nsr.specifiers)
+                && searchPeriodicity == nsr.searchPeriodicity
+                && maxSearchTime == nsr.maxSearchTime
+                && incrementalResults == nsr.incrementalResults
+                && incrementalResultsPeriodicity == nsr.incrementalResultsPeriodicity
+                && (((mccMncs != null)
+                && mccMncs.equals(nsr.mccMncs))));
     }
 
     @Override
     public int hashCode () {
         return ((scanType * 31)
-                + (Arrays.hashCode(specifiers)) * 37);
+                + (Arrays.hashCode(specifiers)) * 37
+                + (searchPeriodicity * 41)
+                + (maxSearchTime * 43)
+                + ((incrementalResults == true? 1 : 0) * 47)
+                + (incrementalResultsPeriodicity * 53)
+                + (mccMncs.hashCode() * 59));
     }
 
     public static final Creator<NetworkScanRequest> CREATOR =
diff --git a/android/telephony/ServiceState.java b/android/telephony/ServiceState.java
index e448fb2..116e711 100644
--- a/android/telephony/ServiceState.java
+++ b/android/telephony/ServiceState.java
@@ -1197,15 +1197,6 @@
         }
     }
 
-    /**
-     * @Deprecated to be removed Q3 2013 use {@link #getVoiceNetworkType}
-     * @hide
-     */
-    public int getNetworkType() {
-        Rlog.e(LOG_TAG, "ServiceState.getNetworkType() DEPRECATED will be removed *******");
-        return rilRadioTechnologyToNetworkType(mRilVoiceRadioTechnology);
-    }
-
     /** @hide */
     public int getDataNetworkType() {
         return rilRadioTechnologyToNetworkType(mRilDataRadioTechnology);
diff --git a/android/telephony/SignalStrength.java b/android/telephony/SignalStrength.java
index 9e02399..c8b4776 100644
--- a/android/telephony/SignalStrength.java
+++ b/android/telephony/SignalStrength.java
@@ -19,7 +19,6 @@
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.telephony.Rlog;
 import android.util.Log;
 import android.content.res.Resources;
 
@@ -429,6 +428,15 @@
     }
 
     /**
+     * Fix {@link #isGsm} based on the signal strength data.
+     *
+     * @hide
+     */
+    public void fixType() {
+        isGsm = getCdmaRelatedSignalStrength() == SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
+    }
+
+    /**
      * @param true - Gsm, Lte phones
      *        false - Cdma phones
      *
@@ -541,30 +549,7 @@
      *     while 4 represents a very strong signal strength.
      */
     public int getLevel() {
-        int level = 0;
-
-        if (isGsm) {
-            level = getLteLevel();
-            if (level == SIGNAL_STRENGTH_NONE_OR_UNKNOWN) {
-                level = getTdScdmaLevel();
-                if (level == SIGNAL_STRENGTH_NONE_OR_UNKNOWN) {
-                    level = getGsmLevel();
-                }
-            }
-        } else {
-            int cdmaLevel = getCdmaLevel();
-            int evdoLevel = getEvdoLevel();
-            if (evdoLevel == SIGNAL_STRENGTH_NONE_OR_UNKNOWN) {
-                /* We don't know evdo, use cdma */
-                level = cdmaLevel;
-            } else if (cdmaLevel == SIGNAL_STRENGTH_NONE_OR_UNKNOWN) {
-                /* We don't know cdma, use evdo */
-                level = evdoLevel;
-            } else {
-                /* We know both, use the lowest level */
-                level = cdmaLevel < evdoLevel ? cdmaLevel : evdoLevel;
-            }
-        }
+        int level = isGsm ? getGsmRelatedSignalStrength() : getCdmaRelatedSignalStrength();
         if (DBG) log("getLevel=" + level);
         return level;
     }
@@ -1049,6 +1034,36 @@
                 + " " + (isGsm ? "gsm|lte" : "cdma"));
     }
 
+    /** Returns the signal strength related to GSM. */
+    private int getGsmRelatedSignalStrength() {
+        int level = getLteLevel();
+        if (level == SIGNAL_STRENGTH_NONE_OR_UNKNOWN) {
+            level = getTdScdmaLevel();
+            if (level == SIGNAL_STRENGTH_NONE_OR_UNKNOWN) {
+                level = getGsmLevel();
+            }
+        }
+        return level;
+    }
+
+    /** Returns the signal strength related to CDMA. */
+    private int getCdmaRelatedSignalStrength() {
+        int level;
+        int cdmaLevel = getCdmaLevel();
+        int evdoLevel = getEvdoLevel();
+        if (evdoLevel == SIGNAL_STRENGTH_NONE_OR_UNKNOWN) {
+            /* We don't know evdo, use cdma */
+            level = cdmaLevel;
+        } else if (cdmaLevel == SIGNAL_STRENGTH_NONE_OR_UNKNOWN) {
+            /* We don't know cdma, use evdo */
+            level = evdoLevel;
+        } else {
+            /* We know both, use the lowest level */
+            level = cdmaLevel < evdoLevel ? cdmaLevel : evdoLevel;
+        }
+        return level;
+    }
+
     /**
      * Set SignalStrength based on intent notifier map
      *
diff --git a/android/telephony/mbms/DownloadRequest.java b/android/telephony/mbms/DownloadRequest.java
index 5a57f32..f0d60b6 100644
--- a/android/telephony/mbms/DownloadRequest.java
+++ b/android/telephony/mbms/DownloadRequest.java
@@ -16,6 +16,7 @@
 
 package android.telephony.mbms;
 
+import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.content.Intent;
 import android.net.Uri;
@@ -26,7 +27,6 @@
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
-import java.io.File;
 import java.io.IOException;
 import java.io.ObjectInputStream;
 import java.io.ObjectOutputStream;
@@ -71,6 +71,19 @@
         private String appIntent;
         private int version = CURRENT_VERSION;
 
+
+        /**
+         * Builds a new DownloadRequest.
+         * @param sourceUri the source URI for the DownloadRequest to be built. This URI should
+         *     never be null.
+         */
+        public Builder(@NonNull Uri sourceUri) {
+            if (sourceUri == null) {
+                throw new IllegalArgumentException("Source URI must be non-null.");
+            }
+            source = sourceUri;
+        }
+
         /**
          * Sets the service from which the download request to be built will download from.
          * @param serviceInfo
@@ -92,15 +105,6 @@
         }
 
         /**
-         * Sets the source URI for the download request to be built.
-         * @param source
-         */
-        public Builder setSource(Uri source) {
-            this.source = source;
-            return this;
-        }
-
-        /**
          * Set the subscription ID on which the file(s) should be downloaded.
          * @param subscriptionId
          */
@@ -316,9 +320,11 @@
             throw new RuntimeException("Could not get sha256 hash object");
         }
         if (version >= 1) {
-            // Hash the source URI, destination URI, and the app intent
+            // Hash the source URI and the app intent
             digest.update(sourceUri.toString().getBytes(StandardCharsets.UTF_8));
-            digest.update(serializedResultIntentForApp.getBytes(StandardCharsets.UTF_8));
+            if (serializedResultIntentForApp != null) {
+                digest.update(serializedResultIntentForApp.getBytes(StandardCharsets.UTF_8));
+            }
         }
         // Add updates for future versions here
         return Base64.encodeToString(digest.digest(), Base64.URL_SAFE | Base64.NO_WRAP);
diff --git a/android/telephony/mbms/MbmsDownloadReceiver.java b/android/telephony/mbms/MbmsDownloadReceiver.java
index fe27537..9af1eb9 100644
--- a/android/telephony/mbms/MbmsDownloadReceiver.java
+++ b/android/telephony/mbms/MbmsDownloadReceiver.java
@@ -287,7 +287,7 @@
             return;
         }
 
-        List<Uri> tempFiles = intent.getParcelableExtra(VendorUtils.EXTRA_TEMP_LIST);
+        List<Uri> tempFiles = intent.getParcelableArrayListExtra(VendorUtils.EXTRA_TEMP_LIST);
         if (tempFiles == null) {
             return;
         }
@@ -309,7 +309,7 @@
             return;
         }
         int fdCount = intent.getIntExtra(VendorUtils.EXTRA_FD_COUNT, 0);
-        List<Uri> pausedList = intent.getParcelableExtra(VendorUtils.EXTRA_PAUSED_LIST);
+        List<Uri> pausedList = intent.getParcelableArrayListExtra(VendorUtils.EXTRA_PAUSED_LIST);
 
         if (fdCount == 0 && (pausedList == null || pausedList.size() == 0)) {
             Log.i(LOG_TAG, "No temp files actually requested. Ending.");
@@ -492,9 +492,14 @@
         } catch (PackageManager.NameNotFoundException e) {
             throw new RuntimeException("Package manager couldn't find " + context.getPackageName());
         }
+        if (appInfo.metaData == null) {
+            throw new RuntimeException("App must declare the file provider authority as metadata " +
+                    "in the manifest.");
+        }
         String authority = appInfo.metaData.getString(MBMS_FILE_PROVIDER_META_DATA_KEY);
         if (authority == null) {
-            throw new RuntimeException("Must declare the file provider authority as meta data");
+            throw new RuntimeException("App must declare the file provider authority as metadata " +
+                    "in the manifest.");
         }
         return authority;
     }
diff --git a/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java b/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java
index 2f85a1d..9ccdd56 100644
--- a/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java
+++ b/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java
@@ -113,15 +113,13 @@
     @Override
     public final int initialize(final int subscriptionId,
             final IMbmsDownloadSessionCallback callback) throws RemoteException {
-        final int uid = Binder.getCallingUid();
-        callback.asBinder().linkToDeath(new DeathRecipient() {
-            @Override
-            public void binderDied() {
-                onAppCallbackDied(uid, subscriptionId);
-            }
-        }, 0);
+        if (callback == null) {
+            throw new NullPointerException("Callback must not be null");
+        }
 
-        return initialize(subscriptionId, new MbmsDownloadSessionCallback() {
+        final int uid = Binder.getCallingUid();
+
+        int result = initialize(subscriptionId, new MbmsDownloadSessionCallback() {
             @Override
             public void onError(int errorCode, String message) {
                 try {
@@ -149,6 +147,17 @@
                 }
             }
         });
+
+        if (result == MbmsErrors.SUCCESS) {
+            callback.asBinder().linkToDeath(new DeathRecipient() {
+                @Override
+                public void binderDied() {
+                    onAppCallbackDied(uid, subscriptionId);
+                }
+            }, 0);
+        }
+
+        return result;
     }
 
     /**
@@ -240,16 +249,12 @@
     public final int registerStateCallback(final DownloadRequest downloadRequest,
             final IDownloadStateCallback callback, int flags) throws RemoteException {
         final int uid = Binder.getCallingUid();
-        DeathRecipient deathRecipient = new DeathRecipient() {
-            @Override
-            public void binderDied() {
-                onAppCallbackDied(uid, downloadRequest.getSubscriptionId());
-                mDownloadCallbackBinderMap.remove(callback.asBinder());
-                mDownloadCallbackDeathRecipients.remove(callback.asBinder());
-            }
-        };
-        mDownloadCallbackDeathRecipients.put(callback.asBinder(), deathRecipient);
-        callback.asBinder().linkToDeath(deathRecipient, 0);
+        if (downloadRequest == null) {
+            throw new NullPointerException("Download request must not be null");
+        }
+        if (callback == null) {
+            throw new NullPointerException("Callback must not be null");
+        }
 
         DownloadStateCallback exposedCallback = new FilteredDownloadStateCallback(callback, flags) {
             @Override
@@ -258,9 +263,23 @@
             }
         };
 
-        mDownloadCallbackBinderMap.put(callback.asBinder(), exposedCallback);
+        int result = registerStateCallback(downloadRequest, exposedCallback);
 
-        return registerStateCallback(downloadRequest, exposedCallback);
+        if (result == MbmsErrors.SUCCESS) {
+            DeathRecipient deathRecipient = new DeathRecipient() {
+                @Override
+                public void binderDied() {
+                    onAppCallbackDied(uid, downloadRequest.getSubscriptionId());
+                    mDownloadCallbackBinderMap.remove(callback.asBinder());
+                    mDownloadCallbackDeathRecipients.remove(callback.asBinder());
+                }
+            };
+            mDownloadCallbackDeathRecipients.put(callback.asBinder(), deathRecipient);
+            callback.asBinder().linkToDeath(deathRecipient, 0);
+            mDownloadCallbackBinderMap.put(callback.asBinder(), exposedCallback);
+        }
+
+        return result;
     }
 
     /**
@@ -292,6 +311,13 @@
     public final int unregisterStateCallback(
             final DownloadRequest downloadRequest, final IDownloadStateCallback callback)
             throws RemoteException {
+        if (downloadRequest == null) {
+            throw new NullPointerException("Download request must not be null");
+        }
+        if (callback == null) {
+            throw new NullPointerException("Callback must not be null");
+        }
+
         DeathRecipient deathRecipient =
                 mDownloadCallbackDeathRecipients.remove(callback.asBinder());
         if (deathRecipient == null) {
diff --git a/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java b/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java
index f8f370a..a238153 100644
--- a/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java
+++ b/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java
@@ -65,15 +65,13 @@
     @Override
     public final int initialize(final IMbmsStreamingSessionCallback callback,
             final int subscriptionId) throws RemoteException {
-        final int uid = Binder.getCallingUid();
-        callback.asBinder().linkToDeath(new DeathRecipient() {
-            @Override
-            public void binderDied() {
-                onAppCallbackDied(uid, subscriptionId);
-            }
-        }, 0);
+        if (callback == null) {
+            throw new NullPointerException("Callback must not be null");
+        }
 
-        return initialize(new MbmsStreamingSessionCallback() {
+        final int uid = Binder.getCallingUid();
+
+        int result = initialize(new MbmsStreamingSessionCallback() {
             @Override
             public void onError(final int errorCode, final String message) {
                 try {
@@ -101,6 +99,17 @@
                 }
             }
         }, subscriptionId);
+
+        if (result == MbmsErrors.SUCCESS) {
+            callback.asBinder().linkToDeath(new DeathRecipient() {
+                @Override
+                public void binderDied() {
+                    onAppCallbackDied(uid, subscriptionId);
+                }
+            }, 0);
+        }
+
+        return result;
     }
 
 
@@ -152,15 +161,13 @@
     @Override
     public int startStreaming(final int subscriptionId, String serviceId,
             final IStreamingServiceCallback callback) throws RemoteException {
-        final int uid = Binder.getCallingUid();
-        callback.asBinder().linkToDeath(new DeathRecipient() {
-            @Override
-            public void binderDied() {
-                onAppCallbackDied(uid, subscriptionId);
-            }
-        }, 0);
+        if (callback == null) {
+            throw new NullPointerException("Callback must not be null");
+        }
 
-        return startStreaming(subscriptionId, serviceId, new StreamingServiceCallback() {
+        final int uid = Binder.getCallingUid();
+
+        int result = startStreaming(subscriptionId, serviceId, new StreamingServiceCallback() {
             @Override
             public void onError(final int errorCode, final String message) {
                 try {
@@ -207,6 +214,17 @@
                 }
             }
         });
+
+        if (result == MbmsErrors.SUCCESS) {
+            callback.asBinder().linkToDeath(new DeathRecipient() {
+                @Override
+                public void binderDied() {
+                    onAppCallbackDied(uid, subscriptionId);
+                }
+            }, 0);
+        }
+
+        return result;
     }
 
     /**
diff --git a/android/text/AndroidBidi.java b/android/text/AndroidBidi.java
index bbe1523..179d545 100644
--- a/android/text/AndroidBidi.java
+++ b/android/text/AndroidBidi.java
@@ -16,6 +16,11 @@
 
 package android.text;
 
+import android.icu.lang.UCharacter;
+import android.icu.lang.UCharacterDirection;
+import android.icu.lang.UProperty;
+import android.icu.text.Bidi;
+import android.icu.text.BidiClassifier;
 import android.text.Layout.Directions;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -27,26 +32,57 @@
 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
 public class AndroidBidi {
 
-    public static int bidi(int dir, char[] chs, byte[] chInfo, int n, boolean haveInfo) {
+    private static class EmojiBidiOverride extends BidiClassifier {
+        EmojiBidiOverride() {
+            super(null /* No persisting object needed */);
+        }
+
+        // Tells ICU to use the standard Unicode value.
+        private static final int NO_OVERRIDE =
+                UCharacter.getIntPropertyMaxValue(UProperty.BIDI_CLASS) + 1;
+
+        @Override
+        public int classify(int c) {
+            if (Emoji.isNewEmoji(c)) {
+                // All new emoji characters in Unicode 10.0 are of the bidi class ON.
+                return UCharacterDirection.OTHER_NEUTRAL;
+            } else {
+                return NO_OVERRIDE;
+            }
+        }
+    }
+
+    private static final EmojiBidiOverride sEmojiBidiOverride = new EmojiBidiOverride();
+
+    /**
+     * Runs the bidi algorithm on input text.
+     */
+    public static int bidi(int dir, char[] chs, byte[] chInfo) {
         if (chs == null || chInfo == null) {
             throw new NullPointerException();
         }
 
-        if (n < 0 || chs.length < n || chInfo.length < n) {
+        final int length = chs.length;
+        if (chInfo.length < length) {
             throw new IndexOutOfBoundsException();
         }
 
-        switch(dir) {
-            case Layout.DIR_REQUEST_LTR: dir = 0; break;
-            case Layout.DIR_REQUEST_RTL: dir = 1; break;
-            case Layout.DIR_REQUEST_DEFAULT_LTR: dir = -2; break;
-            case Layout.DIR_REQUEST_DEFAULT_RTL: dir = -1; break;
-            default: dir = 0; break;
+        final byte paraLevel;
+        switch (dir) {
+            case Layout.DIR_REQUEST_LTR: paraLevel = Bidi.LTR; break;
+            case Layout.DIR_REQUEST_RTL: paraLevel = Bidi.RTL; break;
+            case Layout.DIR_REQUEST_DEFAULT_LTR: paraLevel = Bidi.LEVEL_DEFAULT_LTR; break;
+            case Layout.DIR_REQUEST_DEFAULT_RTL: paraLevel = Bidi.LEVEL_DEFAULT_RTL; break;
+            default: paraLevel = Bidi.LTR; break;
         }
-
-        int result = runBidi(dir, chs, chInfo, n, haveInfo);
-        result = (result & 0x1) == 0 ? Layout.DIR_LEFT_TO_RIGHT : Layout.DIR_RIGHT_TO_LEFT;
-        return result;
+        final Bidi icuBidi = new Bidi(length /* maxLength */, 0 /* maxRunCount */);
+        icuBidi.setCustomClassifier(sEmojiBidiOverride);
+        icuBidi.setPara(chs, paraLevel, null /* embeddingLevels */);
+        for (int i = 0; i < length; i++) {
+            chInfo[i] = icuBidi.getLevelAt(i);
+        }
+        final byte result = icuBidi.getParaLevel();
+        return (result & 0x1) == 0 ? Layout.DIR_LEFT_TO_RIGHT : Layout.DIR_RIGHT_TO_LEFT;
     }
 
     /**
@@ -178,6 +214,4 @@
         }
         return new Directions(ld);
     }
-
-    private native static int runBidi(int dir, char[] chs, byte[] chInfo, int n, boolean haveInfo);
 }
\ No newline at end of file
diff --git a/android/text/AndroidBidi_Delegate.java b/android/text/AndroidBidi_Delegate.java
deleted file mode 100644
index 38171dc..0000000
--- a/android/text/AndroidBidi_Delegate.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2011 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.text;
-
-import com.android.ide.common.rendering.api.LayoutLog;
-import com.android.layoutlib.bridge.Bridge;
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-import android.icu.text.Bidi;
-
-/**
- * Delegate used to provide new implementation for the native methods of {@link AndroidBidi}
- *
- * Through the layoutlib_create tool, the original  methods of AndroidBidi have been replaced
- * by calls to methods of the same name in this delegate class.
- *
- */
-public class AndroidBidi_Delegate {
-
-    @LayoutlibDelegate
-    /*package*/ static int runBidi(int dir, char[] chars, byte[] charInfo, int count,
-            boolean haveInfo) {
-
-        switch (dir) {
-        case 0: // Layout.DIR_REQUEST_LTR
-            dir = Bidi.LTR;
-            break;
-        case 1: // Layout.DIR_REQUEST_RTL
-            dir = Bidi.RTL;
-            break;
-        case -1: // Layout.DIR_REQUEST_DEFAULT_RTL
-            dir = Bidi.LEVEL_DEFAULT_RTL;
-            break;
-        case -2: // Layout.DIR_REQUEST_DEFAULT_LTR
-            dir = Bidi.LEVEL_DEFAULT_LTR;
-            break;
-        default:
-            // Invalid code. Log error, assume LEVEL_DEFAULT_LTR and continue.
-            Bridge.getLog().error(LayoutLog.TAG_BROKEN, "Invalid direction flag", null);
-            dir = Bidi.LEVEL_DEFAULT_LTR;
-        }
-        Bidi bidi = new Bidi(chars, 0, null, 0, count, dir);
-        if (charInfo != null) {
-            for (int i = 0; i < count; ++i)
-            charInfo[i] = bidi.getLevelAt(i);
-        }
-        return bidi.getParaLevel();
-    }
-}
diff --git a/android/text/BoringLayoutCreateDrawPerfTest.java b/android/text/BoringLayoutCreateDrawPerfTest.java
index 47dd257..586c385 100644
--- a/android/text/BoringLayoutCreateDrawPerfTest.java
+++ b/android/text/BoringLayoutCreateDrawPerfTest.java
@@ -46,7 +46,7 @@
     private static final float SPACING_ADD = 10f;
     private static final float SPACING_MULT = 1.5f;
 
-    @Parameterized.Parameters(name = "cached={3},{1} chars,{0}")
+    @Parameterized.Parameters(name = "cached={3},{1}chars,{0}")
     public static Collection cases() {
         final List<Object[]> params = new ArrayList<>();
         for (int length : new int[]{128}) {
diff --git a/android/text/BoringLayoutIsBoringPerfTest.java b/android/text/BoringLayoutIsBoringPerfTest.java
index 34de65d..9d11f29 100644
--- a/android/text/BoringLayoutIsBoringPerfTest.java
+++ b/android/text/BoringLayoutIsBoringPerfTest.java
@@ -40,7 +40,7 @@
 
     private static final boolean[] BOOLEANS = new boolean[]{false, true};
 
-    @Parameterized.Parameters(name = "cached={4},{1} chars,{0}")
+    @Parameterized.Parameters(name = "cached={4},{1}chars,{0}")
     public static Collection cases() {
         final List<Object[]> params = new ArrayList<>();
         for (int length : new int[]{128}) {
diff --git a/android/text/DynamicLayout.java b/android/text/DynamicLayout.java
index 24260c4..fba358c 100644
--- a/android/text/DynamicLayout.java
+++ b/android/text/DynamicLayout.java
@@ -299,7 +299,7 @@
 
         private final Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt();
 
-        private static final SynchronizedPool<Builder> sPool = new SynchronizedPool<Builder>(3);
+        private static final SynchronizedPool<Builder> sPool = new SynchronizedPool<>(3);
     }
 
     /**
@@ -440,7 +440,7 @@
             mEllipsizeAt = null;
         }
 
-        mObjects = new PackedObjectVector<Directions>(1);
+        mObjects = new PackedObjectVector<>(1);
 
         // Initial state is a single line with 0 characters (0 to 0), with top at 0 and bottom at
         // whatever is natural, and undefined ellipsis.
@@ -1050,7 +1050,7 @@
 
     private static class ChangeWatcher implements TextWatcher, SpanWatcher {
         public ChangeWatcher(DynamicLayout layout) {
-            mLayout = new WeakReference<DynamicLayout>(layout);
+            mLayout = new WeakReference<>(layout);
         }
 
         private void reflow(CharSequence s, int where, int before, int after) {
diff --git a/android/text/Hyphenator.java b/android/text/Hyphenator.java
index ad26f23..4f1488e 100644
--- a/android/text/Hyphenator.java
+++ b/android/text/Hyphenator.java
@@ -16,258 +16,15 @@
 
 package android.text;
 
-import android.annotation.IntRange;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.system.ErrnoException;
-import android.system.Os;
-import android.system.OsConstants;
-import android.util.Log;
-
-import com.android.internal.annotations.GuardedBy;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.RandomAccessFile;
-import java.util.HashMap;
-import java.util.Locale;
-
 /**
- * Hyphenator is a wrapper class for a native implementation of automatic hyphenation,
+ * Hyphenator just initializes the native implementation of automatic hyphenation,
  * in essence finding valid hyphenation opportunities in a word.
  *
  * @hide
  */
 public class Hyphenator {
-    private static String TAG = "Hyphenator";
-
-    private final static Object sLock = new Object();
-
-    @GuardedBy("sLock")
-    final static HashMap<Locale, Hyphenator> sMap = new HashMap<Locale, Hyphenator>();
-
-    private final long mNativePtr;
-    private final HyphenationData mData;
-
-    private Hyphenator(long nativePtr, HyphenationData data) {
-        mNativePtr = nativePtr;
-        mData = data;
-    }
-
-    public long getNativePtr() {
-        return mNativePtr;
-    }
-
-    public static Hyphenator get(@Nullable Locale locale) {
-        synchronized (sLock) {
-            Hyphenator result = sMap.get(locale);
-            if (result != null) {
-                return result;
-            }
-
-            // If there's a variant, fall back to language+variant only, if available
-            final String variant = locale.getVariant();
-            if (!variant.isEmpty()) {
-                final Locale languageAndVariantOnlyLocale =
-                        new Locale(locale.getLanguage(), "", variant);
-                result = sMap.get(languageAndVariantOnlyLocale);
-                if (result != null) {
-                    return putAlias(locale, result);
-                }
-            }
-
-            // Fall back to language-only, if available
-            final Locale languageOnlyLocale = new Locale(locale.getLanguage());
-            result = sMap.get(languageOnlyLocale);
-            if (result != null) {
-                return putAlias(locale, result);
-            }
-
-            // Fall back to script-only, if available
-            final String script = locale.getScript();
-            if (!script.equals("")) {
-                final Locale scriptOnlyLocale = new Locale.Builder()
-                        .setLanguage("und")
-                        .setScript(script)
-                        .build();
-                result = sMap.get(scriptOnlyLocale);
-                if (result != null) {
-                    return putAlias(locale, result);
-                }
-            }
-
-            return putEmptyAlias(locale);
-        }
-    }
-
-    private static class HyphenationData {
-        private static final String SYSTEM_HYPHENATOR_LOCATION = "/system/usr/hyphen-data";
-
-        public final int mMinPrefix, mMinSuffix;
-        public final long mDataAddress;
-
-        // Reasonable enough values for cases where we have no hyphenation patterns but may be able
-        // to do some automatic hyphenation based on characters. These values would be used very
-        // rarely.
-        private static final int DEFAULT_MIN_PREFIX = 2;
-        private static final int DEFAULT_MIN_SUFFIX = 2;
-
-        public static final HyphenationData sEmptyData =
-                new HyphenationData(DEFAULT_MIN_PREFIX, DEFAULT_MIN_SUFFIX);
-
-        // Create empty HyphenationData.
-        private HyphenationData(int minPrefix, int minSuffix) {
-            mMinPrefix = minPrefix;
-            mMinSuffix = minSuffix;
-            mDataAddress = 0;
-        }
-
-        HyphenationData(String languageTag, int minPrefix, int minSuffix) {
-            mMinPrefix = minPrefix;
-            mMinSuffix = minSuffix;
-
-            final String patternFilename = "hyph-" + languageTag.toLowerCase(Locale.US) + ".hyb";
-            final File patternFile = new File(SYSTEM_HYPHENATOR_LOCATION, patternFilename);
-            if (!patternFile.canRead()) {
-                Log.e(TAG, "hyphenation patterns for " + patternFile + " not found or unreadable");
-                mDataAddress = 0;
-            } else {
-                long address;
-                try (RandomAccessFile f = new RandomAccessFile(patternFile, "r")) {
-                    address = Os.mmap(0, f.length(), OsConstants.PROT_READ,
-                            OsConstants.MAP_SHARED, f.getFD(), 0 /* offset */);
-                } catch (IOException | ErrnoException e) {
-                    Log.e(TAG, "error loading hyphenation " + patternFile, e);
-                    address = 0;
-                }
-                mDataAddress = address;
-            }
-        }
-    }
-
-    // Do not call this method outside of init method.
-    private static Hyphenator putNewHyphenator(Locale loc, HyphenationData data) {
-        final Hyphenator hyphenator = new Hyphenator(nBuildHyphenator(
-                data.mDataAddress, loc.getLanguage(), data.mMinPrefix, data.mMinSuffix), data);
-        sMap.put(loc, hyphenator);
-        return hyphenator;
-    }
-
-    // Do not call this method outside of init method.
-    private static void loadData(String langTag, int minPrefix, int maxPrefix) {
-        final HyphenationData data = new HyphenationData(langTag, minPrefix, maxPrefix);
-        putNewHyphenator(Locale.forLanguageTag(langTag), data);
-    }
-
-    // Caller must acquire sLock before calling this method.
-    // The Hyphenator for the baseLangTag must exists.
-    private static Hyphenator addAliasByTag(String langTag, String baseLangTag) {
-        return putAlias(Locale.forLanguageTag(langTag),
-                sMap.get(Locale.forLanguageTag(baseLangTag)));
-    }
-
-    // Caller must acquire sLock before calling this method.
-    private static Hyphenator putAlias(Locale locale, Hyphenator base) {
-        return putNewHyphenator(locale, base.mData);
-    }
-
-    // Caller must acquire sLock before calling this method.
-    private static Hyphenator putEmptyAlias(Locale locale) {
-        return putNewHyphenator(locale, HyphenationData.sEmptyData);
-    }
-
-    // TODO: Confirm that these are the best values. Various sources suggest (1, 1), but
-    // that appears too small.
-    private static final int INDIC_MIN_PREFIX = 2;
-    private static final int INDIC_MIN_SUFFIX = 2;
-
-    /**
-     * Load hyphenation patterns at initialization time. We want to have patterns
-     * for all locales loaded and ready to use so we don't have to do any file IO
-     * on the UI thread when drawing text in different locales.
-     *
-     * @hide
-     */
     public static void init() {
-        synchronized (sLock) {
-            sMap.put(null, null);
-
-            loadData("as", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Assamese
-            loadData("bg", 2, 2); // Bulgarian
-            loadData("bn", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Bengali
-            loadData("cu", 1, 2); // Church Slavonic
-            loadData("cy", 2, 3); // Welsh
-            loadData("da", 2, 2); // Danish
-            loadData("de-1901", 2, 2); // German 1901 orthography
-            loadData("de-1996", 2, 2); // German 1996 orthography
-            loadData("de-CH-1901", 2, 2); // Swiss High German 1901 orthography
-            loadData("en-GB", 2, 3); // British English
-            loadData("en-US", 2, 3); // American English
-            loadData("es", 2, 2); // Spanish
-            loadData("et", 2, 3); // Estonian
-            loadData("eu", 2, 2); // Basque
-            loadData("fr", 2, 3); // French
-            loadData("ga", 2, 3); // Irish
-            loadData("gu", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Gujarati
-            loadData("hi", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Hindi
-            loadData("hr", 2, 2); // Croatian
-            loadData("hu", 2, 2); // Hungarian
-            // texhyphen sources say Armenian may be (1, 2); but that it needs confirmation.
-            // Going with a more conservative value of (2, 2) for now.
-            loadData("hy", 2, 2); // Armenian
-            loadData("kn", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Kannada
-            loadData("ml", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Malayalam
-            loadData("mn-Cyrl", 2, 2); // Mongolian in Cyrillic script
-            loadData("mr", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Marathi
-            loadData("nb", 2, 2); // Norwegian Bokmål
-            loadData("nn", 2, 2); // Norwegian Nynorsk
-            loadData("or", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Oriya
-            loadData("pa", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Punjabi
-            loadData("pt", 2, 3); // Portuguese
-            loadData("sl", 2, 2); // Slovenian
-            loadData("ta", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Tamil
-            loadData("te", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Telugu
-            loadData("tk", 2, 2); // Turkmen
-            loadData("und-Ethi", 1, 1); // Any language in Ethiopic script
-
-            // English locales that fall back to en-US. The data is
-            // from CLDR. It's all English locales, minus the locales whose
-            // parent is en-001 (from supplementalData.xml, under <parentLocales>).
-            // TODO: Figure out how to get this from ICU.
-            addAliasByTag("en-AS", "en-US"); // English (American Samoa)
-            addAliasByTag("en-GU", "en-US"); // English (Guam)
-            addAliasByTag("en-MH", "en-US"); // English (Marshall Islands)
-            addAliasByTag("en-MP", "en-US"); // English (Northern Mariana Islands)
-            addAliasByTag("en-PR", "en-US"); // English (Puerto Rico)
-            addAliasByTag("en-UM", "en-US"); // English (United States Minor Outlying Islands)
-            addAliasByTag("en-VI", "en-US"); // English (Virgin Islands)
-
-            // All English locales other than those falling back to en-US are mapped to en-GB.
-            addAliasByTag("en", "en-GB");
-
-            // For German, we're assuming the 1996 (and later) orthography by default.
-            addAliasByTag("de", "de-1996");
-            // Liechtenstein uses the Swiss hyphenation rules for the 1901 orthography.
-            addAliasByTag("de-LI-1901", "de-CH-1901");
-
-            // Norwegian is very probably Norwegian Bokmål.
-            addAliasByTag("no", "nb");
-
-            // Use mn-Cyrl. According to CLDR's likelySubtags.xml, mn is most likely to be mn-Cyrl.
-            addAliasByTag("mn", "mn-Cyrl"); // Mongolian
-
-            // Fall back to Ethiopic script for languages likely to be written in Ethiopic.
-            // Data is from CLDR's likelySubtags.xml.
-            // TODO: Convert this to a mechanism using ICU4J's ULocale#addLikelySubtags().
-            addAliasByTag("am", "und-Ethi"); // Amharic
-            addAliasByTag("byn", "und-Ethi"); // Blin
-            addAliasByTag("gez", "und-Ethi"); // Geʻez
-            addAliasByTag("ti", "und-Ethi"); // Tigrinya
-            addAliasByTag("wal", "und-Ethi"); // Wolaytta
-        }
-    };
-
-    private static native long nBuildHyphenator(/* non-zero */ long dataAddress,
-            @NonNull String langTag, @IntRange(from = 1) int minPrefix,
-            @IntRange(from = 1) int minSuffix);
+        nInit();
+    }
+    private static native void nInit();
 }
diff --git a/android/text/Layout.java b/android/text/Layout.java
index 60fff73..ac5c2e9 100644
--- a/android/text/Layout.java
+++ b/android/text/Layout.java
@@ -319,8 +319,6 @@
 
     private float getJustifyWidth(int lineNum) {
         Alignment paraAlign = mAlignment;
-        TabStops tabStops = null;
-        boolean tabStopsIsInitialized = false;
 
         int left = 0;
         int right = mWidth;
@@ -371,10 +369,6 @@
             }
         }
 
-        if (getLineContainsTab(lineNum)) {
-            tabStops = new TabStops(TAB_INCREMENT, spans);
-        }
-
         final Alignment align;
         if (paraAlign == Alignment.ALIGN_LEFT) {
             align = (dir == DIR_LEFT_TO_RIGHT) ?  Alignment.ALIGN_NORMAL : Alignment.ALIGN_OPPOSITE;
@@ -1423,7 +1417,6 @@
         float dist = Math.abs(getHorizontal(max, primary) - horiz);
 
         if (dist <= bestdist) {
-            bestdist = dist;
             best = max;
         }
 
@@ -1570,7 +1563,7 @@
         // XXX: we don't care about tabs
         tl.set(mPaint, mText, lineStart, lineEnd, lineDir, directions, false, null);
         caret = lineStart + tl.getOffsetToLeftRightOf(caret - lineStart, toLeft);
-        tl = TextLine.recycle(tl);
+        TextLine.recycle(tl);
         return caret;
     }
 
@@ -1894,10 +1887,7 @@
 
         int margin = 0;
 
-        boolean isFirstParaLine = lineStart == 0 ||
-            spanned.charAt(lineStart - 1) == '\n';
-
-        boolean useFirstLineMargin = isFirstParaLine;
+        boolean useFirstLineMargin = lineStart == 0 || spanned.charAt(lineStart - 1) == '\n';
         for (int i = 0; i < spans.length; i++) {
             if (spans[i] instanceof LeadingMarginSpan2) {
                 int spStart = spanned.getSpanStart(spans[i]);
diff --git a/android/text/MeasuredText.java b/android/text/MeasuredText.java
index b09ccc2..ffc44a7 100644
--- a/android/text/MeasuredText.java
+++ b/android/text/MeasuredText.java
@@ -106,8 +106,8 @@
         if (mWidths == null || mWidths.length < len) {
             mWidths = ArrayUtils.newUnpaddedFloatArray(len);
         }
-        if (mChars == null || mChars.length < len) {
-            mChars = ArrayUtils.newUnpaddedCharArray(len);
+        if (mChars == null || mChars.length != len) {
+            mChars = new char[len];
         }
         TextUtils.getChars(text, start, end, mChars, 0);
 
@@ -151,7 +151,7 @@
                 boolean isRtl = textDir.isRtl(mChars, 0, len);
                 bidiRequest = isRtl ? Layout.DIR_REQUEST_RTL : Layout.DIR_REQUEST_LTR;
             }
-            mDir = AndroidBidi.bidi(bidiRequest, mChars, mLevels, len, false);
+            mDir = AndroidBidi.bidi(bidiRequest, mChars, mLevels);
             mEasy = false;
         }
     }
diff --git a/android/text/PaintMeasureDrawPerfTest.java b/android/text/PaintMeasureDrawPerfTest.java
index 00b60ad..6768798 100644
--- a/android/text/PaintMeasureDrawPerfTest.java
+++ b/android/text/PaintMeasureDrawPerfTest.java
@@ -42,7 +42,7 @@
 
     private static final boolean[] BOOLEANS = new boolean[]{false, true};
 
-    @Parameterized.Parameters(name = "cached={1},{0} chars")
+    @Parameterized.Parameters(name = "cached={1},{0}chars")
     public static Collection cases() {
         final List<Object[]> params = new ArrayList<>();
         for (int length : new int[]{128}) {
diff --git a/android/text/StaticLayout.java b/android/text/StaticLayout.java
index 961cd8e..5c60188 100644
--- a/android/text/StaticLayout.java
+++ b/android/text/StaticLayout.java
@@ -21,21 +21,18 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.graphics.Paint;
-import android.os.LocaleList;
 import android.text.style.LeadingMarginSpan;
 import android.text.style.LeadingMarginSpan.LeadingMarginSpan2;
 import android.text.style.LineHeightSpan;
 import android.text.style.MetricAffectingSpan;
 import android.text.style.TabStopSpan;
 import android.util.Log;
-import android.util.Pair;
 import android.util.Pools.SynchronizedPool;
 
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.GrowingArrayUtils;
 
 import java.util.Arrays;
-import java.util.Locale;
 
 /**
  * StaticLayout is a Layout for text that will not be edited after it
@@ -101,7 +98,6 @@
             b.mBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE;
             b.mHyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE;
             b.mJustificationMode = Layout.JUSTIFICATION_MODE_NONE;
-            b.mLocales = null;
 
             b.mMeasuredText = MeasuredText.obtain();
             return b;
@@ -118,7 +114,6 @@
             b.mMeasuredText = null;
             b.mLeftIndents = null;
             b.mRightIndents = null;
-            b.mLocales = null;
             b.mLeftPaddings = null;
             b.mRightPaddings = null;
             nFinishBuilder(b.mNativePtr);
@@ -409,17 +404,6 @@
             return this;
         }
 
-        @NonNull
-        private long[] getHyphenators(@NonNull LocaleList locales) {
-            final int length = locales.size();
-            final long[] result = new long[length];
-            for (int i = 0; i < length; i++) {
-                final Locale locale = locales.get(i);
-                result[i] = Hyphenator.get(locale).getNativePtr();
-            }
-            return result;
-        }
-
         /**
          * Measurement and break iteration is done in native code. The protocol for using
          * the native code is as follows.
@@ -433,35 +417,17 @@
          *    + addStyleRun (a text run, to be measured in native code)
          *    + addReplacementRun (a replacement run, width is given)
          *
-         * After measurement, nGetWidths() is valid if the widths are needed (eg for ellipsis).
          * Run nComputeLineBreaks() to obtain line breaks for the paragraph.
          *
          * After all paragraphs, call finish() to release expensive buffers.
          */
 
-        private Pair<String, long[]> getLocaleAndHyphenatorIfChanged(TextPaint paint) {
-            final LocaleList locales = paint.getTextLocales();
-            final String languageTags;
-            long[] hyphenators;
-            if (!locales.equals(mLocales)) {
-                mLocales = locales;
-                return new Pair(locales.toLanguageTags(), getHyphenators(locales));
-            } else {
-                // passing null means keep current locale.
-                // TODO: move locale change detection to native.
-                return new Pair(null, null);
-            }
-        }
-
         /* package */ void addStyleRun(TextPaint paint, int start, int end, boolean isRtl) {
-            Pair<String, long[]> locHyph = getLocaleAndHyphenatorIfChanged(paint);
-            nAddStyleRun(mNativePtr, paint.getNativeInstance(), start, end, isRtl, locHyph.first,
-                    locHyph.second);
+            nAddStyleRun(mNativePtr, paint.getNativeInstance(), start, end, isRtl);
         }
 
         /* package */ void addReplacementRun(TextPaint paint, int start, int end, float width) {
-            Pair<String, long[]> locHyph = getLocaleAndHyphenatorIfChanged(paint);
-            nAddReplacementRun(mNativePtr, start, end, width, locHyph.first, locHyph.second);
+            nAddReplacementRun(mNativePtr, paint.getNativeInstance(), start, end, width);
         }
 
         /**
@@ -519,9 +485,7 @@
         // This will go away and be subsumed by native builder code
         private MeasuredText mMeasuredText;
 
-        private LocaleList mLocales;
-
-        private static final SynchronizedPool<Builder> sPool = new SynchronizedPool<Builder>(3);
+        private static final SynchronizedPool<Builder> sPool = new SynchronizedPool<>(3);
     }
 
     public StaticLayout(CharSequence source, TextPaint paint,
@@ -810,9 +774,6 @@
                 }
             }
 
-            // TODO: Move locale tracking code to native.
-            b.mLocales = null;  // Reset the locale tracking.
-
             nSetupParagraph(b.mNativePtr, chs, paraEnd - paraStart,
                     firstWidth, firstWidthLineCount, restWidth,
                     variableTabStops, TAB_INCREMENT, b.mBreakStrategy, b.mHyphenationFrequency,
@@ -866,10 +827,9 @@
                 spanEndCacheCount++;
             }
 
-            nGetWidths(b.mNativePtr, widths);
             int breakCount = nComputeLineBreaks(b.mNativePtr, lineBreaks, lineBreaks.breaks,
                     lineBreaks.widths, lineBreaks.ascents, lineBreaks.descents, lineBreaks.flags,
-                    lineBreaks.breaks.length);
+                    lineBreaks.breaks.length, widths);
 
             final int[] breaks = lineBreaks.breaks;
             final float[] lineWidths = lineBreaks.widths;
@@ -947,10 +907,10 @@
                     boolean moreChars = (endPos < bufEnd);
 
                     final int ascent = fallbackLineSpacing
-                            ? Math.min(fmAscent, (int) Math.round(ascents[breakIndex]))
+                            ? Math.min(fmAscent, Math.round(ascents[breakIndex]))
                             : fmAscent;
                     final int descent = fallbackLineSpacing
-                            ? Math.max(fmDescent, (int) Math.round(descents[breakIndex]))
+                            ? Math.max(fmDescent, Math.round(descents[breakIndex]))
                             : fmDescent;
                     v = out(source, here, endPos,
                             ascent, descent, fmTop, fmBottom,
@@ -1177,7 +1137,7 @@
         mWorkPaint.set(paint);
         do {
             final float ellipsizedWidth = guessEllipsis(text, lineStart, lineEnd, widths,
-                    widthStart, tempAvail, where, line, textWidth, mWorkPaint, forceEllipsis, dir);
+                    widthStart, tempAvail, where, line, mWorkPaint, forceEllipsis, dir);
             if (ellipsizedWidth <= avail) {
                 lineFits = true;
             } else {
@@ -1207,7 +1167,7 @@
     // This method temporarily modifies the TextPaint passed to it, so the TextPaint passed to it
     // should not be accessed while the method is running.
     private float guessEllipsis(CharSequence text, int lineStart, int lineEnd, float[] widths,
-            int widthStart, float avail, TextUtils.TruncateAt where, int line, float textWidth,
+            int widthStart, float avail, TextUtils.TruncateAt where, int line,
             TextPaint paint, boolean forceEllipsis, int dir) {
         final int savedHyphenEdit = paint.getHyphenEdit();
         paint.setHyphenEdit(0);
@@ -1541,26 +1501,28 @@
             @Nullable int[] indents, @Nullable int[] leftPaddings, @Nullable int[] rightPaddings,
             @IntRange(from = 0) int indentsOffset);
 
+    // TODO: Make this method CriticalNative once native code defers doing layouts.
     private static native void nAddStyleRun(
             /* non-zero */ long nativePtr, /* non-zero */ long nativePaint,
-            @IntRange(from = 0) int start, @IntRange(from = 0) int end, boolean isRtl,
-            @Nullable String languageTags, @Nullable long[] hyphenators);
+            @IntRange(from = 0) int start, @IntRange(from = 0) int end, boolean isRtl);
 
-    private static native void nAddReplacementRun(/* non-zero */ long nativePtr,
+    // TODO: Make this method CriticalNative once native code defers doing layouts.
+    private static native void nAddReplacementRun(
+            /* non-zero */ long nativePtr, /* non-zero */ long nativePaint,
             @IntRange(from = 0) int start, @IntRange(from = 0) int end,
-            @FloatRange(from = 0.0f) float width, @Nullable String languageTags,
-            @Nullable long[] hyphenators);
-
-    private static native void nGetWidths(long nativePtr, float[] widths);
+            @FloatRange(from = 0.0f) float width);
 
     // populates LineBreaks and returns the number of breaks found
     //
     // the arrays inside the LineBreaks objects are passed in as well
     // to reduce the number of JNI calls in the common case where the
     // arrays do not have to be resized
+    // The individual character widths will be returned in charWidths. The length of charWidths must
+    // be at least the length of the text.
     private static native int nComputeLineBreaks(long nativePtr, LineBreaks recycle,
             int[] recycleBreaks, float[] recycleWidths, float[] recycleAscents,
-            float[] recycleDescents, int[] recycleFlags, int recycleLength);
+            float[] recycleDescents, int[] recycleFlags, int recycleLength,
+            float[] charWidths);
 
     private int mLineCount;
     private int mTopPadding, mBottomPadding;
diff --git a/android/text/StaticLayoutCreateDrawPerfTest.java b/android/text/StaticLayoutCreateDrawPerfTest.java
index 356e2e0..bfdb758 100644
--- a/android/text/StaticLayoutCreateDrawPerfTest.java
+++ b/android/text/StaticLayoutCreateDrawPerfTest.java
@@ -50,7 +50,7 @@
     @Rule
     public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
 
-    @Parameterized.Parameters(name = "cached={3},{1} chars,{0}")
+    @Parameterized.Parameters(name = "cached={3},{1}chars,{0}")
     public static Collection cases() {
         final List<Object[]> params = new ArrayList<>();
         for (int length : new int[]{128}) {
diff --git a/android/text/StaticLayout_Delegate.java b/android/text/StaticLayout_Delegate.java
index 63337f0..def3c91 100644
--- a/android/text/StaticLayout_Delegate.java
+++ b/android/text/StaticLayout_Delegate.java
@@ -13,7 +13,6 @@
 import android.text.Primitive.PrimitiveType;
 import android.text.StaticLayout.LineBreaks;
 
-import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -72,13 +71,11 @@
 
     @LayoutlibDelegate
     /*package*/ static void nAddStyleRun(long nativeBuilder, long nativePaint, int start,
-            int end, boolean isRtl, String languageTags, long[] hyphenators) {
+            int end, boolean isRtl) {
         Builder builder = sBuilderManager.getDelegate(nativeBuilder);
         if (builder == null) {
             return;
         }
-        builder.mLocales = languageTags;
-        builder.mNativeHyphenators = hyphenators;
 
         int bidiFlags = isRtl ? Paint.BIDI_FORCE_RTL : Paint.BIDI_FORCE_LTR;
         measureText(nativePaint, builder.mText, start, end - start, builder.mWidths,
@@ -86,30 +83,20 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static void nAddReplacementRun(long nativeBuilder, int start, int end, float width,
-            String languageTags, long[] hyphenators) {
+    /*package*/ static void nAddReplacementRun(long nativeBuilder, long nativePaint, int start,
+            int end, float width) {
         Builder builder = sBuilderManager.getDelegate(nativeBuilder);
         if (builder == null) {
             return;
         }
-        builder.mLocales = languageTags;
-        builder.mNativeHyphenators = hyphenators;
         builder.mWidths[start] = width;
         Arrays.fill(builder.mWidths, start + 1, end, 0.0f);
     }
 
     @LayoutlibDelegate
-    /*package*/ static void nGetWidths(long nativeBuilder, float[] floatsArray) {
-        Builder builder = sBuilderManager.getDelegate(nativeBuilder);
-        if (builder != null) {
-            System.arraycopy(builder.mWidths, 0, floatsArray, 0, builder.mWidths.length);
-        }
-    }
-
-    @LayoutlibDelegate
     /*package*/ static int nComputeLineBreaks(long nativeBuilder, LineBreaks recycle,
             int[] recycleBreaks, float[] recycleWidths, float[] recycleAscents,
-            float[] recycleDescents, int[] recycleFlags, int recycleLength) {
+            float[] recycleDescents, int[] recycleFlags, int recycleLength, float[] charWidths) {
 
         Builder builder = sBuilderManager.getDelegate(nativeBuilder);
         if (builder == null) {
@@ -118,7 +105,7 @@
 
         // compute all possible breakpoints.
         int length = builder.mWidths.length;
-        BreakIterator it = BreakIterator.getLineInstance(new ULocale(builder.mLocales));
+        BreakIterator it = BreakIterator.getLineInstance();
         it.setText(new Segment(builder.mText, 0, length));
 
         // average word length in english is 5. So, initialize the possible breaks with a guess.
@@ -149,6 +136,7 @@
                         builder.mTabStopCalculator);
         }
         builder.mLineBreaker.computeBreaks(recycle);
+        System.arraycopy(builder.mWidths, 0, charWidths, 0, builder.mWidths.length);
         return recycle.breaks.length;
     }
 
@@ -206,11 +194,9 @@
      * Java representation of the native Builder class.
      */
     private static class Builder {
-        String mLocales;
         char[] mText;
         float[] mWidths;
         LineBreaker mLineBreaker;
-        long[] mNativeHyphenators;
         int mBreakStrategy;
         LineWidth mLineWidth;
         TabStops mTabStopCalculator;
diff --git a/android/text/TextLine.java b/android/text/TextLine.java
index 2dbff10..20c0ed8 100644
--- a/android/text/TextLine.java
+++ b/android/text/TextLine.java
@@ -73,7 +73,7 @@
             new SpanSet<ReplacementSpan>(ReplacementSpan.class);
 
     private final DecorationInfo mDecorationInfo = new DecorationInfo();
-    private final ArrayList<DecorationInfo> mDecorations = new ArrayList();
+    private final ArrayList<DecorationInfo> mDecorations = new ArrayList<>();
 
     private static final TextLine[] sCached = new TextLine[3];
 
@@ -340,14 +340,14 @@
 
                     boolean advance = (mDir == Layout.DIR_RIGHT_TO_LEFT) == runIsRtl;
                     if (inSegment && advance) {
-                        return h += measureRun(segstart, offset, j, runIsRtl, fmi);
+                        return h + measureRun(segstart, offset, j, runIsRtl, fmi);
                     }
 
                     float w = measureRun(segstart, j, j, runIsRtl, fmi);
                     h += advance ? w : -w;
 
                     if (inSegment) {
-                        return h += measureRun(segstart, offset, j, runIsRtl, null);
+                        return h + measureRun(segstart, offset, j, runIsRtl, null);
                     }
 
                     if (codept == '\t') {
@@ -828,14 +828,14 @@
                     }
                     if (info.isUnderlineText) {
                         final float thickness =
-                                Math.max(((Paint) wp).getUnderlineThickness(), 1.0f);
+                                Math.max(wp.getUnderlineThickness(), 1.0f);
                         drawStroke(wp, c, wp.getColor(), wp.getUnderlinePosition(), thickness,
                                 decorationXLeft, decorationXRight, y);
                     }
 
                     if (info.isStrikeThruText) {
                         final float thickness =
-                                Math.max(((Paint) wp).getStrikeThruThickness(), 1.0f);
+                                Math.max(wp.getStrikeThruThickness(), 1.0f);
                         drawStroke(wp, c, wp.getColor(), wp.getStrikeThruPosition(), thickness,
                                 decorationXLeft, decorationXRight, y);
                     }
diff --git a/android/text/TextViewSetTextMeasurePerfTest.java b/android/text/TextViewSetTextMeasurePerfTest.java
index a2bf33e..ff2d57e 100644
--- a/android/text/TextViewSetTextMeasurePerfTest.java
+++ b/android/text/TextViewSetTextMeasurePerfTest.java
@@ -40,7 +40,7 @@
 import java.util.Random;
 
 /**
- * Performance test for multi line, single style {@link StaticLayout} creation/draw.
+ * Performance test for {@link TextView} measure/draw.
  */
 @LargeTest
 @RunWith(Parameterized.class)
@@ -51,7 +51,7 @@
     @Rule
     public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
 
-    @Parameterized.Parameters(name = "cached={3},{1} chars,{0}")
+    @Parameterized.Parameters(name = "cached={3},{1}chars,{0}")
     public static Collection cases() {
         final List<Object[]> params = new ArrayList<>();
         for (int length : new int[]{128}) {
diff --git a/android/util/Log.java b/android/util/Log.java
index 0299865..b94e48b 100644
--- a/android/util/Log.java
+++ b/android/util/Log.java
@@ -16,45 +16,12 @@
 
 package android.util;
 
-import android.os.DeadSystemException;
-
-import com.android.internal.os.RuntimeInit;
-import com.android.internal.util.FastPrintWriter;
-import com.android.internal.util.LineBreakBufferedWriter;
-
 import java.io.PrintWriter;
 import java.io.StringWriter;
-import java.io.Writer;
 import java.net.UnknownHostException;
 
 /**
- * API for sending log output.
- *
- * <p>Generally, you should use the {@link #v Log.v()}, {@link #d Log.d()},
- * {@link #i Log.i()}, {@link #w Log.w()}, and {@link #e Log.e()} methods to write logs.
- * You can then <a href="{@docRoot}studio/debug/am-logcat.html">view the logs in logcat</a>.
- *
- * <p>The order in terms of verbosity, from least to most is
- * ERROR, WARN, INFO, DEBUG, VERBOSE.  Verbose should never be compiled
- * into an application except during development.  Debug logs are compiled
- * in but stripped at runtime.  Error, warning and info logs are always kept.
- *
- * <p><b>Tip:</b> A good convention is to declare a <code>TAG</code> constant
- * in your class:
- *
- * <pre>private static final String TAG = "MyActivity";</pre>
- *
- * and use that in subsequent calls to the log methods.
- * </p>
- *
- * <p><b>Tip:</b> Don't forget that when you make a call like
- * <pre>Log.v(TAG, "index=" + i);</pre>
- * that when you're building the string to pass into Log.d, the compiler uses a
- * StringBuilder and at least three allocations occur: the StringBuilder
- * itself, the buffer, and the String object.  Realistically, there is also
- * another buffer allocation and copy, and even more pressure on the gc.
- * That means that if your log message is filtered out, you might be doing
- * significant work and incurring significant overhead.
+ * Mock Log implementation for testing on non android host.
  */
 public final class Log {
 
@@ -88,29 +55,6 @@
      */
     public static final int ASSERT = 7;
 
-    /**
-     * Exception class used to capture a stack trace in {@link #wtf}.
-     * @hide
-     */
-    public static class TerribleFailure extends Exception {
-        TerribleFailure(String msg, Throwable cause) { super(msg, cause); }
-    }
-
-    /**
-     * Interface to handle terrible failures from {@link #wtf}.
-     *
-     * @hide
-     */
-    public interface TerribleFailureHandler {
-        void onTerribleFailure(String tag, TerribleFailure what, boolean system);
-    }
-
-    private static TerribleFailureHandler sWtfHandler = new TerribleFailureHandler() {
-            public void onTerribleFailure(String tag, TerribleFailure what, boolean system) {
-                RuntimeInit.wtf(tag, what, system);
-            }
-        };
-
     private Log() {
     }
 
@@ -121,7 +65,7 @@
      * @param msg The message you would like logged.
      */
     public static int v(String tag, String msg) {
-        return println_native(LOG_ID_MAIN, VERBOSE, tag, msg);
+        return println(LOG_ID_MAIN, VERBOSE, tag, msg);
     }
 
     /**
@@ -132,7 +76,7 @@
      * @param tr An exception to log
      */
     public static int v(String tag, String msg, Throwable tr) {
-        return printlns(LOG_ID_MAIN, VERBOSE, tag, msg, tr);
+        return println(LOG_ID_MAIN, VERBOSE, tag, msg + '\n' + getStackTraceString(tr));
     }
 
     /**
@@ -142,7 +86,7 @@
      * @param msg The message you would like logged.
      */
     public static int d(String tag, String msg) {
-        return println_native(LOG_ID_MAIN, DEBUG, tag, msg);
+        return println(LOG_ID_MAIN, DEBUG, tag, msg);
     }
 
     /**
@@ -153,7 +97,7 @@
      * @param tr An exception to log
      */
     public static int d(String tag, String msg, Throwable tr) {
-        return printlns(LOG_ID_MAIN, DEBUG, tag, msg, tr);
+        return println(LOG_ID_MAIN, DEBUG, tag, msg + '\n' + getStackTraceString(tr));
     }
 
     /**
@@ -163,7 +107,7 @@
      * @param msg The message you would like logged.
      */
     public static int i(String tag, String msg) {
-        return println_native(LOG_ID_MAIN, INFO, tag, msg);
+        return println(LOG_ID_MAIN, INFO, tag, msg);
     }
 
     /**
@@ -174,7 +118,7 @@
      * @param tr An exception to log
      */
     public static int i(String tag, String msg, Throwable tr) {
-        return printlns(LOG_ID_MAIN, INFO, tag, msg, tr);
+        return println(LOG_ID_MAIN, INFO, tag, msg + '\n' + getStackTraceString(tr));
     }
 
     /**
@@ -184,7 +128,7 @@
      * @param msg The message you would like logged.
      */
     public static int w(String tag, String msg) {
-        return println_native(LOG_ID_MAIN, WARN, tag, msg);
+        return println(LOG_ID_MAIN, WARN, tag, msg);
     }
 
     /**
@@ -195,31 +139,9 @@
      * @param tr An exception to log
      */
     public static int w(String tag, String msg, Throwable tr) {
-        return printlns(LOG_ID_MAIN, WARN, tag, msg, tr);
+        return println(LOG_ID_MAIN, WARN, tag, msg + '\n' + getStackTraceString(tr));
     }
 
-    /**
-     * Checks to see whether or not a log for the specified tag is loggable at the specified level.
-     *
-     *  The default level of any tag is set to INFO. This means that any level above and including
-     *  INFO will be logged. Before you make any calls to a logging method you should check to see
-     *  if your tag should be logged. You can change the default level by setting a system property:
-     *      'setprop log.tag.&lt;YOUR_LOG_TAG> &lt;LEVEL>'
-     *  Where level is either VERBOSE, DEBUG, INFO, WARN, ERROR, ASSERT, or SUPPRESS. SUPPRESS will
-     *  turn off all logging for your tag. You can also create a local.prop file that with the
-     *  following in it:
-     *      'log.tag.&lt;YOUR_LOG_TAG>=&lt;LEVEL>'
-     *  and place that in /data/local.prop.
-     *
-     * @param tag The tag to check.
-     * @param level The level to check.
-     * @return Whether or not that this is allowed to be logged.
-     * @throws IllegalArgumentException is thrown if the tag.length() > 23
-     *         for Nougat (7.0) releases (API <= 23) and prior, there is no
-     *         tag limit of concern after this API level.
-     */
-    public static native boolean isLoggable(String tag, int level);
-
     /*
      * Send a {@link #WARN} log message and log the exception.
      * @param tag Used to identify the source of a log message.  It usually identifies
@@ -227,7 +149,7 @@
      * @param tr An exception to log
      */
     public static int w(String tag, Throwable tr) {
-        return printlns(LOG_ID_MAIN, WARN, tag, "", tr);
+        return println(LOG_ID_MAIN, WARN, tag, getStackTraceString(tr));
     }
 
     /**
@@ -237,7 +159,7 @@
      * @param msg The message you would like logged.
      */
     public static int e(String tag, String msg) {
-        return println_native(LOG_ID_MAIN, ERROR, tag, msg);
+        return println(LOG_ID_MAIN, ERROR, tag, msg);
     }
 
     /**
@@ -248,82 +170,7 @@
      * @param tr An exception to log
      */
     public static int e(String tag, String msg, Throwable tr) {
-        return printlns(LOG_ID_MAIN, ERROR, tag, msg, tr);
-    }
-
-    /**
-     * What a Terrible Failure: Report a condition that should never happen.
-     * The error will always be logged at level ASSERT with the call stack.
-     * Depending on system configuration, a report may be added to the
-     * {@link android.os.DropBoxManager} and/or the process may be terminated
-     * immediately with an error dialog.
-     * @param tag Used to identify the source of a log message.
-     * @param msg The message you would like logged.
-     */
-    public static int wtf(String tag, String msg) {
-        return wtf(LOG_ID_MAIN, tag, msg, null, false, false);
-    }
-
-    /**
-     * Like {@link #wtf(String, String)}, but also writes to the log the full
-     * call stack.
-     * @hide
-     */
-    public static int wtfStack(String tag, String msg) {
-        return wtf(LOG_ID_MAIN, tag, msg, null, true, false);
-    }
-
-    /**
-     * What a Terrible Failure: Report an exception that should never happen.
-     * Similar to {@link #wtf(String, String)}, with an exception to log.
-     * @param tag Used to identify the source of a log message.
-     * @param tr An exception to log.
-     */
-    public static int wtf(String tag, Throwable tr) {
-        return wtf(LOG_ID_MAIN, tag, tr.getMessage(), tr, false, false);
-    }
-
-    /**
-     * What a Terrible Failure: Report an exception that should never happen.
-     * Similar to {@link #wtf(String, Throwable)}, with a message as well.
-     * @param tag Used to identify the source of a log message.
-     * @param msg The message you would like logged.
-     * @param tr An exception to log.  May be null.
-     */
-    public static int wtf(String tag, String msg, Throwable tr) {
-        return wtf(LOG_ID_MAIN, tag, msg, tr, false, false);
-    }
-
-    static int wtf(int logId, String tag, String msg, Throwable tr, boolean localStack,
-            boolean system) {
-        TerribleFailure what = new TerribleFailure(msg, tr);
-        // Only mark this as ERROR, do not use ASSERT since that should be
-        // reserved for cases where the system is guaranteed to abort.
-        // The onTerribleFailure call does not always cause a crash.
-        int bytes = printlns(logId, ERROR, tag, msg, localStack ? what : tr);
-        sWtfHandler.onTerribleFailure(tag, what, system);
-        return bytes;
-    }
-
-    static void wtfQuiet(int logId, String tag, String msg, boolean system) {
-        TerribleFailure what = new TerribleFailure(msg, null);
-        sWtfHandler.onTerribleFailure(tag, what, system);
-    }
-
-    /**
-     * Sets the terrible failure handler, for testing.
-     *
-     * @return the old handler
-     *
-     * @hide
-     */
-    public static TerribleFailureHandler setWtfHandler(TerribleFailureHandler handler) {
-        if (handler == null) {
-            throw new NullPointerException("handler == null");
-        }
-        TerribleFailureHandler oldHandler = sWtfHandler;
-        sWtfHandler = handler;
-        return oldHandler;
+        return println(LOG_ID_MAIN, ERROR, tag, msg + '\n' + getStackTraceString(tr));
     }
 
     /**
@@ -346,7 +193,7 @@
         }
 
         StringWriter sw = new StringWriter();
-        PrintWriter pw = new FastPrintWriter(sw, false, 256);
+        PrintWriter pw = new PrintWriter(sw);
         tr.printStackTrace(pw);
         pw.flush();
         return sw.toString();
@@ -361,7 +208,7 @@
      * @return The number of bytes written.
      */
     public static int println(int priority, String tag, String msg) {
-        return println_native(LOG_ID_MAIN, priority, tag, msg);
+        return println(LOG_ID_MAIN, priority, tag, msg);
     }
 
     /** @hide */ public static final int LOG_ID_MAIN = 0;
@@ -370,115 +217,9 @@
     /** @hide */ public static final int LOG_ID_SYSTEM = 3;
     /** @hide */ public static final int LOG_ID_CRASH = 4;
 
-    /** @hide */ public static native int println_native(int bufID,
-            int priority, String tag, String msg);
-
-    /**
-     * Return the maximum payload the log daemon accepts without truncation.
-     * @return LOGGER_ENTRY_MAX_PAYLOAD.
-     */
-    private static native int logger_entry_max_payload_native();
-
-    /**
-     * Helper function for long messages. Uses the LineBreakBufferedWriter to break
-     * up long messages and stacktraces along newlines, but tries to write in large
-     * chunks. This is to avoid truncation.
-     * @hide
-     */
-    public static int printlns(int bufID, int priority, String tag, String msg,
-            Throwable tr) {
-        ImmediateLogWriter logWriter = new ImmediateLogWriter(bufID, priority, tag);
-        // Acceptable buffer size. Get the native buffer size, subtract two zero terminators,
-        // and the length of the tag.
-        // Note: we implicitly accept possible truncation for Modified-UTF8 differences. It
-        //       is too expensive to compute that ahead of time.
-        int bufferSize = PreloadHolder.LOGGER_ENTRY_MAX_PAYLOAD    // Base.
-                - 2                                                // Two terminators.
-                - (tag != null ? tag.length() : 0)                 // Tag length.
-                - 32;                                              // Some slack.
-        // At least assume you can print *some* characters (tag is not too large).
-        bufferSize = Math.max(bufferSize, 100);
-
-        LineBreakBufferedWriter lbbw = new LineBreakBufferedWriter(logWriter, bufferSize);
-
-        lbbw.println(msg);
-
-        if (tr != null) {
-            // This is to reduce the amount of log spew that apps do in the non-error
-            // condition of the network being unavailable.
-            Throwable t = tr;
-            while (t != null) {
-                if (t instanceof UnknownHostException) {
-                    break;
-                }
-                if (t instanceof DeadSystemException) {
-                    lbbw.println("DeadSystemException: The system died; "
-                            + "earlier logs will point to the root cause");
-                    break;
-                }
-                t = t.getCause();
-            }
-            if (t == null) {
-                tr.printStackTrace(lbbw);
-            }
-        }
-
-        lbbw.flush();
-
-        return logWriter.getWritten();
-    }
-
-    /**
-     * PreloadHelper class. Caches the LOGGER_ENTRY_MAX_PAYLOAD value to avoid
-     * a JNI call during logging.
-     */
-    static class PreloadHolder {
-        public final static int LOGGER_ENTRY_MAX_PAYLOAD =
-                logger_entry_max_payload_native();
-    }
-
-    /**
-     * Helper class to write to the logcat. Different from LogWriter, this writes
-     * the whole given buffer and does not break along newlines.
-     */
-    private static class ImmediateLogWriter extends Writer {
-
-        private int bufID;
-        private int priority;
-        private String tag;
-
-        private int written = 0;
-
-        /**
-         * Create a writer that immediately writes to the log, using the given
-         * parameters.
-         */
-        public ImmediateLogWriter(int bufID, int priority, String tag) {
-            this.bufID = bufID;
-            this.priority = priority;
-            this.tag = tag;
-        }
-
-        public int getWritten() {
-            return written;
-        }
-
-        @Override
-        public void write(char[] cbuf, int off, int len) {
-            // Note: using String here has a bit of overhead as a Java object is created,
-            //       but using the char[] directly is not easier, as it needs to be translated
-            //       to a C char[] for logging.
-            written += println_native(bufID, priority, tag, new String(cbuf, off, len));
-        }
-
-        @Override
-        public void flush() {
-            // Ignored.
-        }
-
-        @Override
-        public void close() {
-            // Ignored.
-        }
+    /** @hide */ @SuppressWarnings("unused")
+    public static int println(int bufID,
+            int priority, String tag, String msg) {
+        return 0;
     }
 }
diff --git a/android/util/LruCache.java b/android/util/LruCache.java
index 4015488..5208606 100644
--- a/android/util/LruCache.java
+++ b/android/util/LruCache.java
@@ -20,6 +20,10 @@
 import java.util.Map;
 
 /**
+ * BEGIN LAYOUTLIB CHANGE
+ * This is a custom version that doesn't use the non standard LinkedHashMap#eldest.
+ * END LAYOUTLIB CHANGE
+ *
  * A cache that holds strong references to a limited number of values. Each time
  * a value is accessed, it is moved to the head of a queue. When a value is
  * added to a full cache, the value at the end of that queue is evicted and may
@@ -87,8 +91,9 @@
 
     /**
      * Sets the size of the cache.
-     *
      * @param maxSize The new maximum size.
+     *
+     * @hide
      */
     public void resize(int maxSize) {
         if (maxSize <= 0) {
@@ -185,13 +190,10 @@
     }
 
     /**
-     * Remove the eldest entries until the total of remaining entries is at or
-     * below the requested size.
-     *
      * @param maxSize the maximum size of the cache before returning. May be -1
-     *            to evict even 0-sized elements.
+     *     to evict even 0-sized elements.
      */
-    public void trimToSize(int maxSize) {
+    private void trimToSize(int maxSize) {
         while (true) {
             K key;
             V value;
@@ -205,7 +207,16 @@
                     break;
                 }
 
-                Map.Entry<K, V> toEvict = map.eldest();
+                // BEGIN LAYOUTLIB CHANGE
+                // get the last item in the linked list.
+                // This is not efficient, the goal here is to minimize the changes
+                // compared to the platform version.
+                Map.Entry<K, V> toEvict = null;
+                for (Map.Entry<K, V> entry : map.entrySet()) {
+                    toEvict = entry;
+                }
+                // END LAYOUTLIB CHANGE
+
                 if (toEvict == null) {
                     break;
                 }
diff --git a/android/util/MutableBoolean.java b/android/util/MutableBoolean.java
index 5a8a200..ed837ab 100644
--- a/android/util/MutableBoolean.java
+++ b/android/util/MutableBoolean.java
@@ -19,9 +19,9 @@
 /**
  */
 public final class MutableBoolean {
-  public boolean value;
+    public boolean value;
 
-  public MutableBoolean(boolean value) {
-    this.value = value;
-  }
+    public MutableBoolean(boolean value) {
+        this.value = value;
+    }
 }
diff --git a/android/util/MutableByte.java b/android/util/MutableByte.java
index 7397ba4..cc6b00a 100644
--- a/android/util/MutableByte.java
+++ b/android/util/MutableByte.java
@@ -19,9 +19,9 @@
 /**
  */
 public final class MutableByte {
-  public byte value;
+    public byte value;
 
-  public MutableByte(byte value) {
-    this.value = value;
-  }
+    public MutableByte(byte value) {
+        this.value = value;
+    }
 }
diff --git a/android/util/MutableChar.java b/android/util/MutableChar.java
index f435331..9a2e2bc 100644
--- a/android/util/MutableChar.java
+++ b/android/util/MutableChar.java
@@ -19,9 +19,9 @@
 /**
  */
 public final class MutableChar {
-  public char value;
+    public char value;
 
-  public MutableChar(char value) {
-    this.value = value;
-  }
+    public MutableChar(char value) {
+        this.value = value;
+    }
 }
diff --git a/android/util/MutableDouble.java b/android/util/MutableDouble.java
index f62f47e..bd7329a 100644
--- a/android/util/MutableDouble.java
+++ b/android/util/MutableDouble.java
@@ -19,9 +19,9 @@
 /**
  */
 public final class MutableDouble {
-  public double value;
+    public double value;
 
-  public MutableDouble(double value) {
-    this.value = value;
-  }
+    public MutableDouble(double value) {
+        this.value = value;
+    }
 }
diff --git a/android/util/MutableFloat.java b/android/util/MutableFloat.java
index 6b5441c..e6f2d7d 100644
--- a/android/util/MutableFloat.java
+++ b/android/util/MutableFloat.java
@@ -19,9 +19,9 @@
 /**
  */
 public final class MutableFloat {
-  public float value;
+    public float value;
 
-  public MutableFloat(float value) {
-    this.value = value;
-  }
+    public MutableFloat(float value) {
+        this.value = value;
+    }
 }
diff --git a/android/util/MutableInt.java b/android/util/MutableInt.java
index 2f93030..a3d8606 100644
--- a/android/util/MutableInt.java
+++ b/android/util/MutableInt.java
@@ -19,9 +19,9 @@
 /**
  */
 public final class MutableInt {
-  public int value;
+    public int value;
 
-  public MutableInt(int value) {
-    this.value = value;
-  }
+    public MutableInt(int value) {
+        this.value = value;
+    }
 }
diff --git a/android/util/MutableLong.java b/android/util/MutableLong.java
index 94beab5..575068e 100644
--- a/android/util/MutableLong.java
+++ b/android/util/MutableLong.java
@@ -19,9 +19,9 @@
 /**
  */
 public final class MutableLong {
-  public long value;
+    public long value;
 
-  public MutableLong(long value) {
-    this.value = value;
-  }
+    public MutableLong(long value) {
+        this.value = value;
+    }
 }
diff --git a/android/util/MutableShort.java b/android/util/MutableShort.java
index cdd9923..48fb232 100644
--- a/android/util/MutableShort.java
+++ b/android/util/MutableShort.java
@@ -19,9 +19,9 @@
 /**
  */
 public final class MutableShort {
-  public short value;
+    public short value;
 
-  public MutableShort(short value) {
-    this.value = value;
-  }
+    public MutableShort(short value) {
+        this.value = value;
+    }
 }
diff --git a/android/util/StatsLog.java b/android/util/StatsLog.java
deleted file mode 100644
index 0be1a8c..0000000
--- a/android/util/StatsLog.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.util;
-
-/**
- * Logging access for platform metrics.
- *
- * <p>This is <b>not</b> the main "logcat" debugging log ({@link android.util.Log})!
- * These diagnostic stats are for system integrators, not application authors.
- *
- * <p>Stats use integer tag codes.
- * They carry a payload of one or more int, long, or String values.
- * @hide
- */
-public class StatsLog {
-    /** @hide */ public StatsLog() {}
-
-    private static final String TAG = "StatsLog";
-
-    // We assume that the native methods deal with any concurrency issues.
-
-    /**
-     * Records an stats log message.
-     * @param tag The stats type tag code
-     * @param value A value to log
-     * @return The number of bytes written
-     */
-    public static native int writeInt(int tag, int value);
-
-    /**
-     * Records an stats log message.
-     * @param tag The stats type tag code
-     * @param value A value to log
-     * @return The number of bytes written
-     */
-    public static native int writeLong(int tag, long value);
-
-    /**
-     * Records an stats log message.
-     * @param tag The stats type tag code
-     * @param value A value to log
-     * @return The number of bytes written
-     */
-    public static native int writeFloat(int tag, float value);
-
-    /**
-     * Records an stats log message.
-     * @param tag The stats type tag code
-     * @param str A value to log
-     * @return The number of bytes written
-     */
-    public static native int writeString(int tag, String str);
-
-    /**
-     * Records an stats log message.
-     * @param tag The stats type tag code
-     * @param list A list of values to log. All values should
-     * be of type int, long, float or String.
-     * @return The number of bytes written
-     */
-    public static native int writeArray(int tag, Object... list);
-}
diff --git a/android/util/StatsLogKey.java b/android/util/StatsLogKey.java
deleted file mode 100644
index 9ad0a23..0000000
--- a/android/util/StatsLogKey.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2017 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.
- */
-
-// THIS FILE IS AUTO-GENERATED.
-// DO NOT MODIFY.
-
-package android.util;
-
-/** @hide */
-public class StatsLogKey {
-    private StatsLogKey() {}
-
-    /** Constants for android.os.statsd.ScreenStateChange. */
-
-    /** display_state */
-    public static final int SCREEN_STATE_CHANGE__DISPLAY_STATE = 1;
-
-    /** Constants for android.os.statsd.ProcessStateChange. */
-
-    /** state */
-    public static final int PROCESS_STATE_CHANGE__STATE = 1;
-
-    /** uid */
-    public static final int PROCESS_STATE_CHANGE__UID = 2;
-
-    /** package_name */
-    public static final int PROCESS_STATE_CHANGE__PACKAGE_NAME = 1002;
-
-    /** package_version */
-    public static final int PROCESS_STATE_CHANGE__PACKAGE_VERSION = 3;
-
-    /** package_version_string */
-    public static final int PROCESS_STATE_CHANGE__PACKAGE_VERSION_STRING = 4;
-
-}
diff --git a/android/util/StatsLogTag.java b/android/util/StatsLogTag.java
deleted file mode 100644
index 5e5a828..0000000
--- a/android/util/StatsLogTag.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2017 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.
- */
-
-// THIS FILE IS AUTO-GENERATED.
-// DO NOT MODIFY.
-
-package android.util;
-
-/** @hide */
-public class StatsLogTag {
-    private StatsLogTag() {}
-
-    /** android.os.statsd.ScreenStateChange. */
-    public static final int SCREEN_STATE_CHANGE = 2;
-
-    /** android.os.statsd.ProcessStateChange. */
-    public static final int PROCESS_STATE_CHANGE = 1112;
-
-}
diff --git a/android/util/StatsLogValue.java b/android/util/StatsLogValue.java
deleted file mode 100644
index 05b9d93..0000000
--- a/android/util/StatsLogValue.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2017 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.
- */
-
-// THIS FILE IS AUTO-GENERATED.
-// DO NOT MODIFY.
-
-package android.util;
-
-/** @hide */
-public class StatsLogValue {
-    private StatsLogValue() {}
-
-    /** Constants for android.os.statsd.ScreenStateChange. */
-
-    /** display_state: STATE_UNKNOWN */
-    public static final int SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_UNKNOWN = 0;
-
-    /** display_state: STATE_OFF */
-    public static final int SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_OFF = 1;
-
-    /** display_state: STATE_ON */
-    public static final int SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_ON = 2;
-
-    /** display_state: STATE_DOZE */
-    public static final int SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_DOZE = 3;
-
-    /** display_state: STATE_DOZE_SUSPEND */
-    public static final int SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_DOZE_SUSPEND = 4;
-
-    /** display_state: STATE_VR */
-    public static final int SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_VR = 5;
-
-    /** Constants for android.os.statsd.ProcessStateChange. */
-
-    /** state: START */
-    public static final int PROCESS_STATE_CHANGE__STATE__START = 1;
-
-    /** state: CRASH */
-    public static final int PROCESS_STATE_CHANGE__STATE__CRASH = 2;
-
-}
diff --git a/android/util/apk/ApkSignatureSchemeV2Verifier.java b/android/util/apk/ApkSignatureSchemeV2Verifier.java
index 0216a07..a9ccae1 100644
--- a/android/util/apk/ApkSignatureSchemeV2Verifier.java
+++ b/android/util/apk/ApkSignatureSchemeV2Verifier.java
@@ -17,6 +17,7 @@
 package android.util.apk;
 
 import android.system.ErrnoException;
+import android.system.Os;
 import android.system.OsConstants;
 import android.util.ArrayMap;
 import android.util.Pair;
@@ -59,9 +60,6 @@
 import java.util.Map;
 import java.util.Set;
 
-import libcore.io.Libcore;
-import libcore.io.Os;
-
 /**
  * APK Signature Scheme v2 verifier.
  *
@@ -994,8 +992,7 @@
      * {@link DataSource#feedIntoMessageDigests(MessageDigest[], long, int) feedIntoMessageDigests}.
      */
     private static final class MemoryMappedFileDataSource implements DataSource {
-        private static final Os OS = Libcore.os;
-        private static final long MEMORY_PAGE_SIZE_BYTES = OS.sysconf(OsConstants._SC_PAGESIZE);
+        private static final long MEMORY_PAGE_SIZE_BYTES = Os.sysconf(OsConstants._SC_PAGESIZE);
 
         private final FileDescriptor mFd;
         private final long mFilePosition;
@@ -1041,7 +1038,7 @@
             long mmapRegionSize = size + dataStartOffsetInMmapRegion;
             long mmapPtr = 0;
             try {
-                mmapPtr = OS.mmap(
+                mmapPtr = Os.mmap(
                         0, // let the OS choose the start address of the region in memory
                         mmapRegionSize,
                         OsConstants.PROT_READ,
@@ -1066,7 +1063,7 @@
             } finally {
                 if (mmapPtr != 0) {
                     try {
-                        OS.munmap(mmapPtr, mmapRegionSize);
+                        Os.munmap(mmapPtr, mmapRegionSize);
                     } catch (ErrnoException ignored) {}
                 }
             }
diff --git a/android/view/MenuItem.java b/android/view/MenuItem.java
index 88b9c0d..ad160cb 100644
--- a/android/view/MenuItem.java
+++ b/android/view/MenuItem.java
@@ -72,11 +72,6 @@
     public static final int SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW = 8;
 
     /**
-     * @hide
-     */
-    int SHOW_AS_OVERFLOW_ALWAYS = 1 << 31;
-
-    /**
      * Interface definition for a callback to be invoked when a menu item is
      * clicked.
      *
@@ -806,12 +801,22 @@
     }
 
     /**
-     * Returns true if {@link #setShowAsAction(int)} was set to {@link #SHOW_AS_OVERFLOW_ALWAYS}.
-     * Default value if {@code false}.
+     * Returns true if {@link #setShowAsAction(int)} was set to {@link #SHOW_AS_ACTION_ALWAYS}.
+     * Default value is {@code false}.
+     *
+     * @hide
+     */
+    default boolean requiresActionButton() {
+        return false;
+    }
+
+    /**
+     * Returns true if {@link #setShowAsAction(int)} was set to {@link #SHOW_AS_ACTION_NEVER}.
+     * Default value is {@code true}.
      *
      * @hide
      */
     default boolean requiresOverflow() {
-        return false;
+        return true;
     }
 }
diff --git a/android/view/SurfaceControl.java b/android/view/SurfaceControl.java
index 31daeff..ff027a9 100644
--- a/android/view/SurfaceControl.java
+++ b/android/view/SurfaceControl.java
@@ -21,15 +21,22 @@
 import android.annotation.Size;
 import android.graphics.Bitmap;
 import android.graphics.GraphicBuffer;
+import android.graphics.Point;
+import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.os.Binder;
+import android.os.Debug;
 import android.os.IBinder;
 import android.util.Log;
 import android.view.Surface.OutOfResourcesException;
 
 import dalvik.system.CloseGuard;
 
+import java.io.Closeable;
+
+import libcore.util.NativeAllocationRegistry;
+
 /**
  * SurfaceControl
  *  @hide
@@ -54,25 +61,34 @@
             Rect sourceCrop, int width, int height, int minLayer, int maxLayer,
             boolean allLayers, boolean useIdentityTransform);
 
-    private static native void nativeOpenTransaction();
-    private static native void nativeCloseTransaction(boolean sync);
-    private static native void nativeSetAnimationTransaction();
+    private static native long nativeCreateTransaction();
+    private static native long nativeGetNativeTransactionFinalizer();
+    private static native void nativeApplyTransaction(long transactionObj, boolean sync);
+    private static native void nativeSetAnimationTransaction(long transactionObj);
 
-    private static native void nativeSetLayer(long nativeObject, int zorder);
-    private static native void nativeSetRelativeLayer(long nativeObject, IBinder relativeTo,
-            int zorder);
-    private static native void nativeSetPosition(long nativeObject, float x, float y);
-    private static native void nativeSetGeometryAppliesWithResize(long nativeObject);
-    private static native void nativeSetSize(long nativeObject, int w, int h);
-    private static native void nativeSetTransparentRegionHint(long nativeObject, Region region);
-    private static native void nativeSetAlpha(long nativeObject, float alpha);
-    private static native void nativeSetColor(long nativeObject, float[] color);
-    private static native void nativeSetMatrix(long nativeObject, float dsdx, float dtdx,
+    private static native void nativeSetLayer(long transactionObj, long nativeObject, int zorder);
+    private static native void nativeSetRelativeLayer(long transactionObj, long nativeObject,
+            IBinder relativeTo, int zorder);
+    private static native void nativeSetPosition(long transactionObj, long nativeObject,
+            float x, float y);
+    private static native void nativeSetGeometryAppliesWithResize(long transactionObj,
+            long nativeObject);
+    private static native void nativeSetSize(long transactionObj, long nativeObject, int w, int h);
+    private static native void nativeSetTransparentRegionHint(long transactionObj,
+            long nativeObject, Region region);
+    private static native void nativeSetAlpha(long transactionObj, long nativeObject, float alpha);
+    private static native void nativeSetMatrix(long transactionObj, long nativeObject,
+            float dsdx, float dtdx,
             float dtdy, float dsdy);
-    private static native void nativeSetFlags(long nativeObject, int flags, int mask);
-    private static native void nativeSetWindowCrop(long nativeObject, int l, int t, int r, int b);
-    private static native void nativeSetFinalCrop(long nativeObject, int l, int t, int r, int b);
-    private static native void nativeSetLayerStack(long nativeObject, int layerStack);
+    private static native void nativeSetColor(long transactionObj, long nativeObject, float[] color);
+    private static native void nativeSetFlags(long transactionObj, long nativeObject,
+            int flags, int mask);
+    private static native void nativeSetWindowCrop(long transactionObj, long nativeObject,
+            int l, int t, int r, int b);
+    private static native void nativeSetFinalCrop(long transactionObj, long nativeObject,
+            int l, int t, int r, int b);
+    private static native void nativeSetLayerStack(long transactionObj, long nativeObject,
+            int layerStack);
 
     private static native boolean nativeClearContentFrameStats(long nativeObject);
     private static native boolean nativeGetContentFrameStats(long nativeObject, WindowContentFrameStats outStats);
@@ -82,15 +98,16 @@
     private static native IBinder nativeGetBuiltInDisplay(int physicalDisplayId);
     private static native IBinder nativeCreateDisplay(String name, boolean secure);
     private static native void nativeDestroyDisplay(IBinder displayToken);
-    private static native void nativeSetDisplaySurface(
+    private static native void nativeSetDisplaySurface(long transactionObj,
             IBinder displayToken, long nativeSurfaceObject);
-    private static native void nativeSetDisplayLayerStack(
+    private static native void nativeSetDisplayLayerStack(long transactionObj,
             IBinder displayToken, int layerStack);
-    private static native void nativeSetDisplayProjection(
+    private static native void nativeSetDisplayProjection(long transactionObj,
             IBinder displayToken, int orientation,
             int l, int t, int r, int b,
             int L, int T, int R, int B);
-    private static native void nativeSetDisplaySize(IBinder displayToken, int width, int height);
+    private static native void nativeSetDisplaySize(long transactionObj, IBinder displayToken,
+            int width, int height);
     private static native SurfaceControl.PhysicalDisplayInfo[] nativeGetDisplayConfigs(
             IBinder displayToken);
     private static native int nativeGetActiveConfig(IBinder displayToken);
@@ -101,16 +118,17 @@
             int colorMode);
     private static native void nativeSetDisplayPowerMode(
             IBinder displayToken, int mode);
-    private static native void nativeDeferTransactionUntil(long nativeObject,
+    private static native void nativeDeferTransactionUntil(long transactionObj, long nativeObject,
             IBinder handle, long frame);
-    private static native void nativeDeferTransactionUntilSurface(long nativeObject,
+    private static native void nativeDeferTransactionUntilSurface(long transactionObj,
+            long nativeObject,
             long surfaceObject, long frame);
-    private static native void nativeReparentChildren(long nativeObject,
+    private static native void nativeReparentChildren(long transactionObj, long nativeObject,
             IBinder handle);
-    private static native void nativeReparent(long nativeObject,
+    private static native void nativeReparent(long transactionObj, long nativeObject,
             IBinder parentHandle);
-    private static native void nativeSeverChildren(long nativeObject);
-    private static native void nativeSetOverrideScalingMode(long nativeObject,
+    private static native void nativeSeverChildren(long transactionObj, long nativeObject);
+    private static native void nativeSetOverrideScalingMode(long transactionObj, long nativeObject,
             int scalingMode);
     private static native IBinder nativeGetHandle(long nativeObject);
     private static native boolean nativeGetTransformToDisplayInverse(long nativeObject);
@@ -122,6 +140,9 @@
     private final String mName;
     long mNativeObject; // package visibility only for Surface.java access
 
+    static Transaction sGlobalTransaction;
+    static long sTransactionNestCount = 0;
+
     /* flags used in constructor (keep in sync with ISurfaceComposerClient.h) */
 
     /**
@@ -377,11 +398,6 @@
         }
     }
 
-    @Override
-    public String toString() {
-        return "Surface(name=" + mName + ")";
-    }
-
     /**
      * Release the local reference to the server-side surface.
      * Always call release() when you're done with a Surface.
@@ -429,102 +445,141 @@
 
     /** start a transaction */
     public static void openTransaction() {
-        nativeOpenTransaction();
+        synchronized (SurfaceControl.class) {
+            if (sGlobalTransaction == null) {
+                sGlobalTransaction = new Transaction();
+            }
+            synchronized(SurfaceControl.class) {
+                sTransactionNestCount++;
+            }
+        }
+    }
+
+    private static void closeTransaction(boolean sync) {
+        synchronized(SurfaceControl.class) {
+            if (sTransactionNestCount == 0) {
+                Log.e(TAG, "Call to SurfaceControl.closeTransaction without matching openTransaction");
+            } else if (--sTransactionNestCount > 0) {
+                return;
+            }
+            sGlobalTransaction.apply(sync);
+        }
     }
 
     /** end a transaction */
     public static void closeTransaction() {
-        nativeCloseTransaction(false);
+        closeTransaction(false);
     }
 
     public static void closeTransactionSync() {
-        nativeCloseTransaction(true);
+        closeTransaction(true);
     }
 
     public void deferTransactionUntil(IBinder handle, long frame) {
         if (frame > 0) {
-            nativeDeferTransactionUntil(mNativeObject, handle, frame);
+            synchronized(SurfaceControl.class) {
+                sGlobalTransaction.deferTransactionUntil(this, handle, frame);
+            }
         }
     }
 
     public void deferTransactionUntil(Surface barrier, long frame) {
         if (frame > 0) {
-            nativeDeferTransactionUntilSurface(mNativeObject, barrier.mNativeObject, frame);
+            synchronized(SurfaceControl.class) {
+                sGlobalTransaction.deferTransactionUntilSurface(this, barrier, frame);
+            }
         }
     }
 
     public void reparentChildren(IBinder newParentHandle) {
-        nativeReparentChildren(mNativeObject, newParentHandle);
+        synchronized(SurfaceControl.class) {
+            sGlobalTransaction.reparentChildren(this, newParentHandle);
+        }
     }
 
-    /** Re-parents this layer to a new parent. */
     public void reparent(IBinder newParentHandle) {
-        nativeReparent(mNativeObject, newParentHandle);
+        synchronized(SurfaceControl.class) {
+            sGlobalTransaction.reparent(this, newParentHandle);
+        }
     }
 
     public void detachChildren() {
-        nativeSeverChildren(mNativeObject);
+        synchronized(SurfaceControl.class) {
+            sGlobalTransaction.detachChildren(this);
+        }
     }
 
     public void setOverrideScalingMode(int scalingMode) {
         checkNotReleased();
-        nativeSetOverrideScalingMode(mNativeObject, scalingMode);
+        synchronized(SurfaceControl.class) {
+            sGlobalTransaction.setOverrideScalingMode(this, scalingMode);
+        }
     }
 
     public IBinder getHandle() {
         return nativeGetHandle(mNativeObject);
     }
 
-    /** flag the transaction as an animation */
     public static void setAnimationTransaction() {
-        nativeSetAnimationTransaction();
+        synchronized (SurfaceControl.class) {
+            sGlobalTransaction.setAnimationTransaction();
+        }
     }
 
     public void setLayer(int zorder) {
         checkNotReleased();
-        nativeSetLayer(mNativeObject, zorder);
+        synchronized(SurfaceControl.class) {
+            sGlobalTransaction.setLayer(this, zorder);
+        }
     }
 
     public void setRelativeLayer(IBinder relativeTo, int zorder) {
         checkNotReleased();
-        nativeSetRelativeLayer(mNativeObject, relativeTo, zorder);
+        synchronized(SurfaceControl.class) {
+            sGlobalTransaction.setRelativeLayer(this, relativeTo, zorder);
+        }
     }
 
     public void setPosition(float x, float y) {
         checkNotReleased();
-        nativeSetPosition(mNativeObject, x, y);
+        synchronized(SurfaceControl.class) {
+            sGlobalTransaction.setPosition(this, x, y);
+        }
     }
 
-    /**
-     * If the buffer size changes in this transaction, position and crop updates specified
-     * in this transaction will not complete until a buffer of the new size
-     * arrives. As transform matrix and size are already frozen in this fashion,
-     * this enables totally freezing the surface until the resize has completed
-     * (at which point the geometry influencing aspects of this transaction will then occur)
-     */
     public void setGeometryAppliesWithResize() {
         checkNotReleased();
-        nativeSetGeometryAppliesWithResize(mNativeObject);
+        synchronized(SurfaceControl.class) {
+            sGlobalTransaction.setGeometryAppliesWithResize(this);
+        }
     }
 
     public void setSize(int w, int h) {
         checkNotReleased();
-        nativeSetSize(mNativeObject, w, h);
+        synchronized(SurfaceControl.class) {
+            sGlobalTransaction.setSize(this, w, h);
+        }
     }
 
     public void hide() {
         checkNotReleased();
-        nativeSetFlags(mNativeObject, SURFACE_HIDDEN, SURFACE_HIDDEN);
+        synchronized(SurfaceControl.class) {
+            sGlobalTransaction.hide(this);
+        }
     }
 
     public void show() {
         checkNotReleased();
-        nativeSetFlags(mNativeObject, 0, SURFACE_HIDDEN);
+        synchronized(SurfaceControl.class) {
+            sGlobalTransaction.show(this);
+        }
     }
 
     public void setTransparentRegionHint(Region region) {
         checkNotReleased();
-        nativeSetTransparentRegionHint(mNativeObject, region);
+        synchronized(SurfaceControl.class) {
+            sGlobalTransaction.setTransparentRegionHint(this, region);
+        }
     }
 
     public boolean clearContentFrameStats() {
@@ -545,80 +600,70 @@
         return nativeGetAnimationFrameStats(outStats);
     }
 
-    /**
-     * Sets an alpha value for the entire Surface.  This value is combined with the
-     * per-pixel alpha.  It may be used with opaque Surfaces.
-     */
     public void setAlpha(float alpha) {
         checkNotReleased();
-        nativeSetAlpha(mNativeObject, alpha);
+        synchronized(SurfaceControl.class) {
+            sGlobalTransaction.setAlpha(this, alpha);
+        }
     }
 
-    /**
-     * Sets a color for the Surface.
-     * @param color A float array with three values to represent r, g, b in range [0..1]
-     */
     public void setColor(@Size(3) float[] color) {
         checkNotReleased();
-        nativeSetColor(mNativeObject, color);
+        synchronized (SurfaceControl.class) {
+            sGlobalTransaction.setColor(this, color);
+        }
     }
 
     public void setMatrix(float dsdx, float dtdx, float dtdy, float dsdy) {
         checkNotReleased();
-        nativeSetMatrix(mNativeObject, dsdx, dtdx, dtdy, dsdy);
+        synchronized(SurfaceControl.class) {
+            sGlobalTransaction.setMatrix(this, dsdx, dtdx, dtdy, dsdy);
+        }
     }
 
     public void setWindowCrop(Rect crop) {
         checkNotReleased();
-        if (crop != null) {
-            nativeSetWindowCrop(mNativeObject,
-                crop.left, crop.top, crop.right, crop.bottom);
-        } else {
-            nativeSetWindowCrop(mNativeObject, 0, 0, 0, 0);
+        synchronized (SurfaceControl.class) {
+            sGlobalTransaction.setWindowCrop(this, crop);
         }
     }
 
     public void setFinalCrop(Rect crop) {
         checkNotReleased();
-        if (crop != null) {
-            nativeSetFinalCrop(mNativeObject,
-                crop.left, crop.top, crop.right, crop.bottom);
-        } else {
-            nativeSetFinalCrop(mNativeObject, 0, 0, 0, 0);
+        synchronized (SurfaceControl.class) {
+            sGlobalTransaction.setFinalCrop(this, crop);
         }
     }
 
     public void setLayerStack(int layerStack) {
         checkNotReleased();
-        nativeSetLayerStack(mNativeObject, layerStack);
+        synchronized(SurfaceControl.class) {
+            sGlobalTransaction.setLayerStack(this, layerStack);
+        }
     }
 
-    /**
-     * Sets the opacity of the surface.  Setting the flag is equivalent to creating the
-     * Surface with the {@link #OPAQUE} flag.
-     */
     public void setOpaque(boolean isOpaque) {
         checkNotReleased();
-        if (isOpaque) {
-            nativeSetFlags(mNativeObject, SURFACE_OPAQUE, SURFACE_OPAQUE);
-        } else {
-            nativeSetFlags(mNativeObject, 0, SURFACE_OPAQUE);
+
+        synchronized (SurfaceControl.class) {
+            sGlobalTransaction.setOpaque(this, isOpaque);
         }
     }
 
-    /**
-     * Sets the security of the surface.  Setting the flag is equivalent to creating the
-     * Surface with the {@link #SECURE} flag.
-     */
     public void setSecure(boolean isSecure) {
         checkNotReleased();
-        if (isSecure) {
-            nativeSetFlags(mNativeObject, SECURE, SECURE);
-        } else {
-            nativeSetFlags(mNativeObject, 0, SECURE);
+
+        synchronized (SurfaceControl.class) {
+            sGlobalTransaction.setSecure(this, isSecure);
         }
     }
 
+    @Override
+    public String toString() {
+        return "Surface(name=" + mName + ")/@0x" +
+                Integer.toHexString(System.identityHashCode(this));
+    }
+
     /*
      * set display parameters.
      * needs to be inside open/closeTransaction block
@@ -741,50 +786,28 @@
 
     public static void setDisplayProjection(IBinder displayToken,
             int orientation, Rect layerStackRect, Rect displayRect) {
-        if (displayToken == null) {
-            throw new IllegalArgumentException("displayToken must not be null");
+        synchronized (SurfaceControl.class) {
+            sGlobalTransaction.setDisplayProjection(displayToken, orientation,
+                    layerStackRect, displayRect);
         }
-        if (layerStackRect == null) {
-            throw new IllegalArgumentException("layerStackRect must not be null");
-        }
-        if (displayRect == null) {
-            throw new IllegalArgumentException("displayRect must not be null");
-        }
-        nativeSetDisplayProjection(displayToken, orientation,
-                layerStackRect.left, layerStackRect.top, layerStackRect.right, layerStackRect.bottom,
-                displayRect.left, displayRect.top, displayRect.right, displayRect.bottom);
     }
 
     public static void setDisplayLayerStack(IBinder displayToken, int layerStack) {
-        if (displayToken == null) {
-            throw new IllegalArgumentException("displayToken must not be null");
+        synchronized (SurfaceControl.class) {
+            sGlobalTransaction.setDisplayLayerStack(displayToken, layerStack);
         }
-        nativeSetDisplayLayerStack(displayToken, layerStack);
     }
 
     public static void setDisplaySurface(IBinder displayToken, Surface surface) {
-        if (displayToken == null) {
-            throw new IllegalArgumentException("displayToken must not be null");
-        }
-
-        if (surface != null) {
-            synchronized (surface.mLock) {
-                nativeSetDisplaySurface(displayToken, surface.mNativeObject);
-            }
-        } else {
-            nativeSetDisplaySurface(displayToken, 0);
+        synchronized (SurfaceControl.class) {
+            sGlobalTransaction.setDisplaySurface(displayToken, surface);
         }
     }
 
     public static void setDisplaySize(IBinder displayToken, int width, int height) {
-        if (displayToken == null) {
-            throw new IllegalArgumentException("displayToken must not be null");
+        synchronized (SurfaceControl.class) {
+            sGlobalTransaction.setDisplaySize(displayToken, width, height);
         }
-        if (width <= 0 || height <= 0) {
-            throw new IllegalArgumentException("width and height must be positive");
-        }
-
-        nativeSetDisplaySize(displayToken, width, height);
     }
 
     public static Display.HdrCapabilities getHdrCapabilities(IBinder displayToken) {
@@ -946,4 +969,261 @@
         nativeScreenshot(display, consumer, sourceCrop, width, height,
                 minLayer, maxLayer, allLayers, useIdentityTransform);
     }
+
+    public static class Transaction implements Closeable {
+        public static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry(
+                Transaction.class.getClassLoader(),
+                nativeGetNativeTransactionFinalizer(), 512);
+        private long mNativeObject;
+
+        Runnable mFreeNativeResources;
+
+        public Transaction() {
+            mNativeObject = nativeCreateTransaction();
+            mFreeNativeResources
+                = sRegistry.registerNativeAllocation(this, mNativeObject);
+        }
+
+        /**
+         * Apply the transaction, clearing it's state, and making it usable
+         * as a new transaction.
+         */
+        public void apply() {
+            apply(false);
+        }
+
+        /**
+         * Close the transaction, if the transaction was not already applied this will cancel the
+         * transaction.
+         */
+        @Override
+        public void close() {
+            mFreeNativeResources.run();
+            mNativeObject = 0;
+        }
+
+        /**
+         * Jankier version of apply. Avoid use (b/28068298).
+         */
+        public void apply(boolean sync) {
+            nativeApplyTransaction(mNativeObject, sync);
+        }
+
+        public Transaction show(SurfaceControl sc) {
+            nativeSetFlags(mNativeObject, sc.mNativeObject, 0, SURFACE_HIDDEN);
+            return this;
+        }
+
+        public Transaction hide(SurfaceControl sc) {
+            nativeSetFlags(mNativeObject, sc.mNativeObject, SURFACE_HIDDEN, SURFACE_HIDDEN);
+            return this;
+        }
+
+        public Transaction setPosition(SurfaceControl sc, float x, float y) {
+            nativeSetPosition(mNativeObject, sc.mNativeObject, x, y);
+            return this;
+        }
+
+        public Transaction setSize(SurfaceControl sc, int w, int h) {
+            nativeSetSize(mNativeObject, sc.mNativeObject,
+                    w, h);
+            return this;
+        }
+
+        public Transaction setLayer(SurfaceControl sc, int z) {
+            nativeSetLayer(mNativeObject, sc.mNativeObject, z);
+            return this;
+        }
+
+        public Transaction setRelativeLayer(SurfaceControl sc, IBinder relativeTo, int z) {
+            nativeSetRelativeLayer(mNativeObject, sc.mNativeObject,
+                    relativeTo, z);
+            return this;
+        }
+
+        public Transaction setTransparentRegionHint(SurfaceControl sc, Region transparentRegion) {
+            nativeSetTransparentRegionHint(mNativeObject,
+                    sc.mNativeObject, transparentRegion);
+            return this;
+        }
+
+        public Transaction setAlpha(SurfaceControl sc, float alpha) {
+            nativeSetAlpha(mNativeObject, sc.mNativeObject, alpha);
+            return this;
+        }
+
+        public Transaction setMatrix(SurfaceControl sc,
+                float dsdx, float dtdx, float dtdy, float dsdy) {
+            nativeSetMatrix(mNativeObject, sc.mNativeObject,
+                    dsdx, dtdx, dtdy, dsdy);
+            return this;
+        }
+
+        public Transaction setWindowCrop(SurfaceControl sc, Rect crop) {
+            if (crop != null) {
+                nativeSetWindowCrop(mNativeObject, sc.mNativeObject,
+                        crop.left, crop.top, crop.right, crop.bottom);
+            } else {
+                nativeSetWindowCrop(mNativeObject, sc.mNativeObject, 0, 0, 0, 0);
+            }
+
+            return this;
+        }
+
+        public Transaction setFinalCrop(SurfaceControl sc, Rect crop) {
+            if (crop != null) {
+                nativeSetFinalCrop(mNativeObject, sc.mNativeObject,
+                        crop.left, crop.top, crop.right, crop.bottom);
+            } else {
+                nativeSetFinalCrop(mNativeObject, sc.mNativeObject, 0, 0, 0, 0);
+            }
+
+            return this;
+        }
+
+        public Transaction setLayerStack(SurfaceControl sc, int layerStack) {
+            nativeSetLayerStack(mNativeObject, sc.mNativeObject, layerStack);
+            return this;
+        }
+
+        public Transaction deferTransactionUntil(SurfaceControl sc, IBinder handle, long frameNumber) {
+            nativeDeferTransactionUntil(mNativeObject, sc.mNativeObject, handle, frameNumber);
+            return this;
+        }
+
+        public Transaction deferTransactionUntilSurface(SurfaceControl sc, Surface barrierSurface,
+                long frameNumber) {
+            nativeDeferTransactionUntilSurface(mNativeObject, sc.mNativeObject,
+                    barrierSurface.mNativeObject, frameNumber);
+            return this;
+        }
+
+        public Transaction reparentChildren(SurfaceControl sc, IBinder newParentHandle) {
+            nativeReparentChildren(mNativeObject, sc.mNativeObject, newParentHandle);
+            return this;
+        }
+
+        /** Re-parents a specific child layer to a new parent */
+        public Transaction reparent(SurfaceControl sc, IBinder newParentHandle) {
+            nativeReparent(mNativeObject, sc.mNativeObject,
+                    newParentHandle);
+            return this;
+        }
+
+        public Transaction detachChildren(SurfaceControl sc) {
+            nativeSeverChildren(mNativeObject, sc.mNativeObject);
+            return this;
+        }
+
+        public Transaction setOverrideScalingMode(SurfaceControl sc, int overrideScalingMode) {
+            nativeSetOverrideScalingMode(mNativeObject, sc.mNativeObject,
+                    overrideScalingMode);
+            return this;
+        }
+
+        /**
+         * Sets a color for the Surface.
+         * @param color A float array with three values to represent r, g, b in range [0..1]
+         */
+        public Transaction setColor(SurfaceControl sc, @Size(3) float[] color) {
+            nativeSetColor(mNativeObject, sc.mNativeObject, color);
+            return this;
+        }
+
+        /**
+         * If the buffer size changes in this transaction, position and crop updates specified
+         * in this transaction will not complete until a buffer of the new size
+         * arrives. As transform matrix and size are already frozen in this fashion,
+         * this enables totally freezing the surface until the resize has completed
+         * (at which point the geometry influencing aspects of this transaction will then occur)
+         */
+        public Transaction setGeometryAppliesWithResize(SurfaceControl sc) {
+            nativeSetGeometryAppliesWithResize(mNativeObject, sc.mNativeObject);
+            return this;
+        }
+
+        /**
+         * Sets the security of the surface.  Setting the flag is equivalent to creating the
+         * Surface with the {@link #SECURE} flag.
+         */
+        Transaction setSecure(SurfaceControl sc, boolean isSecure) {
+            if (isSecure) {
+                nativeSetFlags(mNativeObject, sc.mNativeObject, SECURE, SECURE);
+            } else {
+                nativeSetFlags(mNativeObject, sc.mNativeObject, 0, SECURE);
+            }
+            return this;
+        }
+
+        /**
+         * Sets the opacity of the surface.  Setting the flag is equivalent to creating the
+         * Surface with the {@link #OPAQUE} flag.
+         */
+        public Transaction setOpaque(SurfaceControl sc, boolean isOpaque) {
+            if (isOpaque) {
+                nativeSetFlags(mNativeObject, sc.mNativeObject, SURFACE_OPAQUE, SURFACE_OPAQUE);
+            } else {
+                nativeSetFlags(mNativeObject, sc.mNativeObject, 0, SURFACE_OPAQUE);
+            }
+            return this;
+        }
+
+        public Transaction setDisplaySurface(IBinder displayToken, Surface surface) {
+            if (displayToken == null) {
+                throw new IllegalArgumentException("displayToken must not be null");
+            }
+
+            if (surface != null) {
+                synchronized (surface.mLock) {
+                    nativeSetDisplaySurface(mNativeObject, displayToken, surface.mNativeObject);
+                }
+            } else {
+                nativeSetDisplaySurface(mNativeObject, displayToken, 0);
+            }
+            return this;
+        }
+
+        public Transaction setDisplayLayerStack(IBinder displayToken, int layerStack) {
+            if (displayToken == null) {
+                throw new IllegalArgumentException("displayToken must not be null");
+            }
+            nativeSetDisplayLayerStack(mNativeObject, displayToken, layerStack);
+            return this;
+        }
+
+        public Transaction setDisplayProjection(IBinder displayToken,
+                int orientation, Rect layerStackRect, Rect displayRect) {
+            if (displayToken == null) {
+                throw new IllegalArgumentException("displayToken must not be null");
+            }
+            if (layerStackRect == null) {
+                throw new IllegalArgumentException("layerStackRect must not be null");
+            }
+            if (displayRect == null) {
+                throw new IllegalArgumentException("displayRect must not be null");
+            }
+            nativeSetDisplayProjection(mNativeObject, displayToken, orientation,
+                    layerStackRect.left, layerStackRect.top, layerStackRect.right, layerStackRect.bottom,
+                    displayRect.left, displayRect.top, displayRect.right, displayRect.bottom);
+            return this;
+        }
+
+        public Transaction setDisplaySize(IBinder displayToken, int width, int height) {
+            if (displayToken == null) {
+                throw new IllegalArgumentException("displayToken must not be null");
+            }
+            if (width <= 0 || height <= 0) {
+                throw new IllegalArgumentException("width and height must be positive");
+            }
+
+            nativeSetDisplaySize(mNativeObject, displayToken, width, height);
+            return this;
+        }
+
+        /** flag the transaction as an animation */
+        public Transaction setAnimationTransaction() {
+            nativeSetAnimationTransaction(mNativeObject);
+            return this;
+        }
+    }
 }
diff --git a/android/view/SurfaceView.java b/android/view/SurfaceView.java
index 462dad3..ebb2af4 100644
--- a/android/view/SurfaceView.java
+++ b/android/view/SurfaceView.java
@@ -16,1208 +16,115 @@
 
 package android.view;
 
-import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
-import static android.view.WindowManagerPolicy.APPLICATION_MEDIA_OVERLAY_SUBLAYER;
-import static android.view.WindowManagerPolicy.APPLICATION_MEDIA_SUBLAYER;
-import static android.view.WindowManagerPolicy.APPLICATION_PANEL_SUBLAYER;
+import com.android.layoutlib.bridge.MockView;
 
 import android.content.Context;
-import android.content.res.CompatibilityInfo.Translator;
-import android.content.res.Configuration;
 import android.graphics.Canvas;
-import android.graphics.PixelFormat;
-import android.graphics.PorterDuff;
 import android.graphics.Rect;
 import android.graphics.Region;
-import android.os.Build;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.SystemClock;
 import android.util.AttributeSet;
-import android.util.Log;
-
-import com.android.internal.view.SurfaceCallbackHelper;
-
-import java.util.ArrayList;
-import java.util.concurrent.locks.ReentrantLock;
 
 /**
- * Provides a dedicated drawing surface embedded inside of a view hierarchy.
- * You can control the format of this surface and, if you like, its size; the
- * SurfaceView takes care of placing the surface at the correct location on the
- * screen
+ * Mock version of the SurfaceView.
+ * Only non override public methods from the real SurfaceView have been added in there.
+ * Methods that take an unknown class as parameter or as return object, have been removed for now.
  *
- * <p>The surface is Z ordered so that it is behind the window holding its
- * SurfaceView; the SurfaceView punches a hole in its window to allow its
- * surface to be displayed. The view hierarchy will take care of correctly
- * compositing with the Surface any siblings of the SurfaceView that would
- * normally appear on top of it. This can be used to place overlays such as
- * buttons on top of the Surface, though note however that it can have an
- * impact on performance since a full alpha-blended composite will be performed
- * each time the Surface changes.
+ * TODO: generate automatically.
  *
- * <p> The transparent region that makes the surface visible is based on the
- * layout positions in the view hierarchy. If the post-layout transform
- * properties are used to draw a sibling view on top of the SurfaceView, the
- * view may not be properly composited with the surface.
- *
- * <p>Access to the underlying surface is provided via the SurfaceHolder interface,
- * which can be retrieved by calling {@link #getHolder}.
- *
- * <p>The Surface will be created for you while the SurfaceView's window is
- * visible; you should implement {@link SurfaceHolder.Callback#surfaceCreated}
- * and {@link SurfaceHolder.Callback#surfaceDestroyed} to discover when the
- * Surface is created and destroyed as the window is shown and hidden.
- *
- * <p>One of the purposes of this class is to provide a surface in which a
- * secondary thread can render into the screen. If you are going to use it
- * this way, you need to be aware of some threading semantics:
- *
- * <ul>
- * <li> All SurfaceView and
- * {@link SurfaceHolder.Callback SurfaceHolder.Callback} methods will be called
- * from the thread running the SurfaceView's window (typically the main thread
- * of the application). They thus need to correctly synchronize with any
- * state that is also touched by the drawing thread.
- * <li> You must ensure that the drawing thread only touches the underlying
- * Surface while it is valid -- between
- * {@link SurfaceHolder.Callback#surfaceCreated SurfaceHolder.Callback.surfaceCreated()}
- * and
- * {@link SurfaceHolder.Callback#surfaceDestroyed SurfaceHolder.Callback.surfaceDestroyed()}.
- * </ul>
- *
- * <p class="note"><strong>Note:</strong> Starting in platform version
- * {@link android.os.Build.VERSION_CODES#N}, SurfaceView's window position is
- * updated synchronously with other View rendering. This means that translating
- * and scaling a SurfaceView on screen will not cause rendering artifacts. Such
- * artifacts may occur on previous versions of the platform when its window is
- * positioned asynchronously.</p>
  */
-public class SurfaceView extends View implements ViewRootImpl.WindowStoppedCallback {
-    private static final String TAG = "SurfaceView";
-    private static final boolean DEBUG = false;
-
-    final ArrayList<SurfaceHolder.Callback> mCallbacks
-            = new ArrayList<SurfaceHolder.Callback>();
-
-    final int[] mLocation = new int[2];
-
-    final ReentrantLock mSurfaceLock = new ReentrantLock();
-    final Surface mSurface = new Surface();       // Current surface in use
-    boolean mDrawingStopped = true;
-    // We use this to track if the application has produced a frame
-    // in to the Surface. Up until that point, we should be careful not to punch
-    // holes.
-    boolean mDrawFinished = false;
-
-    final Rect mScreenRect = new Rect();
-    SurfaceSession mSurfaceSession;
-
-    SurfaceControl mSurfaceControl;
-    // In the case of format changes we switch out the surface in-place
-    // we need to preserve the old one until the new one has drawn.
-    SurfaceControl mDeferredDestroySurfaceControl;
-    final Rect mTmpRect = new Rect();
-    final Configuration mConfiguration = new Configuration();
-
-    int mSubLayer = APPLICATION_MEDIA_SUBLAYER;
-
-    boolean mIsCreating = false;
-    private volatile boolean mRtHandlingPositionUpdates = false;
-
-    private final ViewTreeObserver.OnScrollChangedListener mScrollChangedListener
-            = new ViewTreeObserver.OnScrollChangedListener() {
-                    @Override
-                    public void onScrollChanged() {
-                        updateSurface();
-                    }
-            };
-
-    private final ViewTreeObserver.OnPreDrawListener mDrawListener =
-            new ViewTreeObserver.OnPreDrawListener() {
-                @Override
-                public boolean onPreDraw() {
-                    // reposition ourselves where the surface is
-                    mHaveFrame = getWidth() > 0 && getHeight() > 0;
-                    updateSurface();
-                    return true;
-                }
-            };
-
-    boolean mRequestedVisible = false;
-    boolean mWindowVisibility = false;
-    boolean mLastWindowVisibility = false;
-    boolean mViewVisibility = false;
-    boolean mWindowStopped = false;
-
-    int mRequestedWidth = -1;
-    int mRequestedHeight = -1;
-    /* Set SurfaceView's format to 565 by default to maintain backward
-     * compatibility with applications assuming this format.
-     */
-    int mRequestedFormat = PixelFormat.RGB_565;
-
-    boolean mHaveFrame = false;
-    boolean mSurfaceCreated = false;
-    long mLastLockTime = 0;
-
-    boolean mVisible = false;
-    int mWindowSpaceLeft = -1;
-    int mWindowSpaceTop = -1;
-    int mSurfaceWidth = -1;
-    int mSurfaceHeight = -1;
-    int mFormat = -1;
-    final Rect mSurfaceFrame = new Rect();
-    int mLastSurfaceWidth = -1, mLastSurfaceHeight = -1;
-    private Translator mTranslator;
-
-    private boolean mGlobalListenersAdded;
-    private boolean mAttachedToWindow;
-
-    private int mSurfaceFlags = SurfaceControl.HIDDEN;
-
-    private int mPendingReportDraws;
+public class SurfaceView extends MockView {
 
     public SurfaceView(Context context) {
         this(context, null);
     }
 
     public SurfaceView(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
+        this(context, attrs , 0);
     }
 
-    public SurfaceView(Context context, AttributeSet attrs, int defStyleAttr) {
-        this(context, attrs, defStyleAttr, 0);
+    public SurfaceView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
     }
 
     public SurfaceView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
-        mRenderNode.requestPositionUpdates(this);
-
-        setWillNotDraw(true);
     }
 
-    /**
-     * Return the SurfaceHolder providing access and control over this
-     * SurfaceView's underlying surface.
-     *
-     * @return SurfaceHolder The holder of the surface.
-     */
+    public boolean gatherTransparentRegion(Region region) {
+      return false;
+    }
+
+    public void setZOrderMediaOverlay(boolean isMediaOverlay) {
+    }
+
+    public void setZOrderOnTop(boolean onTop) {
+    }
+
+    public void setSecure(boolean isSecure) {
+    }
+
     public SurfaceHolder getHolder() {
         return mSurfaceHolder;
     }
 
-    private void updateRequestedVisibility() {
-        mRequestedVisible = mViewVisibility && mWindowVisibility && !mWindowStopped;
-    }
-
-    /** @hide */
-    @Override
-    public void windowStopped(boolean stopped) {
-        mWindowStopped = stopped;
-        updateRequestedVisibility();
-        updateSurface();
-    }
-
-    @Override
-    protected void onAttachedToWindow() {
-        super.onAttachedToWindow();
-
-        getViewRootImpl().addWindowStoppedCallback(this);
-        mWindowStopped = false;
-
-        mViewVisibility = getVisibility() == VISIBLE;
-        updateRequestedVisibility();
-
-        mAttachedToWindow = true;
-        mParent.requestTransparentRegion(SurfaceView.this);
-        if (!mGlobalListenersAdded) {
-            ViewTreeObserver observer = getViewTreeObserver();
-            observer.addOnScrollChangedListener(mScrollChangedListener);
-            observer.addOnPreDrawListener(mDrawListener);
-            mGlobalListenersAdded = true;
-        }
-    }
-
-    @Override
-    protected void onWindowVisibilityChanged(int visibility) {
-        super.onWindowVisibilityChanged(visibility);
-        mWindowVisibility = visibility == VISIBLE;
-        updateRequestedVisibility();
-        updateSurface();
-    }
-
-    @Override
-    public void setVisibility(int visibility) {
-        super.setVisibility(visibility);
-        mViewVisibility = visibility == VISIBLE;
-        boolean newRequestedVisible = mWindowVisibility && mViewVisibility && !mWindowStopped;
-        if (newRequestedVisible != mRequestedVisible) {
-            // our base class (View) invalidates the layout only when
-            // we go from/to the GONE state. However, SurfaceView needs
-            // to request a re-layout when the visibility changes at all.
-            // This is needed because the transparent region is computed
-            // as part of the layout phase, and it changes (obviously) when
-            // the visibility changes.
-            requestLayout();
-        }
-        mRequestedVisible = newRequestedVisible;
-        updateSurface();
-    }
-
-    private void performDrawFinished() {
-        if (mPendingReportDraws > 0) {
-            mDrawFinished = true;
-            if (mAttachedToWindow) {
-                notifyDrawFinished();
-                invalidate();
-            }
-        } else {
-            Log.e(TAG, System.identityHashCode(this) + "finished drawing"
-                    + " but no pending report draw (extra call"
-                    + " to draw completion runnable?)");
-        }
-    }
-
-    void notifyDrawFinished() {
-        ViewRootImpl viewRoot = getViewRootImpl();
-        if (viewRoot != null) {
-            viewRoot.pendingDrawFinished();
-        }
-        mPendingReportDraws--;
-    }
-
-    @Override
-    protected void onDetachedFromWindow() {
-        ViewRootImpl viewRoot = getViewRootImpl();
-        // It's possible to create a SurfaceView using the default constructor and never
-        // attach it to a view hierarchy, this is a common use case when dealing with
-        // OpenGL. A developer will probably create a new GLSurfaceView, and let it manage
-        // the lifecycle. Instead of attaching it to a view, he/she can just pass
-        // the SurfaceHolder forward, most live wallpapers do it.
-        if (viewRoot != null) {
-            viewRoot.removeWindowStoppedCallback(this);
-        }
-
-        mAttachedToWindow = false;
-        if (mGlobalListenersAdded) {
-            ViewTreeObserver observer = getViewTreeObserver();
-            observer.removeOnScrollChangedListener(mScrollChangedListener);
-            observer.removeOnPreDrawListener(mDrawListener);
-            mGlobalListenersAdded = false;
-        }
-
-        while (mPendingReportDraws > 0) {
-            notifyDrawFinished();
-        }
-
-        mRequestedVisible = false;
-
-        updateSurface();
-        if (mSurfaceControl != null) {
-            mSurfaceControl.destroy();
-        }
-        mSurfaceControl = null;
-
-        mHaveFrame = false;
-
-        super.onDetachedFromWindow();
-    }
-
-    @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        int width = mRequestedWidth >= 0
-                ? resolveSizeAndState(mRequestedWidth, widthMeasureSpec, 0)
-                : getDefaultSize(0, widthMeasureSpec);
-        int height = mRequestedHeight >= 0
-                ? resolveSizeAndState(mRequestedHeight, heightMeasureSpec, 0)
-                : getDefaultSize(0, heightMeasureSpec);
-        setMeasuredDimension(width, height);
-    }
-
-    /** @hide */
-    @Override
-    protected boolean setFrame(int left, int top, int right, int bottom) {
-        boolean result = super.setFrame(left, top, right, bottom);
-        updateSurface();
-        return result;
-    }
-
-    @Override
-    public boolean gatherTransparentRegion(Region region) {
-        if (isAboveParent() || !mDrawFinished) {
-            return super.gatherTransparentRegion(region);
-        }
-
-        boolean opaque = true;
-        if ((mPrivateFlags & PFLAG_SKIP_DRAW) == 0) {
-            // this view draws, remove it from the transparent region
-            opaque = super.gatherTransparentRegion(region);
-        } else if (region != null) {
-            int w = getWidth();
-            int h = getHeight();
-            if (w>0 && h>0) {
-                getLocationInWindow(mLocation);
-                // otherwise, punch a hole in the whole hierarchy
-                int l = mLocation[0];
-                int t = mLocation[1];
-                region.op(l, t, l+w, t+h, Region.Op.UNION);
-            }
-        }
-        if (PixelFormat.formatHasAlpha(mRequestedFormat)) {
-            opaque = false;
-        }
-        return opaque;
-    }
-
-    @Override
-    public void draw(Canvas canvas) {
-        if (mDrawFinished && !isAboveParent()) {
-            // draw() is not called when SKIP_DRAW is set
-            if ((mPrivateFlags & PFLAG_SKIP_DRAW) == 0) {
-                // punch a whole in the view-hierarchy below us
-                canvas.drawColor(0, PorterDuff.Mode.CLEAR);
-            }
-        }
-        super.draw(canvas);
-    }
-
-    @Override
-    protected void dispatchDraw(Canvas canvas) {
-        if (mDrawFinished && !isAboveParent()) {
-            // draw() is not called when SKIP_DRAW is set
-            if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
-                // punch a whole in the view-hierarchy below us
-                canvas.drawColor(0, PorterDuff.Mode.CLEAR);
-            }
-        }
-        super.dispatchDraw(canvas);
-    }
-
-    /**
-     * Control whether the surface view's surface is placed on top of another
-     * regular surface view in the window (but still behind the window itself).
-     * This is typically used to place overlays on top of an underlying media
-     * surface view.
-     *
-     * <p>Note that this must be set before the surface view's containing
-     * window is attached to the window manager.
-     *
-     * <p>Calling this overrides any previous call to {@link #setZOrderOnTop}.
-     */
-    public void setZOrderMediaOverlay(boolean isMediaOverlay) {
-        mSubLayer = isMediaOverlay
-            ? APPLICATION_MEDIA_OVERLAY_SUBLAYER : APPLICATION_MEDIA_SUBLAYER;
-    }
-
-    /**
-     * Control whether the surface view's surface is placed on top of its
-     * window.  Normally it is placed behind the window, to allow it to
-     * (for the most part) appear to composite with the views in the
-     * hierarchy.  By setting this, you cause it to be placed above the
-     * window.  This means that none of the contents of the window this
-     * SurfaceView is in will be visible on top of its surface.
-     *
-     * <p>Note that this must be set before the surface view's containing
-     * window is attached to the window manager.
-     *
-     * <p>Calling this overrides any previous call to {@link #setZOrderMediaOverlay}.
-     */
-    public void setZOrderOnTop(boolean onTop) {
-        if (onTop) {
-            mSubLayer = APPLICATION_PANEL_SUBLAYER;
-        } else {
-            mSubLayer = APPLICATION_MEDIA_SUBLAYER;
-        }
-    }
-
-    /**
-     * Control whether the surface view's content should be treated as secure,
-     * preventing it from appearing in screenshots or from being viewed on
-     * non-secure displays.
-     *
-     * <p>Note that this must be set before the surface view's containing
-     * window is attached to the window manager.
-     *
-     * <p>See {@link android.view.Display#FLAG_SECURE} for details.
-     *
-     * @param isSecure True if the surface view is secure.
-     */
-    public void setSecure(boolean isSecure) {
-        if (isSecure) {
-            mSurfaceFlags |= SurfaceControl.SECURE;
-        } else {
-            mSurfaceFlags &= ~SurfaceControl.SECURE;
-        }
-    }
-
-    private void updateOpaqueFlag() {
-        if (!PixelFormat.formatHasAlpha(mRequestedFormat)) {
-            mSurfaceFlags |= SurfaceControl.OPAQUE;
-        } else {
-            mSurfaceFlags &= ~SurfaceControl.OPAQUE;
-        }
-    }
-
-    private Rect getParentSurfaceInsets() {
-        final ViewRootImpl root = getViewRootImpl();
-        if (root == null) {
-            return null;
-        } else {
-            return root.mWindowAttributes.surfaceInsets;
-        }
-    }
-
-    /** @hide */
-    protected void updateSurface() {
-        if (!mHaveFrame) {
-            return;
-        }
-        ViewRootImpl viewRoot = getViewRootImpl();
-        if (viewRoot == null || viewRoot.mSurface == null || !viewRoot.mSurface.isValid()) {
-            return;
-        }
-
-        mTranslator = viewRoot.mTranslator;
-        if (mTranslator != null) {
-            mSurface.setCompatibilityTranslator(mTranslator);
-        }
-
-        int myWidth = mRequestedWidth;
-        if (myWidth <= 0) myWidth = getWidth();
-        int myHeight = mRequestedHeight;
-        if (myHeight <= 0) myHeight = getHeight();
-
-        final boolean formatChanged = mFormat != mRequestedFormat;
-        final boolean visibleChanged = mVisible != mRequestedVisible;
-        final boolean creating = (mSurfaceControl == null || formatChanged || visibleChanged)
-                && mRequestedVisible;
-        final boolean sizeChanged = mSurfaceWidth != myWidth || mSurfaceHeight != myHeight;
-        final boolean windowVisibleChanged = mWindowVisibility != mLastWindowVisibility;
-        boolean redrawNeeded = false;
-
-        if (creating || formatChanged || sizeChanged || visibleChanged || windowVisibleChanged) {
-            getLocationInWindow(mLocation);
-
-            if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
-                    + "Changes: creating=" + creating
-                    + " format=" + formatChanged + " size=" + sizeChanged
-                    + " visible=" + visibleChanged
-                    + " left=" + (mWindowSpaceLeft != mLocation[0])
-                    + " top=" + (mWindowSpaceTop != mLocation[1]));
-
-            try {
-                final boolean visible = mVisible = mRequestedVisible;
-                mWindowSpaceLeft = mLocation[0];
-                mWindowSpaceTop = mLocation[1];
-                mSurfaceWidth = myWidth;
-                mSurfaceHeight = myHeight;
-                mFormat = mRequestedFormat;
-                mLastWindowVisibility = mWindowVisibility;
-
-                mScreenRect.left = mWindowSpaceLeft;
-                mScreenRect.top = mWindowSpaceTop;
-                mScreenRect.right = mWindowSpaceLeft + getWidth();
-                mScreenRect.bottom = mWindowSpaceTop + getHeight();
-                if (mTranslator != null) {
-                    mTranslator.translateRectInAppWindowToScreen(mScreenRect);
-                }
-
-                final Rect surfaceInsets = getParentSurfaceInsets();
-                mScreenRect.offset(surfaceInsets.left, surfaceInsets.top);
-
-                if (creating) {
-                    mSurfaceSession = new SurfaceSession(viewRoot.mSurface);
-                    mDeferredDestroySurfaceControl = mSurfaceControl;
-
-                    updateOpaqueFlag();
-                    mSurfaceControl = new SurfaceControlWithBackground(mSurfaceSession,
-                            "SurfaceView - " + viewRoot.getTitle().toString(),
-                            mSurfaceWidth, mSurfaceHeight, mFormat,
-                            mSurfaceFlags);
-                } else if (mSurfaceControl == null) {
-                    return;
-                }
-
-                boolean realSizeChanged = false;
-
-                mSurfaceLock.lock();
-                try {
-                    mDrawingStopped = !visible;
-
-                    if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
-                            + "Cur surface: " + mSurface);
-
-                    SurfaceControl.openTransaction();
-                    try {
-                        mSurfaceControl.setLayer(mSubLayer);
-                        if (mViewVisibility) {
-                            mSurfaceControl.show();
-                        } else {
-                            mSurfaceControl.hide();
-                        }
-
-                        // While creating the surface, we will set it's initial
-                        // geometry. Outside of that though, we should generally
-                        // leave it to the RenderThread.
-                        //
-                        // There is one more case when the buffer size changes we aren't yet
-                        // prepared to sync (as even following the transaction applying
-                        // we still need to latch a buffer).
-                        // b/28866173
-                        if (sizeChanged || creating || !mRtHandlingPositionUpdates) {
-                            mSurfaceControl.setPosition(mScreenRect.left, mScreenRect.top);
-                            mSurfaceControl.setMatrix(mScreenRect.width() / (float) mSurfaceWidth,
-                                    0.0f, 0.0f,
-                                    mScreenRect.height() / (float) mSurfaceHeight);
-                        }
-                        if (sizeChanged) {
-                            mSurfaceControl.setSize(mSurfaceWidth, mSurfaceHeight);
-                        }
-                    } finally {
-                        SurfaceControl.closeTransaction();
-                    }
-
-                    if (sizeChanged || creating) {
-                        redrawNeeded = true;
-                    }
-
-                    mSurfaceFrame.left = 0;
-                    mSurfaceFrame.top = 0;
-                    if (mTranslator == null) {
-                        mSurfaceFrame.right = mSurfaceWidth;
-                        mSurfaceFrame.bottom = mSurfaceHeight;
-                    } else {
-                        float appInvertedScale = mTranslator.applicationInvertedScale;
-                        mSurfaceFrame.right = (int) (mSurfaceWidth * appInvertedScale + 0.5f);
-                        mSurfaceFrame.bottom = (int) (mSurfaceHeight * appInvertedScale + 0.5f);
-                    }
-
-                    final int surfaceWidth = mSurfaceFrame.right;
-                    final int surfaceHeight = mSurfaceFrame.bottom;
-                    realSizeChanged = mLastSurfaceWidth != surfaceWidth
-                            || mLastSurfaceHeight != surfaceHeight;
-                    mLastSurfaceWidth = surfaceWidth;
-                    mLastSurfaceHeight = surfaceHeight;
-                } finally {
-                    mSurfaceLock.unlock();
-                }
-
-                try {
-                    redrawNeeded |= visible && !mDrawFinished;
-
-                    SurfaceHolder.Callback callbacks[] = null;
-
-                    final boolean surfaceChanged = creating;
-                    if (mSurfaceCreated && (surfaceChanged || (!visible && visibleChanged))) {
-                        mSurfaceCreated = false;
-                        if (mSurface.isValid()) {
-                            if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
-                                    + "visibleChanged -- surfaceDestroyed");
-                            callbacks = getSurfaceCallbacks();
-                            for (SurfaceHolder.Callback c : callbacks) {
-                                c.surfaceDestroyed(mSurfaceHolder);
-                            }
-                            // Since Android N the same surface may be reused and given to us
-                            // again by the system server at a later point. However
-                            // as we didn't do this in previous releases, clients weren't
-                            // necessarily required to clean up properly in
-                            // surfaceDestroyed. This leads to problems for example when
-                            // clients don't destroy their EGL context, and try
-                            // and create a new one on the same surface following reuse.
-                            // Since there is no valid use of the surface in-between
-                            // surfaceDestroyed and surfaceCreated, we force a disconnect,
-                            // so the next connect will always work if we end up reusing
-                            // the surface.
-                            if (mSurface.isValid()) {
-                                mSurface.forceScopedDisconnect();
-                            }
-                        }
-                    }
-
-                    if (creating) {
-                        mSurface.copyFrom(mSurfaceControl);
-                    }
-
-                    if (sizeChanged && getContext().getApplicationInfo().targetSdkVersion
-                            < Build.VERSION_CODES.O) {
-                        // Some legacy applications use the underlying native {@link Surface} object
-                        // as a key to whether anything has changed. In these cases, updates to the
-                        // existing {@link Surface} will be ignored when the size changes.
-                        // Therefore, we must explicitly recreate the {@link Surface} in these
-                        // cases.
-                        mSurface.createFrom(mSurfaceControl);
-                    }
-
-                    if (visible && mSurface.isValid()) {
-                        if (!mSurfaceCreated && (surfaceChanged || visibleChanged)) {
-                            mSurfaceCreated = true;
-                            mIsCreating = true;
-                            if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
-                                    + "visibleChanged -- surfaceCreated");
-                            if (callbacks == null) {
-                                callbacks = getSurfaceCallbacks();
-                            }
-                            for (SurfaceHolder.Callback c : callbacks) {
-                                c.surfaceCreated(mSurfaceHolder);
-                            }
-                        }
-                        if (creating || formatChanged || sizeChanged
-                                || visibleChanged || realSizeChanged) {
-                            if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
-                                    + "surfaceChanged -- format=" + mFormat
-                                    + " w=" + myWidth + " h=" + myHeight);
-                            if (callbacks == null) {
-                                callbacks = getSurfaceCallbacks();
-                            }
-                            for (SurfaceHolder.Callback c : callbacks) {
-                                c.surfaceChanged(mSurfaceHolder, mFormat, myWidth, myHeight);
-                            }
-                        }
-                        if (redrawNeeded) {
-                            if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
-                                    + "surfaceRedrawNeeded");
-                            if (callbacks == null) {
-                                callbacks = getSurfaceCallbacks();
-                            }
-
-                            mPendingReportDraws++;
-                            viewRoot.drawPending();
-                            SurfaceCallbackHelper sch =
-                                    new SurfaceCallbackHelper(this::onDrawFinished);
-                            sch.dispatchSurfaceRedrawNeededAsync(mSurfaceHolder, callbacks);
-                        }
-                    }
-                } finally {
-                    mIsCreating = false;
-                    if (mSurfaceControl != null && !mSurfaceCreated) {
-                        mSurface.release();
-                        // If we are not in the stopped state, then the destruction of the Surface
-                        // represents a visual change we need to display, and we should go ahead
-                        // and destroy the SurfaceControl. However if we are in the stopped state,
-                        // we can just leave the Surface around so it can be a part of animations,
-                        // and we let the life-time be tied to the parent surface.
-                        if (!mWindowStopped) {
-                            mSurfaceControl.destroy();
-                            mSurfaceControl = null;
-                        }
-                    }
-                }
-            } catch (Exception ex) {
-                Log.e(TAG, "Exception configuring surface", ex);
-            }
-            if (DEBUG) Log.v(
-                TAG, "Layout: x=" + mScreenRect.left + " y=" + mScreenRect.top
-                + " w=" + mScreenRect.width() + " h=" + mScreenRect.height()
-                + ", frame=" + mSurfaceFrame);
-        } else {
-            // Calculate the window position in case RT loses the window
-            // and we need to fallback to a UI-thread driven position update
-            getLocationInSurface(mLocation);
-            final boolean positionChanged = mWindowSpaceLeft != mLocation[0]
-                    || mWindowSpaceTop != mLocation[1];
-            final boolean layoutSizeChanged = getWidth() != mScreenRect.width()
-                    || getHeight() != mScreenRect.height();
-            if (positionChanged || layoutSizeChanged) { // Only the position has changed
-                mWindowSpaceLeft = mLocation[0];
-                mWindowSpaceTop = mLocation[1];
-                // For our size changed check, we keep mScreenRect.width() and mScreenRect.height()
-                // in view local space.
-                mLocation[0] = getWidth();
-                mLocation[1] = getHeight();
-
-                mScreenRect.set(mWindowSpaceLeft, mWindowSpaceTop,
-                        mWindowSpaceLeft + mLocation[0], mWindowSpaceTop + mLocation[1]);
-
-                if (mTranslator != null) {
-                    mTranslator.translateRectInAppWindowToScreen(mScreenRect);
-                }
-
-                if (mSurfaceControl == null) {
-                    return;
-                }
-
-                if (!isHardwareAccelerated() || !mRtHandlingPositionUpdates) {
-                    try {
-                        if (DEBUG) Log.d(TAG, String.format("%d updateSurfacePosition UI, " +
-                                "postion = [%d, %d, %d, %d]", System.identityHashCode(this),
-                                mScreenRect.left, mScreenRect.top,
-                                mScreenRect.right, mScreenRect.bottom));
-                        setParentSpaceRectangle(mScreenRect, -1);
-                    } catch (Exception ex) {
-                        Log.e(TAG, "Exception configuring surface", ex);
-                    }
-                }
-            }
-        }
-    }
-
-    private void onDrawFinished() {
-        if (DEBUG) {
-            Log.i(TAG, System.identityHashCode(this) + " "
-                    + "finishedDrawing");
-        }
-
-        if (mDeferredDestroySurfaceControl != null) {
-            mDeferredDestroySurfaceControl.destroy();
-            mDeferredDestroySurfaceControl = null;
-        }
-
-        runOnUiThread(() -> {
-            performDrawFinished();
-        });
-    }
-
-    private void setParentSpaceRectangle(Rect position, long frameNumber) {
-        ViewRootImpl viewRoot = getViewRootImpl();
-
-        SurfaceControl.openTransaction();
-        try {
-            if (frameNumber > 0) {
-                mSurfaceControl.deferTransactionUntil(viewRoot.mSurface, frameNumber);
-            }
-            mSurfaceControl.setPosition(position.left, position.top);
-            mSurfaceControl.setMatrix(position.width() / (float) mSurfaceWidth,
-                    0.0f, 0.0f,
-                    position.height() / (float) mSurfaceHeight);
-        } finally {
-            SurfaceControl.closeTransaction();
-        }
-    }
-
-    private Rect mRTLastReportedPosition = new Rect();
-
-    /**
-     * Called by native by a Rendering Worker thread to update the window position
-     * @hide
-     */
-    public final void updateSurfacePosition_renderWorker(long frameNumber,
-            int left, int top, int right, int bottom) {
-        if (mSurfaceControl == null) {
-            return;
-        }
-
-        // TODO: This is teensy bit racey in that a brand new SurfaceView moving on
-        // its 2nd frame if RenderThread is running slowly could potentially see
-        // this as false, enter the branch, get pre-empted, then this comes along
-        // and reports a new position, then the UI thread resumes and reports
-        // its position. This could therefore be de-sync'd in that interval, but
-        // the synchronization would violate the rule that RT must never block
-        // on the UI thread which would open up potential deadlocks. The risk of
-        // a single-frame desync is therefore preferable for now.
-        mRtHandlingPositionUpdates = true;
-        if (mRTLastReportedPosition.left == left
-                && mRTLastReportedPosition.top == top
-                && mRTLastReportedPosition.right == right
-                && mRTLastReportedPosition.bottom == bottom) {
-            return;
-        }
-        try {
-            if (DEBUG) {
-                Log.d(TAG, String.format("%d updateSurfacePosition RenderWorker, frameNr = %d, " +
-                        "postion = [%d, %d, %d, %d]", System.identityHashCode(this),
-                        frameNumber, left, top, right, bottom));
-            }
-            mRTLastReportedPosition.set(left, top, right, bottom);
-            setParentSpaceRectangle(mRTLastReportedPosition, frameNumber);
-            // Now overwrite mRTLastReportedPosition with our values
-        } catch (Exception ex) {
-            Log.e(TAG, "Exception from repositionChild", ex);
-        }
-    }
-
-    /**
-     * Called by native on RenderThread to notify that the view is no longer in the
-     * draw tree. UI thread is blocked at this point.
-     * @hide
-     */
-    public final void surfacePositionLost_uiRtSync(long frameNumber) {
-        if (DEBUG) {
-            Log.d(TAG, String.format("%d windowPositionLost, frameNr = %d",
-                    System.identityHashCode(this), frameNumber));
-        }
-        mRTLastReportedPosition.setEmpty();
-
-        if (mSurfaceControl == null) {
-            return;
-        }
-        if (mRtHandlingPositionUpdates) {
-            mRtHandlingPositionUpdates = false;
-            // This callback will happen while the UI thread is blocked, so we can
-            // safely access other member variables at this time.
-            // So do what the UI thread would have done if RT wasn't handling position
-            // updates.
-            if (!mScreenRect.isEmpty() && !mScreenRect.equals(mRTLastReportedPosition)) {
-                try {
-                    if (DEBUG) Log.d(TAG, String.format("%d updateSurfacePosition, " +
-                            "postion = [%d, %d, %d, %d]", System.identityHashCode(this),
-                            mScreenRect.left, mScreenRect.top,
-                            mScreenRect.right, mScreenRect.bottom));
-                    setParentSpaceRectangle(mScreenRect, frameNumber);
-                } catch (Exception ex) {
-                    Log.e(TAG, "Exception configuring surface", ex);
-                }
-            }
-        }
-    }
-
-    private SurfaceHolder.Callback[] getSurfaceCallbacks() {
-        SurfaceHolder.Callback callbacks[];
-        synchronized (mCallbacks) {
-            callbacks = new SurfaceHolder.Callback[mCallbacks.size()];
-            mCallbacks.toArray(callbacks);
-        }
-        return callbacks;
-    }
-
-    /**
-     * This method still exists only for compatibility reasons because some applications have relied
-     * on this method via reflection. See Issue 36345857 for details.
-     *
-     * @deprecated No platform code is using this method anymore.
-     * @hide
-     */
-    @Deprecated
-    public void setWindowType(int type) {
-        if (getContext().getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.O) {
-            throw new UnsupportedOperationException(
-                    "SurfaceView#setWindowType() has never been a public API.");
-        }
-
-        if (type == TYPE_APPLICATION_PANEL) {
-            Log.e(TAG, "If you are calling SurfaceView#setWindowType(TYPE_APPLICATION_PANEL) "
-                    + "just to make the SurfaceView to be placed on top of its window, you must "
-                    + "call setZOrderOnTop(true) instead.", new Throwable());
-            setZOrderOnTop(true);
-            return;
-        }
-        Log.e(TAG, "SurfaceView#setWindowType(int) is deprecated and now does nothing. "
-                + "type=" + type, new Throwable());
-    }
-
-    private void runOnUiThread(Runnable runnable) {
-        Handler handler = getHandler();
-        if (handler != null && handler.getLooper() != Looper.myLooper()) {
-            handler.post(runnable);
-        } else {
-            runnable.run();
-        }
-    }
-
-    /**
-     * Check to see if the surface has fixed size dimensions or if the surface's
-     * dimensions are dimensions are dependent on its current layout.
-     *
-     * @return true if the surface has dimensions that are fixed in size
-     * @hide
-     */
-    public boolean isFixedSize() {
-        return (mRequestedWidth != -1 || mRequestedHeight != -1);
-    }
-
-    private boolean isAboveParent() {
-        return mSubLayer >= 0;
-    }
-
-    private final SurfaceHolder mSurfaceHolder = new SurfaceHolder() {
-        private static final String LOG_TAG = "SurfaceHolder";
+    private SurfaceHolder mSurfaceHolder = new SurfaceHolder() {
 
         @Override
         public boolean isCreating() {
-            return mIsCreating;
+            return false;
         }
 
         @Override
         public void addCallback(Callback callback) {
-            synchronized (mCallbacks) {
-                // This is a linear search, but in practice we'll
-                // have only a couple callbacks, so it doesn't matter.
-                if (mCallbacks.contains(callback) == false) {
-                    mCallbacks.add(callback);
-                }
-            }
         }
 
         @Override
         public void removeCallback(Callback callback) {
-            synchronized (mCallbacks) {
-                mCallbacks.remove(callback);
-            }
         }
 
         @Override
         public void setFixedSize(int width, int height) {
-            if (mRequestedWidth != width || mRequestedHeight != height) {
-                mRequestedWidth = width;
-                mRequestedHeight = height;
-                requestLayout();
-            }
         }
 
         @Override
         public void setSizeFromLayout() {
-            if (mRequestedWidth != -1 || mRequestedHeight != -1) {
-                mRequestedWidth = mRequestedHeight = -1;
-                requestLayout();
-            }
         }
 
         @Override
         public void setFormat(int format) {
-            // for backward compatibility reason, OPAQUE always
-            // means 565 for SurfaceView
-            if (format == PixelFormat.OPAQUE)
-                format = PixelFormat.RGB_565;
-
-            mRequestedFormat = format;
-            if (mSurfaceControl != null) {
-                updateSurface();
-            }
         }
 
-        /**
-         * @deprecated setType is now ignored.
-         */
         @Override
-        @Deprecated
-        public void setType(int type) { }
+        public void setType(int type) {
+        }
 
         @Override
         public void setKeepScreenOn(boolean screenOn) {
-            runOnUiThread(() -> SurfaceView.this.setKeepScreenOn(screenOn));
         }
 
-        /**
-         * Gets a {@link Canvas} for drawing into the SurfaceView's Surface
-         *
-         * After drawing into the provided {@link Canvas}, the caller must
-         * invoke {@link #unlockCanvasAndPost} to post the new contents to the surface.
-         *
-         * The caller must redraw the entire surface.
-         * @return A canvas for drawing into the surface.
-         */
         @Override
         public Canvas lockCanvas() {
-            return internalLockCanvas(null, false);
-        }
-
-        /**
-         * Gets a {@link Canvas} for drawing into the SurfaceView's Surface
-         *
-         * After drawing into the provided {@link Canvas}, the caller must
-         * invoke {@link #unlockCanvasAndPost} to post the new contents to the surface.
-         *
-         * @param inOutDirty A rectangle that represents the dirty region that the caller wants
-         * to redraw.  This function may choose to expand the dirty rectangle if for example
-         * the surface has been resized or if the previous contents of the surface were
-         * not available.  The caller must redraw the entire dirty region as represented
-         * by the contents of the inOutDirty rectangle upon return from this function.
-         * The caller may also pass <code>null</code> instead, in the case where the
-         * entire surface should be redrawn.
-         * @return A canvas for drawing into the surface.
-         */
-        @Override
-        public Canvas lockCanvas(Rect inOutDirty) {
-            return internalLockCanvas(inOutDirty, false);
-        }
-
-        @Override
-        public Canvas lockHardwareCanvas() {
-            return internalLockCanvas(null, true);
-        }
-
-        private Canvas internalLockCanvas(Rect dirty, boolean hardware) {
-            mSurfaceLock.lock();
-
-            if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " + "Locking canvas... stopped="
-                    + mDrawingStopped + ", surfaceControl=" + mSurfaceControl);
-
-            Canvas c = null;
-            if (!mDrawingStopped && mSurfaceControl != null) {
-                try {
-                    if (hardware) {
-                        c = mSurface.lockHardwareCanvas();
-                    } else {
-                        c = mSurface.lockCanvas(dirty);
-                    }
-                } catch (Exception e) {
-                    Log.e(LOG_TAG, "Exception locking surface", e);
-                }
-            }
-
-            if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " + "Returned canvas: " + c);
-            if (c != null) {
-                mLastLockTime = SystemClock.uptimeMillis();
-                return c;
-            }
-
-            // If the Surface is not ready to be drawn, then return null,
-            // but throttle calls to this function so it isn't called more
-            // than every 100ms.
-            long now = SystemClock.uptimeMillis();
-            long nextTime = mLastLockTime + 100;
-            if (nextTime > now) {
-                try {
-                    Thread.sleep(nextTime-now);
-                } catch (InterruptedException e) {
-                }
-                now = SystemClock.uptimeMillis();
-            }
-            mLastLockTime = now;
-            mSurfaceLock.unlock();
-
             return null;
         }
 
-        /**
-         * Posts the new contents of the {@link Canvas} to the surface and
-         * releases the {@link Canvas}.
-         *
-         * @param canvas The canvas previously obtained from {@link #lockCanvas}.
-         */
+        @Override
+        public Canvas lockCanvas(Rect dirty) {
+            return null;
+        }
+
         @Override
         public void unlockCanvasAndPost(Canvas canvas) {
-            mSurface.unlockCanvasAndPost(canvas);
-            mSurfaceLock.unlock();
         }
 
         @Override
         public Surface getSurface() {
-            return mSurface;
+            return null;
         }
 
         @Override
         public Rect getSurfaceFrame() {
-            return mSurfaceFrame;
+            return null;
         }
     };
-
-    class SurfaceControlWithBackground extends SurfaceControl {
-        private SurfaceControl mBackgroundControl;
-        private boolean mOpaque = true;
-        public boolean mVisible = false;
-
-        public SurfaceControlWithBackground(SurfaceSession s,
-                        String name, int w, int h, int format, int flags)
-                       throws Exception {
-            super(s, name, w, h, format, flags);
-            mBackgroundControl = new SurfaceControl(s, "Background for - " + name, w, h,
-                    PixelFormat.OPAQUE, flags | SurfaceControl.FX_SURFACE_DIM);
-            mOpaque = (flags & SurfaceControl.OPAQUE) != 0;
-        }
-
-        @Override
-        public void setAlpha(float alpha) {
-            super.setAlpha(alpha);
-            mBackgroundControl.setAlpha(alpha);
-        }
-
-        @Override
-        public void setLayer(int zorder) {
-            super.setLayer(zorder);
-            // -3 is below all other child layers as SurfaceView never goes below -2
-            mBackgroundControl.setLayer(-3);
-        }
-
-        @Override
-        public void setPosition(float x, float y) {
-            super.setPosition(x, y);
-            mBackgroundControl.setPosition(x, y);
-        }
-
-        @Override
-        public void setSize(int w, int h) {
-            super.setSize(w, h);
-            mBackgroundControl.setSize(w, h);
-        }
-
-        @Override
-        public void setWindowCrop(Rect crop) {
-            super.setWindowCrop(crop);
-            mBackgroundControl.setWindowCrop(crop);
-        }
-
-        @Override
-        public void setFinalCrop(Rect crop) {
-            super.setFinalCrop(crop);
-            mBackgroundControl.setFinalCrop(crop);
-        }
-
-        @Override
-        public void setLayerStack(int layerStack) {
-            super.setLayerStack(layerStack);
-            mBackgroundControl.setLayerStack(layerStack);
-        }
-
-        @Override
-        public void setOpaque(boolean isOpaque) {
-            super.setOpaque(isOpaque);
-            mOpaque = isOpaque;
-            updateBackgroundVisibility();
-        }
-
-        @Override
-        public void setSecure(boolean isSecure) {
-            super.setSecure(isSecure);
-        }
-
-        @Override
-        public void setMatrix(float dsdx, float dtdx, float dsdy, float dtdy) {
-            super.setMatrix(dsdx, dtdx, dsdy, dtdy);
-            mBackgroundControl.setMatrix(dsdx, dtdx, dsdy, dtdy);
-        }
-
-        @Override
-        public void hide() {
-            super.hide();
-            mVisible = false;
-            updateBackgroundVisibility();
-        }
-
-        @Override
-        public void show() {
-            super.show();
-            mVisible = true;
-            updateBackgroundVisibility();
-        }
-
-        @Override
-        public void destroy() {
-            super.destroy();
-            mBackgroundControl.destroy();
-         }
-
-        @Override
-        public void release() {
-            super.release();
-            mBackgroundControl.release();
-        }
-
-        @Override
-        public void setTransparentRegionHint(Region region) {
-            super.setTransparentRegionHint(region);
-            mBackgroundControl.setTransparentRegionHint(region);
-        }
-
-        @Override
-        public void deferTransactionUntil(IBinder handle, long frame) {
-            super.deferTransactionUntil(handle, frame);
-            mBackgroundControl.deferTransactionUntil(handle, frame);
-        }
-
-        @Override
-        public void deferTransactionUntil(Surface barrier, long frame) {
-            super.deferTransactionUntil(barrier, frame);
-            mBackgroundControl.deferTransactionUntil(barrier, frame);
-        }
-
-        void updateBackgroundVisibility() {
-            if (mOpaque && mVisible) {
-                mBackgroundControl.show();
-            } else {
-                mBackgroundControl.hide();
-            }
-        }
-    }
 }
+
diff --git a/android/view/View.java b/android/view/View.java
index b6be296..c043dca 100644
--- a/android/view/View.java
+++ b/android/view/View.java
@@ -1448,17 +1448,59 @@
 
     /**
      * <p>Enables low quality mode for the drawing cache.</p>
+     *
+     * @deprecated The view drawing cache was largely made obsolete with the introduction of
+     * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+     * layers are largely unnecessary and can easily result in a net loss in performance due to the
+     * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+     * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+     * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+     * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+     * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+     * software-rendered usages are discouraged and have compatibility issues with hardware-only
+     * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+     * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+     * reports or unit testing the {@link PixelCopy} API is recommended.
      */
+    @Deprecated
     public static final int DRAWING_CACHE_QUALITY_LOW = 0x00080000;
 
     /**
      * <p>Enables high quality mode for the drawing cache.</p>
+     *
+     * @deprecated The view drawing cache was largely made obsolete with the introduction of
+     * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+     * layers are largely unnecessary and can easily result in a net loss in performance due to the
+     * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+     * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+     * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+     * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+     * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+     * software-rendered usages are discouraged and have compatibility issues with hardware-only
+     * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+     * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+     * reports or unit testing the {@link PixelCopy} API is recommended.
      */
+    @Deprecated
     public static final int DRAWING_CACHE_QUALITY_HIGH = 0x00100000;
 
     /**
      * <p>Enables automatic quality mode for the drawing cache.</p>
+     *
+     * @deprecated The view drawing cache was largely made obsolete with the introduction of
+     * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+     * layers are largely unnecessary and can easily result in a net loss in performance due to the
+     * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+     * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+     * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+     * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+     * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+     * software-rendered usages are discouraged and have compatibility issues with hardware-only
+     * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+     * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+     * reports or unit testing the {@link PixelCopy} API is recommended.
      */
+    @Deprecated
     public static final int DRAWING_CACHE_QUALITY_AUTO = 0x00000000;
 
     private static final int[] DRAWING_CACHE_QUALITY_FLAGS = {
@@ -2300,9 +2342,9 @@
     private static final int PFLAG_HOVERED             = 0x10000000;
 
     /**
-     * no longer needed, should be reused
+     * Flag set by {@link AutofillManager} if it needs to be notified when this view is clicked.
      */
-    private static final int PFLAG_DOES_NOTHING_REUSE_PLEASE = 0x20000000;
+    private static final int PFLAG_NOTIFY_AUTOFILL_MANAGER_ON_CLICK = 0x20000000;
 
     /** {@hide} */
     static final int PFLAG_ACTIVATED                   = 0x40000000;
@@ -6355,6 +6397,42 @@
         return null;
     }
 
+    /** @hide */
+    public void setNotifyAutofillManagerOnClick(boolean notify) {
+        if (notify) {
+            mPrivateFlags |= PFLAG_NOTIFY_AUTOFILL_MANAGER_ON_CLICK;
+        } else {
+            mPrivateFlags &= ~PFLAG_NOTIFY_AUTOFILL_MANAGER_ON_CLICK;
+        }
+    }
+
+    private void notifyAutofillManagerOnClick() {
+        if ((mPrivateFlags & PFLAG_NOTIFY_AUTOFILL_MANAGER_ON_CLICK) != 0) {
+            try {
+                getAutofillManager().notifyViewClicked(this);
+            } finally {
+                // Set it to already called so it's not called twice when called by
+                // performClickInternal()
+                mPrivateFlags |= ~PFLAG_NOTIFY_AUTOFILL_MANAGER_ON_CLICK;
+            }
+        }
+    }
+
+    /**
+     * Entry point for {@link #performClick()} - other methods on View should call it instead of
+     * {@code performClick()} directly to make sure the autofill manager is notified when
+     * necessary (as subclasses could extend {@code performClick()} without calling the parent's
+     * method).
+     */
+    private boolean performClickInternal() {
+        // Must notify autofill manager before performing the click actions to avoid scenarios where
+        // the app has a click listener that changes the state of views the autofill service might
+        // be interested on.
+        notifyAutofillManagerOnClick();
+
+        return performClick();
+    }
+
     /**
      * Call this view's OnClickListener, if it is defined.  Performs all normal
      * actions associated with clicking: reporting accessibility event, playing
@@ -6363,7 +6441,14 @@
      * @return True there was an assigned OnClickListener that was called, false
      *         otherwise is returned.
      */
+    // NOTE: other methods on View should not call this method directly, but performClickInternal()
+    // instead, to guarantee that the autofill manager is notified when necessary (as subclasses
+    // could extend this method without calling super.performClick()).
     public boolean performClick() {
+        // We still need to call this method to handle the cases where performClick() was called
+        // externally, instead of through performClickInternal()
+        notifyAutofillManagerOnClick();
+
         final boolean result;
         final ListenerInfo li = mListenerInfo;
         if (li != null && li.mOnClickListener != null) {
@@ -8907,7 +8992,21 @@
      * @see #isDrawingCacheEnabled()
      *
      * @attr ref android.R.styleable#View_drawingCacheQuality
+     *
+     * @deprecated The view drawing cache was largely made obsolete with the introduction of
+     * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+     * layers are largely unnecessary and can easily result in a net loss in performance due to the
+     * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+     * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+     * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+     * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+     * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+     * software-rendered usages are discouraged and have compatibility issues with hardware-only
+     * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+     * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+     * reports or unit testing the {@link PixelCopy} API is recommended.
      */
+    @Deprecated
     @DrawingCacheQuality
     public int getDrawingCacheQuality() {
         return mViewFlags & DRAWING_CACHE_QUALITY_MASK;
@@ -8925,7 +9024,21 @@
      * @see #isDrawingCacheEnabled()
      *
      * @attr ref android.R.styleable#View_drawingCacheQuality
+     *
+     * @deprecated The view drawing cache was largely made obsolete with the introduction of
+     * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+     * layers are largely unnecessary and can easily result in a net loss in performance due to the
+     * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+     * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+     * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+     * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+     * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+     * software-rendered usages are discouraged and have compatibility issues with hardware-only
+     * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+     * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+     * reports or unit testing the {@link PixelCopy} API is recommended.
      */
+    @Deprecated
     public void setDrawingCacheQuality(@DrawingCacheQuality int quality) {
         setFlags(quality, DRAWING_CACHE_QUALITY_MASK);
     }
@@ -11433,7 +11546,7 @@
         switch (action) {
             case AccessibilityNodeInfo.ACTION_CLICK: {
                 if (isClickable()) {
-                    performClick();
+                    performClickInternal();
                     return true;
                 }
             } break;
@@ -12545,7 +12658,7 @@
                     // This is a tap, so remove the longpress check
                     removeLongPressCallback();
                     if (!event.isCanceled()) {
-                        return performClick();
+                        return performClickInternal();
                     }
                 }
             }
@@ -13117,7 +13230,7 @@
                                     mPerformClick = new PerformClick();
                                 }
                                 if (!post(mPerformClick)) {
-                                    performClick();
+                                    performClickInternal();
                                 }
                             }
                         }
@@ -18103,7 +18216,21 @@
      * @see #getDrawingCache()
      * @see #buildDrawingCache()
      * @see #setLayerType(int, android.graphics.Paint)
+     *
+     * @deprecated The view drawing cache was largely made obsolete with the introduction of
+     * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+     * layers are largely unnecessary and can easily result in a net loss in performance due to the
+     * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+     * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+     * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+     * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+     * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+     * software-rendered usages are discouraged and have compatibility issues with hardware-only
+     * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+     * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+     * reports or unit testing the {@link PixelCopy} API is recommended.
      */
+    @Deprecated
     public void setDrawingCacheEnabled(boolean enabled) {
         mCachingFailed = false;
         setFlags(enabled ? DRAWING_CACHE_ENABLED : 0, DRAWING_CACHE_ENABLED);
@@ -18116,7 +18243,21 @@
      *
      * @see #setDrawingCacheEnabled(boolean)
      * @see #getDrawingCache()
+     *
+     * @deprecated The view drawing cache was largely made obsolete with the introduction of
+     * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+     * layers are largely unnecessary and can easily result in a net loss in performance due to the
+     * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+     * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+     * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+     * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+     * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+     * software-rendered usages are discouraged and have compatibility issues with hardware-only
+     * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+     * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+     * reports or unit testing the {@link PixelCopy} API is recommended.
      */
+    @Deprecated
     @ViewDebug.ExportedProperty(category = "drawing")
     public boolean isDrawingCacheEnabled() {
         return (mViewFlags & DRAWING_CACHE_ENABLED) == DRAWING_CACHE_ENABLED;
@@ -18130,10 +18271,11 @@
      */
     @SuppressWarnings({"UnusedDeclaration"})
     public void outputDirtyFlags(String indent, boolean clear, int clearMask) {
-        Log.d("View", indent + this + "             DIRTY(" + (mPrivateFlags & View.PFLAG_DIRTY_MASK) +
-                ") DRAWN(" + (mPrivateFlags & PFLAG_DRAWN) + ")" + " CACHE_VALID(" +
-                (mPrivateFlags & View.PFLAG_DRAWING_CACHE_VALID) +
-                ") INVALIDATED(" + (mPrivateFlags & PFLAG_INVALIDATED) + ")");
+        Log.d(VIEW_LOG_TAG, indent + this + "             DIRTY("
+                + (mPrivateFlags & View.PFLAG_DIRTY_MASK)
+                + ") DRAWN(" + (mPrivateFlags & PFLAG_DRAWN) + ")" + " CACHE_VALID("
+                + (mPrivateFlags & View.PFLAG_DRAWING_CACHE_VALID)
+                + ") INVALIDATED(" + (mPrivateFlags & PFLAG_INVALIDATED) + ")");
         if (clear) {
             mPrivateFlags &= clearMask;
         }
@@ -18257,7 +18399,21 @@
      * @return A non-scaled bitmap representing this view or null if cache is disabled.
      *
      * @see #getDrawingCache(boolean)
+     *
+     * @deprecated The view drawing cache was largely made obsolete with the introduction of
+     * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+     * layers are largely unnecessary and can easily result in a net loss in performance due to the
+     * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+     * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+     * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+     * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+     * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+     * software-rendered usages are discouraged and have compatibility issues with hardware-only
+     * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+     * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+     * reports or unit testing the {@link PixelCopy} API is recommended.
      */
+    @Deprecated
     public Bitmap getDrawingCache() {
         return getDrawingCache(false);
     }
@@ -18288,7 +18444,21 @@
      * @see #isDrawingCacheEnabled()
      * @see #buildDrawingCache(boolean)
      * @see #destroyDrawingCache()
+     *
+     * @deprecated The view drawing cache was largely made obsolete with the introduction of
+     * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+     * layers are largely unnecessary and can easily result in a net loss in performance due to the
+     * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+     * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+     * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+     * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+     * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+     * software-rendered usages are discouraged and have compatibility issues with hardware-only
+     * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+     * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+     * reports or unit testing the {@link PixelCopy} API is recommended.
      */
+    @Deprecated
     public Bitmap getDrawingCache(boolean autoScale) {
         if ((mViewFlags & WILL_NOT_CACHE_DRAWING) == WILL_NOT_CACHE_DRAWING) {
             return null;
@@ -18308,7 +18478,21 @@
      * @see #setDrawingCacheEnabled(boolean)
      * @see #buildDrawingCache()
      * @see #getDrawingCache()
+     *
+     * @deprecated The view drawing cache was largely made obsolete with the introduction of
+     * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+     * layers are largely unnecessary and can easily result in a net loss in performance due to the
+     * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+     * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+     * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+     * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+     * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+     * software-rendered usages are discouraged and have compatibility issues with hardware-only
+     * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+     * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+     * reports or unit testing the {@link PixelCopy} API is recommended.
      */
+    @Deprecated
     public void destroyDrawingCache() {
         if (mDrawingCache != null) {
             mDrawingCache.recycle();
@@ -18330,7 +18514,21 @@
      * @see #setDrawingCacheEnabled(boolean)
      * @see #buildDrawingCache()
      * @see #getDrawingCache()
+     *
+     * @deprecated The view drawing cache was largely made obsolete with the introduction of
+     * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+     * layers are largely unnecessary and can easily result in a net loss in performance due to the
+     * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+     * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+     * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+     * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+     * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+     * software-rendered usages are discouraged and have compatibility issues with hardware-only
+     * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+     * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+     * reports or unit testing the {@link PixelCopy} API is recommended.
      */
+    @Deprecated
     public void setDrawingCacheBackgroundColor(@ColorInt int color) {
         if (color != mDrawingCacheBackgroundColor) {
             mDrawingCacheBackgroundColor = color;
@@ -18342,7 +18540,21 @@
      * @see #setDrawingCacheBackgroundColor(int)
      *
      * @return The background color to used for the drawing cache's bitmap
+     *
+     * @deprecated The view drawing cache was largely made obsolete with the introduction of
+     * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+     * layers are largely unnecessary and can easily result in a net loss in performance due to the
+     * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+     * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+     * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+     * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+     * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+     * software-rendered usages are discouraged and have compatibility issues with hardware-only
+     * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+     * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+     * reports or unit testing the {@link PixelCopy} API is recommended.
      */
+    @Deprecated
     @ColorInt
     public int getDrawingCacheBackgroundColor() {
         return mDrawingCacheBackgroundColor;
@@ -18352,7 +18564,21 @@
      * <p>Calling this method is equivalent to calling <code>buildDrawingCache(false)</code>.</p>
      *
      * @see #buildDrawingCache(boolean)
+     *
+     * @deprecated The view drawing cache was largely made obsolete with the introduction of
+     * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+     * layers are largely unnecessary and can easily result in a net loss in performance due to the
+     * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+     * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+     * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+     * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+     * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+     * software-rendered usages are discouraged and have compatibility issues with hardware-only
+     * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+     * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+     * reports or unit testing the {@link PixelCopy} API is recommended.
      */
+    @Deprecated
     public void buildDrawingCache() {
         buildDrawingCache(false);
     }
@@ -18379,7 +18605,21 @@
      *
      * @see #getDrawingCache()
      * @see #destroyDrawingCache()
+     *
+     * @deprecated The view drawing cache was largely made obsolete with the introduction of
+     * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+     * layers are largely unnecessary and can easily result in a net loss in performance due to the
+     * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+     * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+     * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+     * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+     * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+     * software-rendered usages are discouraged and have compatibility issues with hardware-only
+     * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+     * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+     * reports or unit testing the {@link PixelCopy} API is recommended.
      */
+    @Deprecated
     public void buildDrawingCache(boolean autoScale) {
         if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0 || (autoScale ?
                 mDrawingCache == null : mUnscaledDrawingCache == null)) {
@@ -19812,7 +20052,7 @@
         boolean changed = false;
 
         if (DBG) {
-            Log.d("View", this + " View.setFrame(" + left + "," + top + ","
+            Log.d(VIEW_LOG_TAG, this + " View.setFrame(" + left + "," + top + ","
                     + right + "," + bottom + ")");
         }
 
@@ -24858,7 +25098,7 @@
     private final class PerformClick implements Runnable {
         @Override
         public void run() {
-            performClick();
+            performClickInternal();
         }
     }
 
diff --git a/android/view/ViewConfiguration.java b/android/view/ViewConfiguration.java
index 574137b..4500862 100644
--- a/android/view/ViewConfiguration.java
+++ b/android/view/ViewConfiguration.java
@@ -84,12 +84,17 @@
 
     /**
      * Defines the duration in milliseconds a user needs to hold down the
-     * appropriate button to bring up the accessibility shortcut (first time) or enable it
-     * (once shortcut is configured).
+     * appropriate button to bring up the accessibility shortcut for the first time
      */
     private static final int A11Y_SHORTCUT_KEY_TIMEOUT = 3000;
 
     /**
+     * Defines the duration in milliseconds a user needs to hold down the
+     * appropriate button to enable the accessibility shortcut once it's configured.
+     */
+    private static final int A11Y_SHORTCUT_KEY_TIMEOUT_AFTER_CONFIRMATION = 1500;
+
+    /**
      * Defines the duration in milliseconds we will wait to see if a touch event
      * is a tap or a scroll. If the user does not move within this interval, it is
      * considered to be a tap.
@@ -851,6 +856,15 @@
     }
 
     /**
+     * @return The amount of time a user needs to press the relevant keys to activate the
+     *   accessibility shortcut after it's confirmed that accessibility shortcut is used.
+     * @hide
+     */
+    public long getAccessibilityShortcutKeyTimeoutAfterConfirmation() {
+        return A11Y_SHORTCUT_KEY_TIMEOUT_AFTER_CONFIRMATION;
+    }
+
+    /**
      * The amount of friction applied to scrolls and flings.
      *
      * @return A scalar dimensionless value representing the coefficient of
diff --git a/android/view/ViewDebug.java b/android/view/ViewDebug.java
index 3426485..afa9413 100644
--- a/android/view/ViewDebug.java
+++ b/android/view/ViewDebug.java
@@ -528,84 +528,23 @@
     /** @hide */
     public static void profileViewAndChildren(final View view, BufferedWriter out)
             throws IOException {
-        profileViewAndChildren(view, out, true);
+        RenderNode node = RenderNode.create("ViewDebug", null);
+        profileViewAndChildren(view, node, out, true);
+        node.destroy();
     }
 
-    private static void profileViewAndChildren(final View view, BufferedWriter out, boolean root)
-            throws IOException {
-
+    private static void profileViewAndChildren(View view, RenderNode node, BufferedWriter out,
+            boolean root) throws IOException {
         long durationMeasure =
                 (root || (view.mPrivateFlags & View.PFLAG_MEASURED_DIMENSION_SET) != 0)
-                        ? profileViewOperation(view, new ViewOperation<Void>() {
-                    public Void[] pre() {
-                        forceLayout(view);
-                        return null;
-                    }
-
-                    private void forceLayout(View view) {
-                        view.forceLayout();
-                        if (view instanceof ViewGroup) {
-                            ViewGroup group = (ViewGroup) view;
-                            final int count = group.getChildCount();
-                            for (int i = 0; i < count; i++) {
-                                forceLayout(group.getChildAt(i));
-                            }
-                        }
-                    }
-
-                    public void run(Void... data) {
-                        view.measure(view.mOldWidthMeasureSpec, view.mOldHeightMeasureSpec);
-                    }
-
-                    public void post(Void... data) {
-                    }
-                })
-                        : 0;
+                        ? profileViewMeasure(view) : 0;
         long durationLayout =
                 (root || (view.mPrivateFlags & View.PFLAG_LAYOUT_REQUIRED) != 0)
-                        ? profileViewOperation(view, new ViewOperation<Void>() {
-                    public Void[] pre() {
-                        return null;
-                    }
-
-                    public void run(Void... data) {
-                        view.layout(view.mLeft, view.mTop, view.mRight, view.mBottom);
-                    }
-
-                    public void post(Void... data) {
-                    }
-                }) : 0;
+                        ? profileViewLayout(view) : 0;
         long durationDraw =
                 (root || !view.willNotDraw() || (view.mPrivateFlags & View.PFLAG_DRAWN) != 0)
-                        ? profileViewOperation(view, new ViewOperation<Object>() {
-                    public Object[] pre() {
-                        final DisplayMetrics metrics =
-                                (view != null && view.getResources() != null) ?
-                                        view.getResources().getDisplayMetrics() : null;
-                        final Bitmap bitmap = metrics != null ?
-                                Bitmap.createBitmap(metrics, metrics.widthPixels,
-                                        metrics.heightPixels, Bitmap.Config.RGB_565) : null;
-                        final Canvas canvas = bitmap != null ? new Canvas(bitmap) : null;
-                        return new Object[] {
-                                bitmap, canvas
-                        };
-                    }
+                        ? profileViewDraw(view, node) : 0;
 
-                    public void run(Object... data) {
-                        if (data[1] != null) {
-                            view.draw((Canvas) data[1]);
-                        }
-                    }
-
-                    public void post(Object... data) {
-                        if (data[1] != null) {
-                            ((Canvas) data[1]).setBitmap(null);
-                        }
-                        if (data[0] != null) {
-                            ((Bitmap) data[0]).recycle();
-                        }
-                    }
-                }) : 0;
         out.write(String.valueOf(durationMeasure));
         out.write(' ');
         out.write(String.valueOf(durationLayout));
@@ -616,34 +555,86 @@
             ViewGroup group = (ViewGroup) view;
             final int count = group.getChildCount();
             for (int i = 0; i < count; i++) {
-                profileViewAndChildren(group.getChildAt(i), out, false);
+                profileViewAndChildren(group.getChildAt(i), node, out, false);
             }
         }
     }
 
-    interface ViewOperation<T> {
-        T[] pre();
-        void run(T... data);
-        void post(T... data);
+    private static long profileViewMeasure(final View view) {
+        return profileViewOperation(view, new ViewOperation() {
+            @Override
+            public void pre() {
+                forceLayout(view);
+            }
+
+            private void forceLayout(View view) {
+                view.forceLayout();
+                if (view instanceof ViewGroup) {
+                    ViewGroup group = (ViewGroup) view;
+                    final int count = group.getChildCount();
+                    for (int i = 0; i < count; i++) {
+                        forceLayout(group.getChildAt(i));
+                    }
+                }
+            }
+
+            @Override
+            public void run() {
+                view.measure(view.mOldWidthMeasureSpec, view.mOldHeightMeasureSpec);
+            }
+        });
     }
 
-    private static <T> long profileViewOperation(View view, final ViewOperation<T> operation) {
+    private static long profileViewLayout(View view) {
+        return profileViewOperation(view,
+                () -> view.layout(view.mLeft, view.mTop, view.mRight, view.mBottom));
+    }
+
+    private static long profileViewDraw(View view, RenderNode node) {
+        DisplayMetrics dm = view.getResources().getDisplayMetrics();
+        if (dm == null) {
+            return 0;
+        }
+
+        if (view.isHardwareAccelerated()) {
+            DisplayListCanvas canvas = node.start(dm.widthPixels, dm.heightPixels);
+            try {
+                return profileViewOperation(view, () -> view.draw(canvas));
+            } finally {
+                node.end(canvas);
+            }
+        } else {
+            Bitmap bitmap = Bitmap.createBitmap(
+                    dm, dm.widthPixels, dm.heightPixels, Bitmap.Config.RGB_565);
+            Canvas canvas = new Canvas(bitmap);
+            try {
+                return profileViewOperation(view, () -> view.draw(canvas));
+            } finally {
+                canvas.setBitmap(null);
+                bitmap.recycle();
+            }
+        }
+    }
+
+    interface ViewOperation {
+        default void pre() {}
+
+        void run();
+    }
+
+    private static long profileViewOperation(View view, final ViewOperation operation) {
         final CountDownLatch latch = new CountDownLatch(1);
         final long[] duration = new long[1];
 
-        view.post(new Runnable() {
-            public void run() {
-                try {
-                    T[] data = operation.pre();
-                    long start = Debug.threadCpuTimeNanos();
-                    //noinspection unchecked
-                    operation.run(data);
-                    duration[0] = Debug.threadCpuTimeNanos() - start;
-                    //noinspection unchecked
-                    operation.post(data);
-                } finally {
-                    latch.countDown();
-                }
+        view.post(() -> {
+            try {
+                operation.pre();
+                long start = Debug.threadCpuTimeNanos();
+                //noinspection unchecked
+                operation.run();
+                duration[0] = Debug.threadCpuTimeNanos() - start;
+            } finally {
+                latch.countDown();
             }
         });
 
diff --git a/android/view/ViewGroup.java b/android/view/ViewGroup.java
index b2e5a16..929beae 100644
--- a/android/view/ViewGroup.java
+++ b/android/view/ViewGroup.java
@@ -421,22 +421,78 @@
 
     /**
      * Used to indicate that no drawing cache should be kept in memory.
+     *
+     * @deprecated The view drawing cache was largely made obsolete with the introduction of
+     * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+     * layers are largely unnecessary and can easily result in a net loss in performance due to the
+     * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+     * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+     * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+     * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+     * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+     * software-rendered usages are discouraged and have compatibility issues with hardware-only
+     * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+     * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+     * reports or unit testing the {@link PixelCopy} API is recommended.
      */
+    @Deprecated
     public static final int PERSISTENT_NO_CACHE = 0x0;
 
     /**
      * Used to indicate that the animation drawing cache should be kept in memory.
+     *
+     * @deprecated The view drawing cache was largely made obsolete with the introduction of
+     * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+     * layers are largely unnecessary and can easily result in a net loss in performance due to the
+     * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+     * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+     * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+     * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+     * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+     * software-rendered usages are discouraged and have compatibility issues with hardware-only
+     * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+     * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+     * reports or unit testing the {@link PixelCopy} API is recommended.
      */
+    @Deprecated
     public static final int PERSISTENT_ANIMATION_CACHE = 0x1;
 
     /**
      * Used to indicate that the scrolling drawing cache should be kept in memory.
+     *
+     * @deprecated The view drawing cache was largely made obsolete with the introduction of
+     * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+     * layers are largely unnecessary and can easily result in a net loss in performance due to the
+     * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+     * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+     * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+     * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+     * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+     * software-rendered usages are discouraged and have compatibility issues with hardware-only
+     * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+     * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+     * reports or unit testing the {@link PixelCopy} API is recommended.
      */
+    @Deprecated
     public static final int PERSISTENT_SCROLLING_CACHE = 0x2;
 
     /**
      * Used to indicate that all drawing caches should be kept in memory.
+     *
+     * @deprecated The view drawing cache was largely made obsolete with the introduction of
+     * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+     * layers are largely unnecessary and can easily result in a net loss in performance due to the
+     * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+     * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+     * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+     * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+     * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+     * software-rendered usages are discouraged and have compatibility issues with hardware-only
+     * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+     * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+     * reports or unit testing the {@link PixelCopy} API is recommended.
      */
+    @Deprecated
     public static final int PERSISTENT_ALL_CACHES = 0x3;
 
     // Layout Modes
@@ -3769,7 +3825,21 @@
      * Enables or disables the drawing cache for each child of this view group.
      *
      * @param enabled true to enable the cache, false to dispose of it
+     *
+     * @deprecated The view drawing cache was largely made obsolete with the introduction of
+     * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+     * layers are largely unnecessary and can easily result in a net loss in performance due to the
+     * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+     * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+     * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+     * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+     * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+     * software-rendered usages are discouraged and have compatibility issues with hardware-only
+     * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+     * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+     * reports or unit testing the {@link PixelCopy} API is recommended.
      */
+    @Deprecated
     protected void setChildrenDrawingCacheEnabled(boolean enabled) {
         if (enabled || (mPersistentDrawingCache & PERSISTENT_ALL_CACHES) != PERSISTENT_ALL_CACHES) {
             final View[] children = mChildren;
@@ -6331,7 +6401,21 @@
      * @return one or a combination of {@link #PERSISTENT_NO_CACHE},
      *         {@link #PERSISTENT_ANIMATION_CACHE}, {@link #PERSISTENT_SCROLLING_CACHE}
      *         and {@link #PERSISTENT_ALL_CACHES}
+     *
+     * @deprecated The view drawing cache was largely made obsolete with the introduction of
+     * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+     * layers are largely unnecessary and can easily result in a net loss in performance due to the
+     * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+     * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+     * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+     * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+     * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+     * software-rendered usages are discouraged and have compatibility issues with hardware-only
+     * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+     * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+     * reports or unit testing the {@link PixelCopy} API is recommended.
      */
+    @Deprecated
     @ViewDebug.ExportedProperty(category = "drawing", mapping = {
         @ViewDebug.IntToString(from = PERSISTENT_NO_CACHE,        to = "NONE"),
         @ViewDebug.IntToString(from = PERSISTENT_ANIMATION_CACHE, to = "ANIMATION"),
@@ -6352,7 +6436,21 @@
      * @param drawingCacheToKeep one or a combination of {@link #PERSISTENT_NO_CACHE},
      *        {@link #PERSISTENT_ANIMATION_CACHE}, {@link #PERSISTENT_SCROLLING_CACHE}
      *        and {@link #PERSISTENT_ALL_CACHES}
+     *
+     * @deprecated The view drawing cache was largely made obsolete with the introduction of
+     * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+     * layers are largely unnecessary and can easily result in a net loss in performance due to the
+     * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+     * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+     * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+     * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+     * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+     * software-rendered usages are discouraged and have compatibility issues with hardware-only
+     * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+     * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+     * reports or unit testing the {@link PixelCopy} API is recommended.
      */
+    @Deprecated
     public void setPersistentDrawingCache(int drawingCacheToKeep) {
         mPersistentDrawingCache = drawingCacheToKeep & PERSISTENT_ALL_CACHES;
     }
diff --git a/android/view/ViewRootImpl.java b/android/view/ViewRootImpl.java
index 71106ad..99438d8 100644
--- a/android/view/ViewRootImpl.java
+++ b/android/view/ViewRootImpl.java
@@ -72,6 +72,7 @@
 import android.util.Log;
 import android.util.MergedConfiguration;
 import android.util.Slog;
+import android.util.SparseArray;
 import android.util.TimeUtils;
 import android.util.TypedValue;
 import android.view.Surface.OutOfResourcesException;
@@ -1668,8 +1669,6 @@
             host.dispatchAttachedToWindow(mAttachInfo, 0);
             mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true);
             dispatchApplyInsets(host);
-            //Log.i(mTag, "Screen on initialized: " + attachInfo.mKeepScreenOn);
-
         } else {
             desiredWindowWidth = frame.width();
             desiredWindowHeight = frame.height();
@@ -2827,7 +2826,7 @@
                 try {
                     mWindowDrawCountDown.await();
                 } catch (InterruptedException e) {
-                    Log.e(mTag, "Window redraw count down interruped!");
+                    Log.e(mTag, "Window redraw count down interrupted!");
                 }
                 mWindowDrawCountDown = null;
             }
@@ -2897,8 +2896,6 @@
         final float appScale = mAttachInfo.mApplicationScale;
         final boolean scalingRequired = mAttachInfo.mScalingRequired;
 
-        int resizeAlpha = 0;
-
         final Rect dirty = mDirty;
         if (mSurfaceHolder != null) {
             // The app owns the surface, we won't draw.
@@ -3469,6 +3466,7 @@
     }
 
     void dispatchDetachedFromWindow() {
+        mFirstInputStage.onDetachedFromWindow();
         if (mView != null && mView.mAttachInfo != null) {
             mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(false);
             mView.dispatchDetachedFromWindow();
@@ -3731,266 +3729,273 @@
         @Override
         public void handleMessage(Message msg) {
             switch (msg.what) {
-            case MSG_INVALIDATE:
-                ((View) msg.obj).invalidate();
-                break;
-            case MSG_INVALIDATE_RECT:
-                final View.AttachInfo.InvalidateInfo info = (View.AttachInfo.InvalidateInfo) msg.obj;
-                info.target.invalidate(info.left, info.top, info.right, info.bottom);
-                info.recycle();
-                break;
-            case MSG_PROCESS_INPUT_EVENTS:
-                mProcessInputEventsScheduled = false;
-                doProcessInputEvents();
-                break;
-            case MSG_DISPATCH_APP_VISIBILITY:
-                handleAppVisibility(msg.arg1 != 0);
-                break;
-            case MSG_DISPATCH_GET_NEW_SURFACE:
-                handleGetNewSurface();
-                break;
-            case MSG_RESIZED: {
-                // Recycled in the fall through...
-                SomeArgs args = (SomeArgs) msg.obj;
-                if (mWinFrame.equals(args.arg1)
-                        && mPendingOverscanInsets.equals(args.arg5)
-                        && mPendingContentInsets.equals(args.arg2)
-                        && mPendingStableInsets.equals(args.arg6)
-                        && mPendingVisibleInsets.equals(args.arg3)
-                        && mPendingOutsets.equals(args.arg7)
-                        && mPendingBackDropFrame.equals(args.arg8)
-                        && args.arg4 == null
-                        && args.argi1 == 0
-                        && mDisplay.getDisplayId() == args.argi3) {
+                case MSG_INVALIDATE:
+                    ((View) msg.obj).invalidate();
                     break;
-                }
-                } // fall through...
-            case MSG_RESIZED_REPORT:
-                if (mAdded) {
+                case MSG_INVALIDATE_RECT:
+                    final View.AttachInfo.InvalidateInfo info =
+                            (View.AttachInfo.InvalidateInfo) msg.obj;
+                    info.target.invalidate(info.left, info.top, info.right, info.bottom);
+                    info.recycle();
+                    break;
+                case MSG_PROCESS_INPUT_EVENTS:
+                    mProcessInputEventsScheduled = false;
+                    doProcessInputEvents();
+                    break;
+                case MSG_DISPATCH_APP_VISIBILITY:
+                    handleAppVisibility(msg.arg1 != 0);
+                    break;
+                case MSG_DISPATCH_GET_NEW_SURFACE:
+                    handleGetNewSurface();
+                    break;
+                case MSG_RESIZED: {
+                    // Recycled in the fall through...
                     SomeArgs args = (SomeArgs) msg.obj;
-
-                    final int displayId = args.argi3;
-                    MergedConfiguration mergedConfiguration = (MergedConfiguration) args.arg4;
-                    final boolean displayChanged = mDisplay.getDisplayId() != displayId;
-
-                    if (!mLastReportedMergedConfiguration.equals(mergedConfiguration)) {
-                        // If configuration changed - notify about that and, maybe, about move to
-                        // display.
-                        performConfigurationChange(mergedConfiguration, false /* force */,
-                                displayChanged ? displayId : INVALID_DISPLAY /* same display */);
-                    } else if (displayChanged) {
-                        // Moved to display without config change - report last applied one.
-                        onMovedToDisplay(displayId, mLastConfigurationFromResources);
+                    if (mWinFrame.equals(args.arg1)
+                            && mPendingOverscanInsets.equals(args.arg5)
+                            && mPendingContentInsets.equals(args.arg2)
+                            && mPendingStableInsets.equals(args.arg6)
+                            && mPendingVisibleInsets.equals(args.arg3)
+                            && mPendingOutsets.equals(args.arg7)
+                            && mPendingBackDropFrame.equals(args.arg8)
+                            && args.arg4 == null
+                            && args.argi1 == 0
+                            && mDisplay.getDisplayId() == args.argi3) {
+                        break;
                     }
+                } // fall through...
+                case MSG_RESIZED_REPORT:
+                    if (mAdded) {
+                        SomeArgs args = (SomeArgs) msg.obj;
 
-                    final boolean framesChanged = !mWinFrame.equals(args.arg1)
-                            || !mPendingOverscanInsets.equals(args.arg5)
-                            || !mPendingContentInsets.equals(args.arg2)
-                            || !mPendingStableInsets.equals(args.arg6)
-                            || !mPendingVisibleInsets.equals(args.arg3)
-                            || !mPendingOutsets.equals(args.arg7);
+                        final int displayId = args.argi3;
+                        MergedConfiguration mergedConfiguration = (MergedConfiguration) args.arg4;
+                        final boolean displayChanged = mDisplay.getDisplayId() != displayId;
 
-                    mWinFrame.set((Rect) args.arg1);
-                    mPendingOverscanInsets.set((Rect) args.arg5);
-                    mPendingContentInsets.set((Rect) args.arg2);
-                    mPendingStableInsets.set((Rect) args.arg6);
-                    mPendingVisibleInsets.set((Rect) args.arg3);
-                    mPendingOutsets.set((Rect) args.arg7);
-                    mPendingBackDropFrame.set((Rect) args.arg8);
-                    mForceNextWindowRelayout = args.argi1 != 0;
-                    mPendingAlwaysConsumeNavBar = args.argi2 != 0;
+                        if (!mLastReportedMergedConfiguration.equals(mergedConfiguration)) {
+                            // If configuration changed - notify about that and, maybe,
+                            // about move to display.
+                            performConfigurationChange(mergedConfiguration, false /* force */,
+                                    displayChanged
+                                            ? displayId : INVALID_DISPLAY /* same display */);
+                        } else if (displayChanged) {
+                            // Moved to display without config change - report last applied one.
+                            onMovedToDisplay(displayId, mLastConfigurationFromResources);
+                        }
 
-                    args.recycle();
+                        final boolean framesChanged = !mWinFrame.equals(args.arg1)
+                                || !mPendingOverscanInsets.equals(args.arg5)
+                                || !mPendingContentInsets.equals(args.arg2)
+                                || !mPendingStableInsets.equals(args.arg6)
+                                || !mPendingVisibleInsets.equals(args.arg3)
+                                || !mPendingOutsets.equals(args.arg7);
 
-                    if (msg.what == MSG_RESIZED_REPORT) {
-                        reportNextDraw();
+                        mWinFrame.set((Rect) args.arg1);
+                        mPendingOverscanInsets.set((Rect) args.arg5);
+                        mPendingContentInsets.set((Rect) args.arg2);
+                        mPendingStableInsets.set((Rect) args.arg6);
+                        mPendingVisibleInsets.set((Rect) args.arg3);
+                        mPendingOutsets.set((Rect) args.arg7);
+                        mPendingBackDropFrame.set((Rect) args.arg8);
+                        mForceNextWindowRelayout = args.argi1 != 0;
+                        mPendingAlwaysConsumeNavBar = args.argi2 != 0;
+
+                        args.recycle();
+
+                        if (msg.what == MSG_RESIZED_REPORT) {
+                            reportNextDraw();
+                        }
+
+                        if (mView != null && framesChanged) {
+                            forceLayout(mView);
+                        }
+                        requestLayout();
                     }
+                    break;
+                case MSG_WINDOW_MOVED:
+                    if (mAdded) {
+                        final int w = mWinFrame.width();
+                        final int h = mWinFrame.height();
+                        final int l = msg.arg1;
+                        final int t = msg.arg2;
+                        mWinFrame.left = l;
+                        mWinFrame.right = l + w;
+                        mWinFrame.top = t;
+                        mWinFrame.bottom = t + h;
 
-                    if (mView != null && framesChanged) {
-                        forceLayout(mView);
+                        mPendingBackDropFrame.set(mWinFrame);
+                        maybeHandleWindowMove(mWinFrame);
                     }
-                    requestLayout();
-                }
-                break;
-            case MSG_WINDOW_MOVED:
-                if (mAdded) {
-                    final int w = mWinFrame.width();
-                    final int h = mWinFrame.height();
-                    final int l = msg.arg1;
-                    final int t = msg.arg2;
-                    mWinFrame.left = l;
-                    mWinFrame.right = l + w;
-                    mWinFrame.top = t;
-                    mWinFrame.bottom = t + h;
+                    break;
+                case MSG_WINDOW_FOCUS_CHANGED: {
+                    final boolean hasWindowFocus = msg.arg1 != 0;
+                    if (mAdded) {
+                        mAttachInfo.mHasWindowFocus = hasWindowFocus;
 
-                    mPendingBackDropFrame.set(mWinFrame);
-                    maybeHandleWindowMove(mWinFrame);
-                }
-                break;
-            case MSG_WINDOW_FOCUS_CHANGED: {
-                if (mAdded) {
-                    boolean hasWindowFocus = msg.arg1 != 0;
-                    mAttachInfo.mHasWindowFocus = hasWindowFocus;
+                        profileRendering(hasWindowFocus);
 
-                    profileRendering(hasWindowFocus);
-
-                    if (hasWindowFocus) {
-                        boolean inTouchMode = msg.arg2 != 0;
-                        ensureTouchModeLocally(inTouchMode);
-
-                        if (mAttachInfo.mThreadedRenderer != null && mSurface.isValid()){
-                            mFullRedrawNeeded = true;
-                            try {
-                                final WindowManager.LayoutParams lp = mWindowAttributes;
-                                final Rect surfaceInsets = lp != null ? lp.surfaceInsets : null;
-                                mAttachInfo.mThreadedRenderer.initializeIfNeeded(
-                                        mWidth, mHeight, mAttachInfo, mSurface, surfaceInsets);
-                            } catch (OutOfResourcesException e) {
-                                Log.e(mTag, "OutOfResourcesException locking surface", e);
+                        if (hasWindowFocus) {
+                            boolean inTouchMode = msg.arg2 != 0;
+                            ensureTouchModeLocally(inTouchMode);
+                            if (mAttachInfo.mThreadedRenderer != null && mSurface.isValid()) {
+                                mFullRedrawNeeded = true;
                                 try {
-                                    if (!mWindowSession.outOfMemory(mWindow)) {
-                                        Slog.w(mTag, "No processes killed for memory; killing self");
-                                        Process.killProcess(Process.myPid());
+                                    final WindowManager.LayoutParams lp = mWindowAttributes;
+                                    final Rect surfaceInsets = lp != null ? lp.surfaceInsets : null;
+                                    mAttachInfo.mThreadedRenderer.initializeIfNeeded(
+                                            mWidth, mHeight, mAttachInfo, mSurface, surfaceInsets);
+                                } catch (OutOfResourcesException e) {
+                                    Log.e(mTag, "OutOfResourcesException locking surface", e);
+                                    try {
+                                        if (!mWindowSession.outOfMemory(mWindow)) {
+                                            Slog.w(mTag, "No processes killed for memory;"
+                                                    + " killing self");
+                                            Process.killProcess(Process.myPid());
+                                        }
+                                    } catch (RemoteException ex) {
                                     }
-                                } catch (RemoteException ex) {
+                                    // Retry in a bit.
+                                    sendMessageDelayed(obtainMessage(msg.what, msg.arg1, msg.arg2),
+                                            500);
+                                    return;
                                 }
-                                // Retry in a bit.
-                                sendMessageDelayed(obtainMessage(msg.what, msg.arg1, msg.arg2), 500);
-                                return;
+                            }
+                        }
+
+                        mLastWasImTarget = WindowManager.LayoutParams
+                                .mayUseInputMethod(mWindowAttributes.flags);
+
+                        InputMethodManager imm = InputMethodManager.peekInstance();
+                        if (imm != null && mLastWasImTarget && !isInLocalFocusMode()) {
+                            imm.onPreWindowFocus(mView, hasWindowFocus);
+                        }
+                        if (mView != null) {
+                            mAttachInfo.mKeyDispatchState.reset();
+                            mView.dispatchWindowFocusChanged(hasWindowFocus);
+                            mAttachInfo.mTreeObserver.dispatchOnWindowFocusChange(hasWindowFocus);
+
+                            if (mAttachInfo.mTooltipHost != null) {
+                                mAttachInfo.mTooltipHost.hideTooltip();
+                            }
+                        }
+
+                        // Note: must be done after the focus change callbacks,
+                        // so all of the view state is set up correctly.
+                        if (hasWindowFocus) {
+                            if (imm != null && mLastWasImTarget && !isInLocalFocusMode()) {
+                                imm.onPostWindowFocus(mView, mView.findFocus(),
+                                        mWindowAttributes.softInputMode,
+                                        !mHasHadWindowFocus, mWindowAttributes.flags);
+                            }
+                            // Clear the forward bit.  We can just do this directly, since
+                            // the window manager doesn't care about it.
+                            mWindowAttributes.softInputMode &=
+                                    ~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
+                            ((WindowManager.LayoutParams) mView.getLayoutParams())
+                                    .softInputMode &=
+                                        ~WindowManager.LayoutParams
+                                                .SOFT_INPUT_IS_FORWARD_NAVIGATION;
+                            mHasHadWindowFocus = true;
+                        } else {
+                            if (mPointerCapture) {
+                                handlePointerCaptureChanged(false);
                             }
                         }
                     }
-
-                    mLastWasImTarget = WindowManager.LayoutParams
-                            .mayUseInputMethod(mWindowAttributes.flags);
-
+                    mFirstInputStage.onWindowFocusChanged(hasWindowFocus);
+                } break;
+                case MSG_DIE:
+                    doDie();
+                    break;
+                case MSG_DISPATCH_INPUT_EVENT: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    InputEvent event = (InputEvent) args.arg1;
+                    InputEventReceiver receiver = (InputEventReceiver) args.arg2;
+                    enqueueInputEvent(event, receiver, 0, true);
+                    args.recycle();
+                } break;
+                case MSG_SYNTHESIZE_INPUT_EVENT: {
+                    InputEvent event = (InputEvent) msg.obj;
+                    enqueueInputEvent(event, null, QueuedInputEvent.FLAG_UNHANDLED, true);
+                } break;
+                case MSG_DISPATCH_KEY_FROM_IME: {
+                    if (LOCAL_LOGV) {
+                        Log.v(TAG, "Dispatching key " + msg.obj + " from IME to " + mView);
+                    }
+                    KeyEvent event = (KeyEvent) msg.obj;
+                    if ((event.getFlags() & KeyEvent.FLAG_FROM_SYSTEM) != 0) {
+                        // The IME is trying to say this event is from the
+                        // system!  Bad bad bad!
+                        //noinspection UnusedAssignment
+                        event = KeyEvent.changeFlags(event,
+                                event.getFlags() & ~KeyEvent.FLAG_FROM_SYSTEM);
+                    }
+                    enqueueInputEvent(event, null, QueuedInputEvent.FLAG_DELIVER_POST_IME, true);
+                } break;
+                case MSG_CHECK_FOCUS: {
                     InputMethodManager imm = InputMethodManager.peekInstance();
-                    if (imm != null && mLastWasImTarget && !isInLocalFocusMode()) {
-                        imm.onPreWindowFocus(mView, hasWindowFocus);
+                    if (imm != null) {
+                        imm.checkFocus();
                     }
+                } break;
+                case MSG_CLOSE_SYSTEM_DIALOGS: {
                     if (mView != null) {
-                        mAttachInfo.mKeyDispatchState.reset();
-                        mView.dispatchWindowFocusChanged(hasWindowFocus);
-                        mAttachInfo.mTreeObserver.dispatchOnWindowFocusChange(hasWindowFocus);
-
-                        if (mAttachInfo.mTooltipHost != null) {
-                            mAttachInfo.mTooltipHost.hideTooltip();
-                        }
+                        mView.onCloseSystemDialogs((String) msg.obj);
+                    }
+                } break;
+                case MSG_DISPATCH_DRAG_EVENT: {
+                } // fall through
+                case MSG_DISPATCH_DRAG_LOCATION_EVENT: {
+                    DragEvent event = (DragEvent) msg.obj;
+                    // only present when this app called startDrag()
+                    event.mLocalState = mLocalDragState;
+                    handleDragEvent(event);
+                } break;
+                case MSG_DISPATCH_SYSTEM_UI_VISIBILITY: {
+                    handleDispatchSystemUiVisibilityChanged((SystemUiVisibilityInfo) msg.obj);
+                } break;
+                case MSG_UPDATE_CONFIGURATION: {
+                    Configuration config = (Configuration) msg.obj;
+                    if (config.isOtherSeqNewer(
+                            mLastReportedMergedConfiguration.getMergedConfiguration())) {
+                        // If we already have a newer merged config applied - use its global part.
+                        config = mLastReportedMergedConfiguration.getGlobalConfiguration();
                     }
 
-                    // Note: must be done after the focus change callbacks,
-                    // so all of the view state is set up correctly.
-                    if (hasWindowFocus) {
-                        if (imm != null && mLastWasImTarget && !isInLocalFocusMode()) {
-                            imm.onPostWindowFocus(mView, mView.findFocus(),
-                                    mWindowAttributes.softInputMode,
-                                    !mHasHadWindowFocus, mWindowAttributes.flags);
-                        }
-                        // Clear the forward bit.  We can just do this directly, since
-                        // the window manager doesn't care about it.
-                        mWindowAttributes.softInputMode &=
-                                ~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
-                        ((WindowManager.LayoutParams)mView.getLayoutParams())
-                                .softInputMode &=
-                                    ~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
-                        mHasHadWindowFocus = true;
-                    } else {
-                        if (mPointerCapture) {
-                            handlePointerCaptureChanged(false);
-                        }
+                    // Use the newer global config and last reported override config.
+                    mPendingMergedConfiguration.setConfiguration(config,
+                            mLastReportedMergedConfiguration.getOverrideConfiguration());
+
+                    performConfigurationChange(mPendingMergedConfiguration, false /* force */,
+                            INVALID_DISPLAY /* same display */);
+                } break;
+                case MSG_CLEAR_ACCESSIBILITY_FOCUS_HOST: {
+                    setAccessibilityFocus(null, null);
+                } break;
+                case MSG_INVALIDATE_WORLD: {
+                    if (mView != null) {
+                        invalidateWorld(mView);
                     }
-                }
-            } break;
-            case MSG_DIE:
-                doDie();
-                break;
-            case MSG_DISPATCH_INPUT_EVENT: {
-                SomeArgs args = (SomeArgs)msg.obj;
-                InputEvent event = (InputEvent)args.arg1;
-                InputEventReceiver receiver = (InputEventReceiver)args.arg2;
-                enqueueInputEvent(event, receiver, 0, true);
-                args.recycle();
-            } break;
-            case MSG_SYNTHESIZE_INPUT_EVENT: {
-                InputEvent event = (InputEvent)msg.obj;
-                enqueueInputEvent(event, null, QueuedInputEvent.FLAG_UNHANDLED, true);
-            } break;
-            case MSG_DISPATCH_KEY_FROM_IME: {
-                if (LOCAL_LOGV) Log.v(
-                    TAG, "Dispatching key "
-                    + msg.obj + " from IME to " + mView);
-                KeyEvent event = (KeyEvent)msg.obj;
-                if ((event.getFlags()&KeyEvent.FLAG_FROM_SYSTEM) != 0) {
-                    // The IME is trying to say this event is from the
-                    // system!  Bad bad bad!
-                    //noinspection UnusedAssignment
-                    event = KeyEvent.changeFlags(event, event.getFlags() &
-                            ~KeyEvent.FLAG_FROM_SYSTEM);
-                }
-                enqueueInputEvent(event, null, QueuedInputEvent.FLAG_DELIVER_POST_IME, true);
-            } break;
-            case MSG_CHECK_FOCUS: {
-                InputMethodManager imm = InputMethodManager.peekInstance();
-                if (imm != null) {
-                    imm.checkFocus();
-                }
-            } break;
-            case MSG_CLOSE_SYSTEM_DIALOGS: {
-                if (mView != null) {
-                    mView.onCloseSystemDialogs((String)msg.obj);
-                }
-            } break;
-            case MSG_DISPATCH_DRAG_EVENT:
-            case MSG_DISPATCH_DRAG_LOCATION_EVENT: {
-                DragEvent event = (DragEvent)msg.obj;
-                event.mLocalState = mLocalDragState;    // only present when this app called startDrag()
-                handleDragEvent(event);
-            } break;
-            case MSG_DISPATCH_SYSTEM_UI_VISIBILITY: {
-                handleDispatchSystemUiVisibilityChanged((SystemUiVisibilityInfo) msg.obj);
-            } break;
-            case MSG_UPDATE_CONFIGURATION: {
-                Configuration config = (Configuration) msg.obj;
-                if (config.isOtherSeqNewer(
-                        mLastReportedMergedConfiguration.getMergedConfiguration())) {
-                    // If we already have a newer merged config applied - use its global part.
-                    config = mLastReportedMergedConfiguration.getGlobalConfiguration();
-                }
-
-                // Use the newer global config and last reported override config.
-                mPendingMergedConfiguration.setConfiguration(config,
-                        mLastReportedMergedConfiguration.getOverrideConfiguration());
-
-                performConfigurationChange(mPendingMergedConfiguration, false /* force */,
-                        INVALID_DISPLAY /* same display */);
-            } break;
-            case MSG_CLEAR_ACCESSIBILITY_FOCUS_HOST: {
-                setAccessibilityFocus(null, null);
-            } break;
-            case MSG_INVALIDATE_WORLD: {
-                if (mView != null) {
-                    invalidateWorld(mView);
-                }
-            } break;
-            case MSG_DISPATCH_WINDOW_SHOWN: {
-                handleDispatchWindowShown();
-            } break;
-            case MSG_REQUEST_KEYBOARD_SHORTCUTS: {
-                final IResultReceiver receiver = (IResultReceiver) msg.obj;
-                final int deviceId = msg.arg1;
-                handleRequestKeyboardShortcuts(receiver, deviceId);
-            } break;
-            case MSG_UPDATE_POINTER_ICON: {
-                MotionEvent event = (MotionEvent) msg.obj;
-                resetPointerIcon(event);
-            } break;
-            case MSG_POINTER_CAPTURE_CHANGED: {
-                final boolean hasCapture = msg.arg1 != 0;
-                handlePointerCaptureChanged(hasCapture);
-            } break;
-            case MSG_DRAW_FINISHED: {
-                pendingDrawFinished();
-            } break;
+                } break;
+                case MSG_DISPATCH_WINDOW_SHOWN: {
+                    handleDispatchWindowShown();
+                } break;
+                case MSG_REQUEST_KEYBOARD_SHORTCUTS: {
+                    final IResultReceiver receiver = (IResultReceiver) msg.obj;
+                    final int deviceId = msg.arg1;
+                    handleRequestKeyboardShortcuts(receiver, deviceId);
+                } break;
+                case MSG_UPDATE_POINTER_ICON: {
+                    MotionEvent event = (MotionEvent) msg.obj;
+                    resetPointerIcon(event);
+                } break;
+                case MSG_POINTER_CAPTURE_CHANGED: {
+                    final boolean hasCapture = msg.arg1 != 0;
+                    handlePointerCaptureChanged(hasCapture);
+                } break;
+                case MSG_DRAW_FINISHED: {
+                    pendingDrawFinished();
+                } break;
             }
         }
     }
@@ -4203,6 +4208,18 @@
             }
         }
 
+        protected void onWindowFocusChanged(boolean hasWindowFocus) {
+            if (mNext != null) {
+                mNext.onWindowFocusChanged(hasWindowFocus);
+            }
+        }
+
+        protected void onDetachedFromWindow() {
+            if (mNext != null) {
+                mNext.onDetachedFromWindow();
+            }
+        }
+
         protected boolean shouldDropInputEvent(QueuedInputEvent q) {
             if (mView == null || !mAdded) {
                 Slog.w(mTag, "Dropping event due to root view being removed: " + q.mEvent);
@@ -4956,9 +4973,9 @@
                     final MotionEvent event = (MotionEvent)q.mEvent;
                     final int source = event.getSource();
                     if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
-                        mTrackball.cancel(event);
+                        mTrackball.cancel();
                     } else if ((source & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
-                        mJoystick.cancel(event);
+                        mJoystick.cancel();
                     } else if ((source & InputDevice.SOURCE_TOUCH_NAVIGATION)
                             == InputDevice.SOURCE_TOUCH_NAVIGATION) {
                         mTouchNavigation.cancel(event);
@@ -4967,6 +4984,18 @@
             }
             super.onDeliverToNext(q);
         }
+
+        @Override
+        protected void onWindowFocusChanged(boolean hasWindowFocus) {
+            if (!hasWindowFocus) {
+                mJoystick.cancel();
+            }
+        }
+
+        @Override
+        protected void onDetachedFromWindow() {
+            mJoystick.cancel();
+        }
     }
 
     /**
@@ -5079,7 +5108,7 @@
             }
         }
 
-        public void cancel(MotionEvent event) {
+        public void cancel() {
             mLastTime = Integer.MIN_VALUE;
 
             // If we reach this, we consumed a trackball event.
@@ -5263,14 +5292,11 @@
      * Creates dpad events from unhandled joystick movements.
      */
     final class SyntheticJoystickHandler extends Handler {
-        private final static String TAG = "SyntheticJoystickHandler";
         private final static int MSG_ENQUEUE_X_AXIS_KEY_REPEAT = 1;
         private final static int MSG_ENQUEUE_Y_AXIS_KEY_REPEAT = 2;
 
-        private int mLastXDirection;
-        private int mLastYDirection;
-        private int mLastXKeyCode;
-        private int mLastYKeyCode;
+        private final JoystickAxesState mJoystickAxesState = new JoystickAxesState();
+        private final SparseArray<KeyEvent> mDeviceKeyEvents = new SparseArray<>();
 
         public SyntheticJoystickHandler() {
             super(true);
@@ -5281,11 +5307,10 @@
             switch (msg.what) {
                 case MSG_ENQUEUE_X_AXIS_KEY_REPEAT:
                 case MSG_ENQUEUE_Y_AXIS_KEY_REPEAT: {
-                    KeyEvent oldEvent = (KeyEvent)msg.obj;
-                    KeyEvent e = KeyEvent.changeTimeRepeat(oldEvent,
-                            SystemClock.uptimeMillis(),
-                            oldEvent.getRepeatCount() + 1);
                     if (mAttachInfo.mHasWindowFocus) {
+                        KeyEvent oldEvent = (KeyEvent) msg.obj;
+                        KeyEvent e = KeyEvent.changeTimeRepeat(oldEvent,
+                                SystemClock.uptimeMillis(), oldEvent.getRepeatCount() + 1);
                         enqueueInputEvent(e);
                         Message m = obtainMessage(msg.what, e);
                         m.setAsynchronous(true);
@@ -5297,97 +5322,176 @@
 
         public void process(MotionEvent event) {
             switch(event.getActionMasked()) {
-            case MotionEvent.ACTION_CANCEL:
-                cancel(event);
-                break;
-            case MotionEvent.ACTION_MOVE:
-                update(event, true);
-                break;
-            default:
-                Log.w(mTag, "Unexpected action: " + event.getActionMasked());
+                case MotionEvent.ACTION_CANCEL:
+                    cancel();
+                    break;
+                case MotionEvent.ACTION_MOVE:
+                    update(event);
+                    break;
+                default:
+                    Log.w(mTag, "Unexpected action: " + event.getActionMasked());
             }
         }
 
-        private void cancel(MotionEvent event) {
+        private void cancel() {
             removeMessages(MSG_ENQUEUE_X_AXIS_KEY_REPEAT);
             removeMessages(MSG_ENQUEUE_Y_AXIS_KEY_REPEAT);
-            update(event, false);
+            for (int i = 0; i < mDeviceKeyEvents.size(); i++) {
+                final KeyEvent keyEvent = mDeviceKeyEvents.valueAt(i);
+                if (keyEvent != null) {
+                    enqueueInputEvent(KeyEvent.changeTimeRepeat(keyEvent,
+                            SystemClock.uptimeMillis(), 0));
+                }
+            }
+            mDeviceKeyEvents.clear();
+            mJoystickAxesState.resetState();
         }
 
-        private void update(MotionEvent event, boolean synthesizeNewKeys) {
+        private void update(MotionEvent event) {
+            final int historySize = event.getHistorySize();
+            for (int h = 0; h < historySize; h++) {
+                final long time = event.getHistoricalEventTime(h);
+                mJoystickAxesState.updateStateForAxis(event, time, MotionEvent.AXIS_X,
+                        event.getHistoricalAxisValue(MotionEvent.AXIS_X, 0, h));
+                mJoystickAxesState.updateStateForAxis(event, time, MotionEvent.AXIS_Y,
+                        event.getHistoricalAxisValue(MotionEvent.AXIS_Y, 0, h));
+                mJoystickAxesState.updateStateForAxis(event, time, MotionEvent.AXIS_HAT_X,
+                        event.getHistoricalAxisValue(MotionEvent.AXIS_HAT_X, 0, h));
+                mJoystickAxesState.updateStateForAxis(event, time, MotionEvent.AXIS_HAT_Y,
+                        event.getHistoricalAxisValue(MotionEvent.AXIS_HAT_Y, 0, h));
+            }
             final long time = event.getEventTime();
-            final int metaState = event.getMetaState();
-            final int deviceId = event.getDeviceId();
-            final int source = event.getSource();
-
-            int xDirection = joystickAxisValueToDirection(
+            mJoystickAxesState.updateStateForAxis(event, time, MotionEvent.AXIS_X,
+                    event.getAxisValue(MotionEvent.AXIS_X));
+            mJoystickAxesState.updateStateForAxis(event, time, MotionEvent.AXIS_Y,
+                    event.getAxisValue(MotionEvent.AXIS_Y));
+            mJoystickAxesState.updateStateForAxis(event, time, MotionEvent.AXIS_HAT_X,
                     event.getAxisValue(MotionEvent.AXIS_HAT_X));
-            if (xDirection == 0) {
-                xDirection = joystickAxisValueToDirection(event.getX());
-            }
-
-            int yDirection = joystickAxisValueToDirection(
+            mJoystickAxesState.updateStateForAxis(event, time, MotionEvent.AXIS_HAT_Y,
                     event.getAxisValue(MotionEvent.AXIS_HAT_Y));
-            if (yDirection == 0) {
-                yDirection = joystickAxisValueToDirection(event.getY());
-            }
-
-            if (xDirection != mLastXDirection) {
-                if (mLastXKeyCode != 0) {
-                    removeMessages(MSG_ENQUEUE_X_AXIS_KEY_REPEAT);
-                    enqueueInputEvent(new KeyEvent(time, time,
-                            KeyEvent.ACTION_UP, mLastXKeyCode, 0, metaState,
-                            deviceId, 0, KeyEvent.FLAG_FALLBACK, source));
-                    mLastXKeyCode = 0;
-                }
-
-                mLastXDirection = xDirection;
-
-                if (xDirection != 0 && synthesizeNewKeys) {
-                    mLastXKeyCode = xDirection > 0
-                            ? KeyEvent.KEYCODE_DPAD_RIGHT : KeyEvent.KEYCODE_DPAD_LEFT;
-                    final KeyEvent e = new KeyEvent(time, time,
-                            KeyEvent.ACTION_DOWN, mLastXKeyCode, 0, metaState,
-                            deviceId, 0, KeyEvent.FLAG_FALLBACK, source);
-                    enqueueInputEvent(e);
-                    Message m = obtainMessage(MSG_ENQUEUE_X_AXIS_KEY_REPEAT, e);
-                    m.setAsynchronous(true);
-                    sendMessageDelayed(m, ViewConfiguration.getKeyRepeatTimeout());
-                }
-            }
-
-            if (yDirection != mLastYDirection) {
-                if (mLastYKeyCode != 0) {
-                    removeMessages(MSG_ENQUEUE_Y_AXIS_KEY_REPEAT);
-                    enqueueInputEvent(new KeyEvent(time, time,
-                            KeyEvent.ACTION_UP, mLastYKeyCode, 0, metaState,
-                            deviceId, 0, KeyEvent.FLAG_FALLBACK, source));
-                    mLastYKeyCode = 0;
-                }
-
-                mLastYDirection = yDirection;
-
-                if (yDirection != 0 && synthesizeNewKeys) {
-                    mLastYKeyCode = yDirection > 0
-                            ? KeyEvent.KEYCODE_DPAD_DOWN : KeyEvent.KEYCODE_DPAD_UP;
-                    final KeyEvent e = new KeyEvent(time, time,
-                            KeyEvent.ACTION_DOWN, mLastYKeyCode, 0, metaState,
-                            deviceId, 0, KeyEvent.FLAG_FALLBACK, source);
-                    enqueueInputEvent(e);
-                    Message m = obtainMessage(MSG_ENQUEUE_Y_AXIS_KEY_REPEAT, e);
-                    m.setAsynchronous(true);
-                    sendMessageDelayed(m, ViewConfiguration.getKeyRepeatTimeout());
-                }
-            }
         }
 
-        private int joystickAxisValueToDirection(float value) {
-            if (value >= 0.5f) {
-                return 1;
-            } else if (value <= -0.5f) {
-                return -1;
-            } else {
-                return 0;
+        final class JoystickAxesState {
+            // State machine: from neutral state (no button press) can go into
+            // button STATE_UP_OR_LEFT or STATE_DOWN_OR_RIGHT state, emitting an ACTION_DOWN event.
+            // From STATE_UP_OR_LEFT or STATE_DOWN_OR_RIGHT state can go into neutral state,
+            // emitting an ACTION_UP event.
+            private static final int STATE_UP_OR_LEFT = -1;
+            private static final int STATE_NEUTRAL = 0;
+            private static final int STATE_DOWN_OR_RIGHT = 1;
+
+            final int[] mAxisStatesHat = {STATE_NEUTRAL, STATE_NEUTRAL}; // {AXIS_HAT_X, AXIS_HAT_Y}
+            final int[] mAxisStatesStick = {STATE_NEUTRAL, STATE_NEUTRAL}; // {AXIS_X, AXIS_Y}
+
+            void resetState() {
+                mAxisStatesHat[0] = STATE_NEUTRAL;
+                mAxisStatesHat[1] = STATE_NEUTRAL;
+                mAxisStatesStick[0] = STATE_NEUTRAL;
+                mAxisStatesStick[1] = STATE_NEUTRAL;
+            }
+
+            void updateStateForAxis(MotionEvent event, long time, int axis, float value) {
+                // Emit KeyEvent if necessary
+                // axis can be AXIS_X, AXIS_Y, AXIS_HAT_X, AXIS_HAT_Y
+                final int axisStateIndex;
+                final int repeatMessage;
+                if (isXAxis(axis)) {
+                    axisStateIndex = 0;
+                    repeatMessage = MSG_ENQUEUE_X_AXIS_KEY_REPEAT;
+                } else if (isYAxis(axis)) {
+                    axisStateIndex = 1;
+                    repeatMessage = MSG_ENQUEUE_Y_AXIS_KEY_REPEAT;
+                } else {
+                    Log.e(mTag, "Unexpected axis " + axis + " in updateStateForAxis!");
+                    return;
+                }
+                final int newState = joystickAxisValueToState(value);
+
+                final int currentState;
+                if (axis == MotionEvent.AXIS_X || axis == MotionEvent.AXIS_Y) {
+                    currentState = mAxisStatesStick[axisStateIndex];
+                } else {
+                    currentState = mAxisStatesHat[axisStateIndex];
+                }
+
+                if (currentState == newState) {
+                    return;
+                }
+
+                final int metaState = event.getMetaState();
+                final int deviceId = event.getDeviceId();
+                final int source = event.getSource();
+
+                if (currentState == STATE_DOWN_OR_RIGHT || currentState == STATE_UP_OR_LEFT) {
+                    // send a button release event
+                    final int keyCode = joystickAxisAndStateToKeycode(axis, currentState);
+                    if (keyCode != KeyEvent.KEYCODE_UNKNOWN) {
+                        enqueueInputEvent(new KeyEvent(time, time, KeyEvent.ACTION_UP, keyCode,
+                                0, metaState, deviceId, 0, KeyEvent.FLAG_FALLBACK, source));
+                        // remove the corresponding pending UP event if focus lost/view detached
+                        mDeviceKeyEvents.put(deviceId, null);
+                    }
+                    removeMessages(repeatMessage);
+                }
+
+                if (newState == STATE_DOWN_OR_RIGHT || newState == STATE_UP_OR_LEFT) {
+                    // send a button down event
+                    final int keyCode = joystickAxisAndStateToKeycode(axis, newState);
+                    if (keyCode != KeyEvent.KEYCODE_UNKNOWN) {
+                        KeyEvent keyEvent = new KeyEvent(time, time, KeyEvent.ACTION_DOWN, keyCode,
+                                0, metaState, deviceId, 0, KeyEvent.FLAG_FALLBACK, source);
+                        enqueueInputEvent(keyEvent);
+                        Message m = obtainMessage(repeatMessage, keyEvent);
+                        m.setAsynchronous(true);
+                        sendMessageDelayed(m, ViewConfiguration.getKeyRepeatTimeout());
+                        // store the corresponding ACTION_UP event so that it can be sent
+                        // if focus is lost or root view is removed
+                        mDeviceKeyEvents.put(deviceId,
+                                new KeyEvent(time, time, KeyEvent.ACTION_UP, keyCode,
+                                        0, metaState, deviceId, 0,
+                                        KeyEvent.FLAG_FALLBACK | KeyEvent.FLAG_CANCELED,
+                                        source));
+                    }
+                }
+                if (axis == MotionEvent.AXIS_X || axis == MotionEvent.AXIS_Y) {
+                    mAxisStatesStick[axisStateIndex] = newState;
+                } else {
+                    mAxisStatesHat[axisStateIndex] = newState;
+                }
+            }
+
+            private boolean isXAxis(int axis) {
+                return axis == MotionEvent.AXIS_X || axis == MotionEvent.AXIS_HAT_X;
+            }
+            private boolean isYAxis(int axis) {
+                return axis == MotionEvent.AXIS_Y || axis == MotionEvent.AXIS_HAT_Y;
+            }
+
+            private int joystickAxisAndStateToKeycode(int axis, int state) {
+                if (isXAxis(axis) && state == STATE_UP_OR_LEFT) {
+                    return KeyEvent.KEYCODE_DPAD_LEFT;
+                }
+                if (isXAxis(axis) && state == STATE_DOWN_OR_RIGHT) {
+                    return KeyEvent.KEYCODE_DPAD_RIGHT;
+                }
+                if (isYAxis(axis) && state == STATE_UP_OR_LEFT) {
+                    return KeyEvent.KEYCODE_DPAD_UP;
+                }
+                if (isYAxis(axis) && state == STATE_DOWN_OR_RIGHT) {
+                    return KeyEvent.KEYCODE_DPAD_DOWN;
+                }
+                Log.e(mTag, "Unknown axis " + axis + " or direction " + state);
+                return KeyEvent.KEYCODE_UNKNOWN; // should never happen
+            }
+
+            private int joystickAxisValueToState(float value) {
+                if (value >= 0.5f) {
+                    return STATE_DOWN_OR_RIGHT;
+                } else if (value <= -0.5f) {
+                    return STATE_UP_OR_LEFT;
+                } else {
+                    return STATE_NEUTRAL;
+                }
             }
         }
     }
@@ -6108,7 +6212,6 @@
             if (DBG) Log.d(mTag, "WindowLayout in layoutWindow:" + params);
         }
 
-        //Log.d(mTag, ">>>>>> CALLING relayout");
         if (params != null && mOrigWindowType != params.type) {
             // For compatibility with old apps, don't crash here.
             if (mTargetSdkVersion < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
@@ -6129,7 +6232,6 @@
         mPendingAlwaysConsumeNavBar =
                 (relayoutResult & WindowManagerGlobal.RELAYOUT_RES_CONSUME_ALWAYS_NAV_BAR) != 0;
 
-        //Log.d(mTag, "<<<<<< BACK FROM relayout");
         if (restore) {
             params.restore();
         }
diff --git a/android/view/ViewStructure.java b/android/view/ViewStructure.java
index f671c34..d665dde 100644
--- a/android/view/ViewStructure.java
+++ b/android/view/ViewStructure.java
@@ -365,6 +365,30 @@
     public abstract void setDataIsSensitive(boolean sensitive);
 
     /**
+     * Sets the minimum width in ems of the text associated with this view, when supported.
+     *
+     * <p>Should only be set when the node is used for autofill purposes - it will be ignored
+     * when used for Assist.
+     */
+    public void setMinTextEms(@SuppressWarnings("unused") int minEms) {}
+
+    /**
+     * Sets the maximum width in ems of the text associated with this view, when supported.
+     *
+     * <p>Should only be set when the node is used for autofill purposes - it will be ignored
+     * when used for Assist.
+     */
+    public void setMaxTextEms(@SuppressWarnings("unused") int maxEms) {}
+
+    /**
+     * Sets the maximum length of the text associated with this view, when supported.
+     *
+     * <p>Should only be set when the node is used for autofill purposes - it will be ignored
+     * when used for Assist.
+     */
+    public void setMaxTextLength(@SuppressWarnings("unused") int maxLength) {}
+
+    /**
      * Call when done populating a {@link ViewStructure} returned by
      * {@link #asyncNewChild}.
      */
diff --git a/android/view/WindowManagerInternal.java b/android/view/WindowManagerInternal.java
index 98f8dc8..69cc100 100644
--- a/android/view/WindowManagerInternal.java
+++ b/android/view/WindowManagerInternal.java
@@ -335,8 +335,8 @@
     public abstract void setOnHardKeyboardStatusChangeListener(
         OnHardKeyboardStatusChangeListener listener);
 
-    /** Returns true if the stack with the input Id is currently visible. */
-    public abstract boolean isStackVisible(int stackId);
+    /** Returns true if a stack in the windowing mode is currently visible. */
+    public abstract boolean isStackVisible(int windowingMode);
 
     /**
      * @return True if and only if the docked divider is currently in resize mode.
diff --git a/android/view/WindowManagerPolicy.java b/android/view/WindowManagerPolicy.java
index da72535..137e551 100644
--- a/android/view/WindowManagerPolicy.java
+++ b/android/view/WindowManagerPolicy.java
@@ -467,11 +467,8 @@
          */
         public boolean isDimming();
 
-        /**
-         * @return the stack id this windows belongs to, or {@link StackId#INVALID_STACK_ID} if
-         *         not attached to any stack.
-         */
-        int getStackId();
+        /** @return the current windowing mode of this window. */
+        int getWindowingMode();
 
         /**
          * Returns true if the window is current in multi-windowing mode. i.e. it shares the
diff --git a/android/view/accessibility/AccessibilityCache.java b/android/view/accessibility/AccessibilityCache.java
index 0f21c5c..d785117 100644
--- a/android/view/accessibility/AccessibilityCache.java
+++ b/android/view/accessibility/AccessibilityCache.java
@@ -329,8 +329,6 @@
                 final long oldParentId = oldInfo.getParentNodeId();
                 if (info.getParentNodeId() != oldParentId) {
                     clearSubTreeLocked(windowId, oldParentId);
-                } else {
-                    oldInfo.recycle();
                 }
            }
 
diff --git a/android/view/accessibility/AccessibilityManager.java b/android/view/accessibility/AccessibilityManager.java
index 0b9bc57..11cb046 100644
--- a/android/view/accessibility/AccessibilityManager.java
+++ b/android/view/accessibility/AccessibilityManager.java
@@ -16,152 +16,46 @@
 
 package android.view.accessibility;
 
-import static android.accessibilityservice.AccessibilityServiceInfo.FLAG_ENABLE_ACCESSIBILITY_VOLUME;
-
-import android.Manifest;
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.SdkConstant;
-import android.annotation.SystemService;
-import android.content.ComponentName;
 import android.content.Context;
-import android.content.pm.PackageManager;
 import android.content.pm.ServiceInfo;
-import android.content.res.Resources;
-import android.os.Binder;
 import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
-import android.os.Process;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.SystemClock;
-import android.os.UserHandle;
-import android.util.ArrayMap;
-import android.util.Log;
-import android.util.SparseArray;
 import android.view.IWindow;
 import android.view.View;
 
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.IntPair;
-
-import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 
 /**
- * System level service that serves as an event dispatch for {@link AccessibilityEvent}s,
- * and provides facilities for querying the accessibility state of the system.
- * Accessibility events are generated when something notable happens in the user interface,
+ * System level service that serves as an event dispatch for {@link AccessibilityEvent}s.
+ * Such events are generated when something notable happens in the user interface,
  * for example an {@link android.app.Activity} starts, the focus or selection of a
  * {@link android.view.View} changes etc. Parties interested in handling accessibility
  * events implement and register an accessibility service which extends
- * {@link android.accessibilityservice.AccessibilityService}.
+ * {@code android.accessibilityservice.AccessibilityService}.
  *
  * @see AccessibilityEvent
- * @see AccessibilityNodeInfo
- * @see android.accessibilityservice.AccessibilityService
- * @see Context#getSystemService
- * @see Context#ACCESSIBILITY_SERVICE
+ * @see android.content.Context#getSystemService
  */
-@SystemService(Context.ACCESSIBILITY_SERVICE)
+@SuppressWarnings("UnusedDeclaration")
 public final class AccessibilityManager {
-    private static final boolean DEBUG = false;
 
-    private static final String LOG_TAG = "AccessibilityManager";
+    private static AccessibilityManager sInstance = new AccessibilityManager(null, null, 0);
 
-    /** @hide */
-    public static final int STATE_FLAG_ACCESSIBILITY_ENABLED = 0x00000001;
-
-    /** @hide */
-    public static final int STATE_FLAG_TOUCH_EXPLORATION_ENABLED = 0x00000002;
-
-    /** @hide */
-    public static final int STATE_FLAG_HIGH_TEXT_CONTRAST_ENABLED = 0x00000004;
-
-    /** @hide */
-    public static final int DALTONIZER_DISABLED = -1;
-
-    /** @hide */
-    public static final int DALTONIZER_SIMULATE_MONOCHROMACY = 0;
-
-    /** @hide */
-    public static final int DALTONIZER_CORRECT_DEUTERANOMALY = 12;
-
-    /** @hide */
-    public static final int AUTOCLICK_DELAY_DEFAULT = 600;
 
     /**
-     * Activity action: Launch UI to manage which accessibility service or feature is assigned
-     * to the navigation bar Accessibility button.
-     * <p>
-     * Input: Nothing.
-     * </p>
-     * <p>
-     * Output: Nothing.
-     * </p>
-     *
-     * @hide
-     */
-    @SdkConstant(SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION)
-    public static final String ACTION_CHOOSE_ACCESSIBILITY_BUTTON =
-            "com.android.internal.intent.action.CHOOSE_ACCESSIBILITY_BUTTON";
-
-    static final Object sInstanceSync = new Object();
-
-    private static AccessibilityManager sInstance;
-
-    private final Object mLock = new Object();
-
-    private IAccessibilityManager mService;
-
-    final int mUserId;
-
-    final Handler mHandler;
-
-    final Handler.Callback mCallback;
-
-    boolean mIsEnabled;
-
-    int mRelevantEventTypes = AccessibilityEvent.TYPES_ALL_MASK;
-
-    boolean mIsTouchExplorationEnabled;
-
-    boolean mIsHighTextContrastEnabled;
-
-    private final ArrayMap<AccessibilityStateChangeListener, Handler>
-            mAccessibilityStateChangeListeners = new ArrayMap<>();
-
-    private final ArrayMap<TouchExplorationStateChangeListener, Handler>
-            mTouchExplorationStateChangeListeners = new ArrayMap<>();
-
-    private final ArrayMap<HighTextContrastChangeListener, Handler>
-            mHighTextContrastStateChangeListeners = new ArrayMap<>();
-
-    private final ArrayMap<AccessibilityServicesStateChangeListener, Handler>
-            mServicesStateChangeListeners = new ArrayMap<>();
-
-    /**
-     * Map from a view's accessibility id to the list of request preparers set for that view
-     */
-    private SparseArray<List<AccessibilityRequestPreparer>> mRequestPreparerLists;
-
-    /**
-     * Listener for the system accessibility state. To listen for changes to the
-     * accessibility state on the device, implement this interface and register
-     * it with the system by calling {@link #addAccessibilityStateChangeListener}.
+     * Listener for the accessibility state.
      */
     public interface AccessibilityStateChangeListener {
 
         /**
-         * Called when the accessibility enabled state changes.
+         * Called back on change in the accessibility state.
          *
          * @param enabled Whether accessibility is enabled.
          */
-        void onAccessibilityStateChanged(boolean enabled);
+        public void onAccessibilityStateChanged(boolean enabled);
     }
 
     /**
@@ -177,24 +71,7 @@
          *
          * @param enabled Whether touch exploration is enabled.
          */
-        void onTouchExplorationStateChanged(boolean enabled);
-    }
-
-    /**
-     * Listener for changes to the state of accessibility services. Changes include services being
-     * enabled or disabled, or changes to the {@link AccessibilityServiceInfo} of a running service.
-     * {@see #addAccessibilityServicesStateChangeListener}.
-     *
-     * @hide
-     */
-    public interface AccessibilityServicesStateChangeListener {
-
-        /**
-         * Called when the state of accessibility services changes.
-         *
-         * @param manager The manager that is calling back
-         */
-        void onAccessibilityServicesStateChanged(AccessibilityManager manager);
+        public void onTouchExplorationStateChanged(boolean enabled);
     }
 
     /**
@@ -202,8 +79,6 @@
      * the high text contrast state on the device, implement this interface and
      * register it with the system by calling
      * {@link #addHighTextContrastStateChangeListener}.
-     *
-     * @hide
      */
     public interface HighTextContrastChangeListener {
 
@@ -212,72 +87,26 @@
          *
          * @param enabled Whether high text contrast is enabled.
          */
-        void onHighTextContrastStateChanged(boolean enabled);
+        public void onHighTextContrastStateChanged(boolean enabled);
     }
 
     private final IAccessibilityManagerClient.Stub mClient =
             new IAccessibilityManagerClient.Stub() {
-        @Override
-        public void setState(int state) {
-            // We do not want to change this immediately as the application may
-            // have already checked that accessibility is on and fired an event,
-            // that is now propagating up the view tree, Hence, if accessibility
-            // is now off an exception will be thrown. We want to have the exception
-            // enforcement to guard against apps that fire unnecessary accessibility
-            // events when accessibility is off.
-            mHandler.obtainMessage(MyCallback.MSG_SET_STATE, state, 0).sendToTarget();
-        }
-
-        @Override
-        public void notifyServicesStateChanged() {
-            final ArrayMap<AccessibilityServicesStateChangeListener, Handler> listeners;
-            synchronized (mLock) {
-                if (mServicesStateChangeListeners.isEmpty()) {
-                    return;
+                public void setState(int state) {
                 }
-                listeners = new ArrayMap<>(mServicesStateChangeListeners);
-            }
 
-            int numListeners = listeners.size();
-            for (int i = 0; i < numListeners; i++) {
-                final AccessibilityServicesStateChangeListener listener =
-                        mServicesStateChangeListeners.keyAt(i);
-                mServicesStateChangeListeners.valueAt(i).post(() -> listener
-                        .onAccessibilityServicesStateChanged(AccessibilityManager.this));
-            }
-        }
+                public void notifyServicesStateChanged() {
+                }
 
-        @Override
-        public void setRelevantEventTypes(int eventTypes) {
-            mRelevantEventTypes = eventTypes;
-        }
-    };
+                public void setRelevantEventTypes(int eventTypes) {
+                }
+            };
 
     /**
      * Get an AccessibilityManager instance (create one if necessary).
      *
-     * @param context Context in which this manager operates.
-     *
-     * @hide
      */
     public static AccessibilityManager getInstance(Context context) {
-        synchronized (sInstanceSync) {
-            if (sInstance == null) {
-                final int userId;
-                if (Binder.getCallingUid() == Process.SYSTEM_UID
-                        || context.checkCallingOrSelfPermission(
-                                Manifest.permission.INTERACT_ACROSS_USERS)
-                                        == PackageManager.PERMISSION_GRANTED
-                        || context.checkCallingOrSelfPermission(
-                                Manifest.permission.INTERACT_ACROSS_USERS_FULL)
-                                        == PackageManager.PERMISSION_GRANTED) {
-                    userId = UserHandle.USER_CURRENT;
-                } else {
-                    userId = UserHandle.myUserId();
-                }
-                sInstance = new AccessibilityManager(context, null, userId);
-            }
-        }
         return sInstance;
     }
 
@@ -285,68 +114,21 @@
      * Create an instance.
      *
      * @param context A {@link Context}.
-     * @param service An interface to the backing service.
-     * @param userId User id under which to run.
-     *
-     * @hide
      */
     public AccessibilityManager(Context context, IAccessibilityManager service, int userId) {
-        // Constructor can't be chained because we can't create an instance of an inner class
-        // before calling another constructor.
-        mCallback = new MyCallback();
-        mHandler = new Handler(context.getMainLooper(), mCallback);
-        mUserId = userId;
-        synchronized (mLock) {
-            tryConnectToServiceLocked(service);
-        }
     }
 
-    /**
-     * Create an instance.
-     *
-     * @param handler The handler to use
-     * @param service An interface to the backing service.
-     * @param userId User id under which to run.
-     *
-     * @hide
-     */
-    public AccessibilityManager(Handler handler, IAccessibilityManager service, int userId) {
-        mCallback = new MyCallback();
-        mHandler = handler;
-        mUserId = userId;
-        synchronized (mLock) {
-            tryConnectToServiceLocked(service);
-        }
-    }
-
-    /**
-     * @hide
-     */
     public IAccessibilityManagerClient getClient() {
         return mClient;
     }
 
     /**
-     * @hide
-     */
-    @VisibleForTesting
-    public Handler.Callback getCallback() {
-        return mCallback;
-    }
-
-    /**
-     * Returns if the accessibility in the system is enabled.
+     * Returns if the {@link AccessibilityManager} is enabled.
      *
-     * @return True if accessibility is enabled, false otherwise.
+     * @return True if this {@link AccessibilityManager} is enabled, false otherwise.
      */
     public boolean isEnabled() {
-        synchronized (mLock) {
-            IAccessibilityManager service = getServiceLocked();
-            if (service == null) {
-                return false;
-            }
-            return mIsEnabled;
-        }
+        return false;
     }
 
     /**
@@ -355,13 +137,7 @@
      * @return True if touch exploration is enabled, false otherwise.
      */
     public boolean isTouchExplorationEnabled() {
-        synchronized (mLock) {
-            IAccessibilityManager service = getServiceLocked();
-            if (service == null) {
-                return false;
-            }
-            return mIsTouchExplorationEnabled;
-        }
+        return true;
     }
 
     /**
@@ -371,169 +147,35 @@
      * doing its own rendering and does not rely on the platform rendering pipeline.
      * </p>
      *
-     * @return True if high text contrast is enabled, false otherwise.
-     *
-     * @hide
      */
     public boolean isHighTextContrastEnabled() {
-        synchronized (mLock) {
-            IAccessibilityManager service = getServiceLocked();
-            if (service == null) {
-                return false;
-            }
-            return mIsHighTextContrastEnabled;
-        }
+        return false;
     }
 
     /**
      * Sends an {@link AccessibilityEvent}.
-     *
-     * @param event The event to send.
-     *
-     * @throws IllegalStateException if accessibility is not enabled.
-     *
-     * <strong>Note:</strong> The preferred mechanism for sending custom accessibility
-     * events is through calling
-     * {@link android.view.ViewParent#requestSendAccessibilityEvent(View, AccessibilityEvent)}
-     * instead of this method to allow predecessors to augment/filter events sent by
-     * their descendants.
      */
     public void sendAccessibilityEvent(AccessibilityEvent event) {
-        final IAccessibilityManager service;
-        final int userId;
-        synchronized (mLock) {
-            service = getServiceLocked();
-            if (service == null) {
-                return;
-            }
-            if (!mIsEnabled) {
-                Looper myLooper = Looper.myLooper();
-                if (myLooper == Looper.getMainLooper()) {
-                    throw new IllegalStateException(
-                            "Accessibility off. Did you forget to check that?");
-                } else {
-                    // If we're not running on the thread with the main looper, it's possible for
-                    // the state of accessibility to change between checking isEnabled and
-                    // calling this method. So just log the error rather than throwing the
-                    // exception.
-                    Log.e(LOG_TAG, "AccessibilityEvent sent with accessibility disabled");
-                    return;
-                }
-            }
-            if ((event.getEventType() & mRelevantEventTypes) == 0) {
-                if (DEBUG) {
-                    Log.i(LOG_TAG, "Not dispatching irrelevant event: " + event
-                            + " that is not among "
-                            + AccessibilityEvent.eventTypeToString(mRelevantEventTypes));
-                }
-                return;
-            }
-            userId = mUserId;
-        }
-        try {
-            event.setEventTime(SystemClock.uptimeMillis());
-            // it is possible that this manager is in the same process as the service but
-            // client using it is called through Binder from another process. Example: MMS
-            // app adds a SMS notification and the NotificationManagerService calls this method
-            long identityToken = Binder.clearCallingIdentity();
-            service.sendAccessibilityEvent(event, userId);
-            Binder.restoreCallingIdentity(identityToken);
-            if (DEBUG) {
-                Log.i(LOG_TAG, event + " sent");
-            }
-        } catch (RemoteException re) {
-            Log.e(LOG_TAG, "Error during sending " + event + " ", re);
-        } finally {
-            event.recycle();
-        }
     }
 
     /**
-     * Requests feedback interruption from all accessibility services.
+     * Requests interruption of the accessibility feedback from all accessibility services.
      */
     public void interrupt() {
-        final IAccessibilityManager service;
-        final int userId;
-        synchronized (mLock) {
-            service = getServiceLocked();
-            if (service == null) {
-                return;
-            }
-            if (!mIsEnabled) {
-                Looper myLooper = Looper.myLooper();
-                if (myLooper == Looper.getMainLooper()) {
-                    throw new IllegalStateException(
-                            "Accessibility off. Did you forget to check that?");
-                } else {
-                    // If we're not running on the thread with the main looper, it's possible for
-                    // the state of accessibility to change between checking isEnabled and
-                    // calling this method. So just log the error rather than throwing the
-                    // exception.
-                    Log.e(LOG_TAG, "Interrupt called with accessibility disabled");
-                    return;
-                }
-            }
-            userId = mUserId;
-        }
-        try {
-            service.interrupt(userId);
-            if (DEBUG) {
-                Log.i(LOG_TAG, "Requested interrupt from all services");
-            }
-        } catch (RemoteException re) {
-            Log.e(LOG_TAG, "Error while requesting interrupt from all services. ", re);
-        }
     }
 
     /**
      * Returns the {@link ServiceInfo}s of the installed accessibility services.
      *
      * @return An unmodifiable list with {@link ServiceInfo}s.
-     *
-     * @deprecated Use {@link #getInstalledAccessibilityServiceList()}
      */
     @Deprecated
     public List<ServiceInfo> getAccessibilityServiceList() {
-        List<AccessibilityServiceInfo> infos = getInstalledAccessibilityServiceList();
-        List<ServiceInfo> services = new ArrayList<>();
-        final int infoCount = infos.size();
-        for (int i = 0; i < infoCount; i++) {
-            AccessibilityServiceInfo info = infos.get(i);
-            services.add(info.getResolveInfo().serviceInfo);
-        }
-        return Collections.unmodifiableList(services);
+        return Collections.emptyList();
     }
 
-    /**
-     * Returns the {@link AccessibilityServiceInfo}s of the installed accessibility services.
-     *
-     * @return An unmodifiable list with {@link AccessibilityServiceInfo}s.
-     */
     public List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList() {
-        final IAccessibilityManager service;
-        final int userId;
-        synchronized (mLock) {
-            service = getServiceLocked();
-            if (service == null) {
-                return Collections.emptyList();
-            }
-            userId = mUserId;
-        }
-
-        List<AccessibilityServiceInfo> services = null;
-        try {
-            services = service.getInstalledAccessibilityServiceList(userId);
-            if (DEBUG) {
-                Log.i(LOG_TAG, "Installed AccessibilityServices " + services);
-            }
-        } catch (RemoteException re) {
-            Log.e(LOG_TAG, "Error while obtaining the installed AccessibilityServices. ", re);
-        }
-        if (services != null) {
-            return Collections.unmodifiableList(services);
-        } else {
-            return Collections.emptyList();
-        }
+        return Collections.emptyList();
     }
 
     /**
@@ -548,48 +190,21 @@
      * @see AccessibilityServiceInfo#FEEDBACK_HAPTIC
      * @see AccessibilityServiceInfo#FEEDBACK_SPOKEN
      * @see AccessibilityServiceInfo#FEEDBACK_VISUAL
-     * @see AccessibilityServiceInfo#FEEDBACK_BRAILLE
      */
     public List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList(
             int feedbackTypeFlags) {
-        final IAccessibilityManager service;
-        final int userId;
-        synchronized (mLock) {
-            service = getServiceLocked();
-            if (service == null) {
-                return Collections.emptyList();
-            }
-            userId = mUserId;
-        }
-
-        List<AccessibilityServiceInfo> services = null;
-        try {
-            services = service.getEnabledAccessibilityServiceList(feedbackTypeFlags, userId);
-            if (DEBUG) {
-                Log.i(LOG_TAG, "Installed AccessibilityServices " + services);
-            }
-        } catch (RemoteException re) {
-            Log.e(LOG_TAG, "Error while obtaining the installed AccessibilityServices. ", re);
-        }
-        if (services != null) {
-            return Collections.unmodifiableList(services);
-        } else {
-            return Collections.emptyList();
-        }
+        return Collections.emptyList();
     }
 
     /**
      * Registers an {@link AccessibilityStateChangeListener} for changes in
-     * the global accessibility state of the system. Equivalent to calling
-     * {@link #addAccessibilityStateChangeListener(AccessibilityStateChangeListener, Handler)}
-     * with a null handler.
+     * the global accessibility state of the system.
      *
      * @param listener The listener.
-     * @return Always returns {@code true}.
+     * @return True if successfully registered.
      */
     public boolean addAccessibilityStateChangeListener(
-            @NonNull AccessibilityStateChangeListener listener) {
-        addAccessibilityStateChangeListener(listener, null);
+            AccessibilityStateChangeListener listener) {
         return true;
     }
 
@@ -603,40 +218,22 @@
      *                for a callback on the process's main handler.
      */
     public void addAccessibilityStateChangeListener(
-            @NonNull AccessibilityStateChangeListener listener, @Nullable Handler handler) {
-        synchronized (mLock) {
-            mAccessibilityStateChangeListeners
-                    .put(listener, (handler == null) ? mHandler : handler);
-        }
-    }
+            @NonNull AccessibilityStateChangeListener listener, @Nullable Handler handler) {}
 
-    /**
-     * Unregisters an {@link AccessibilityStateChangeListener}.
-     *
-     * @param listener The listener.
-     * @return True if the listener was previously registered.
-     */
     public boolean removeAccessibilityStateChangeListener(
-            @NonNull AccessibilityStateChangeListener listener) {
-        synchronized (mLock) {
-            int index = mAccessibilityStateChangeListeners.indexOfKey(listener);
-            mAccessibilityStateChangeListeners.remove(listener);
-            return (index >= 0);
-        }
+            AccessibilityStateChangeListener listener) {
+        return true;
     }
 
     /**
      * Registers a {@link TouchExplorationStateChangeListener} for changes in
-     * the global touch exploration state of the system. Equivalent to calling
-     * {@link #addTouchExplorationStateChangeListener(TouchExplorationStateChangeListener, Handler)}
-     * with a null handler.
+     * the global touch exploration state of the system.
      *
      * @param listener The listener.
-     * @return Always returns {@code true}.
+     * @return True if successfully registered.
      */
     public boolean addTouchExplorationStateChangeListener(
             @NonNull TouchExplorationStateChangeListener listener) {
-        addTouchExplorationStateChangeListener(listener, null);
         return true;
     }
 
@@ -650,103 +247,17 @@
      *                for a callback on the process's main handler.
      */
     public void addTouchExplorationStateChangeListener(
-            @NonNull TouchExplorationStateChangeListener listener, @Nullable Handler handler) {
-        synchronized (mLock) {
-            mTouchExplorationStateChangeListeners
-                    .put(listener, (handler == null) ? mHandler : handler);
-        }
-    }
+            @NonNull TouchExplorationStateChangeListener listener, @Nullable Handler handler) {}
 
     /**
      * Unregisters a {@link TouchExplorationStateChangeListener}.
      *
      * @param listener The listener.
-     * @return True if listener was previously registered.
+     * @return True if successfully unregistered.
      */
     public boolean removeTouchExplorationStateChangeListener(
             @NonNull TouchExplorationStateChangeListener listener) {
-        synchronized (mLock) {
-            int index = mTouchExplorationStateChangeListeners.indexOfKey(listener);
-            mTouchExplorationStateChangeListeners.remove(listener);
-            return (index >= 0);
-        }
-    }
-
-    /**
-     * Registers a {@link AccessibilityServicesStateChangeListener}.
-     *
-     * @param listener The listener.
-     * @param handler The handler on which the listener should be called back, or {@code null}
-     *                for a callback on the process's main handler.
-     * @hide
-     */
-    public void addAccessibilityServicesStateChangeListener(
-            @NonNull AccessibilityServicesStateChangeListener listener, @Nullable Handler handler) {
-        synchronized (mLock) {
-            mServicesStateChangeListeners
-                    .put(listener, (handler == null) ? mHandler : handler);
-        }
-    }
-
-    /**
-     * Unregisters a {@link AccessibilityServicesStateChangeListener}.
-     *
-     * @param listener The listener.
-     *
-     * @hide
-     */
-    public void removeAccessibilityServicesStateChangeListener(
-            @NonNull AccessibilityServicesStateChangeListener listener) {
-        // Final CopyOnWriteArrayList - no lock needed.
-        mServicesStateChangeListeners.remove(listener);
-    }
-
-    /**
-     * Registers a {@link AccessibilityRequestPreparer}.
-     */
-    public void addAccessibilityRequestPreparer(AccessibilityRequestPreparer preparer) {
-        if (mRequestPreparerLists == null) {
-            mRequestPreparerLists = new SparseArray<>(1);
-        }
-        int id = preparer.getView().getAccessibilityViewId();
-        List<AccessibilityRequestPreparer> requestPreparerList = mRequestPreparerLists.get(id);
-        if (requestPreparerList == null) {
-            requestPreparerList = new ArrayList<>(1);
-            mRequestPreparerLists.put(id, requestPreparerList);
-        }
-        requestPreparerList.add(preparer);
-    }
-
-    /**
-     * Unregisters a {@link AccessibilityRequestPreparer}.
-     */
-    public void removeAccessibilityRequestPreparer(AccessibilityRequestPreparer preparer) {
-        if (mRequestPreparerLists == null) {
-            return;
-        }
-        int viewId = preparer.getView().getAccessibilityViewId();
-        List<AccessibilityRequestPreparer> requestPreparerList = mRequestPreparerLists.get(viewId);
-        if (requestPreparerList != null) {
-            requestPreparerList.remove(preparer);
-            if (requestPreparerList.isEmpty()) {
-                mRequestPreparerLists.remove(viewId);
-            }
-        }
-    }
-
-    /**
-     * Get the preparers that are registered for an accessibility ID
-     *
-     * @param id The ID of interest
-     * @return The list of preparers, or {@code null} if there are none.
-     *
-     * @hide
-     */
-    public List<AccessibilityRequestPreparer> getRequestPreparersForAccessibilityId(int id) {
-        if (mRequestPreparerLists == null) {
-            return null;
-        }
-        return mRequestPreparerLists.get(id);
+        return true;
     }
 
     /**
@@ -758,12 +269,7 @@
      * @hide
      */
     public void addHighTextContrastStateChangeListener(
-            @NonNull HighTextContrastChangeListener listener, @Nullable Handler handler) {
-        synchronized (mLock) {
-            mHighTextContrastStateChangeListeners
-                    .put(listener, (handler == null) ? mHandler : handler);
-        }
-    }
+            @NonNull HighTextContrastChangeListener listener, @Nullable Handler handler) {}
 
     /**
      * Unregisters a {@link HighTextContrastChangeListener}.
@@ -773,51 +279,7 @@
      * @hide
      */
     public void removeHighTextContrastStateChangeListener(
-            @NonNull HighTextContrastChangeListener listener) {
-        synchronized (mLock) {
-            mHighTextContrastStateChangeListeners.remove(listener);
-        }
-    }
-
-    /**
-     * Check if the accessibility volume stream is active.
-     *
-     * @return True if accessibility volume is active (i.e. some service has requested it). False
-     * otherwise.
-     * @hide
-     */
-    public boolean isAccessibilityVolumeStreamActive() {
-        List<AccessibilityServiceInfo> serviceInfos =
-                getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
-        for (int i = 0; i < serviceInfos.size(); i++) {
-            if ((serviceInfos.get(i).flags & FLAG_ENABLE_ACCESSIBILITY_VOLUME) != 0) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    /**
-     * Report a fingerprint gesture to accessibility. Only available for the system process.
-     *
-     * @param keyCode The key code of the gesture
-     * @return {@code true} if accessibility consumes the event. {@code false} if not.
-     * @hide
-     */
-    public boolean sendFingerprintGesture(int keyCode) {
-        final IAccessibilityManager service;
-        synchronized (mLock) {
-            service = getServiceLocked();
-            if (service == null) {
-                return false;
-            }
-        }
-        try {
-            return service.sendFingerprintGesture(keyCode);
-        } catch (RemoteException e) {
-            return false;
-        }
-    }
+            @NonNull HighTextContrastChangeListener listener) {}
 
     /**
      * Sets the current state and notifies listeners, if necessary.
@@ -825,314 +287,14 @@
      * @param stateFlags The state flags.
      */
     private void setStateLocked(int stateFlags) {
-        final boolean enabled = (stateFlags & STATE_FLAG_ACCESSIBILITY_ENABLED) != 0;
-        final boolean touchExplorationEnabled =
-                (stateFlags & STATE_FLAG_TOUCH_EXPLORATION_ENABLED) != 0;
-        final boolean highTextContrastEnabled =
-                (stateFlags & STATE_FLAG_HIGH_TEXT_CONTRAST_ENABLED) != 0;
-
-        final boolean wasEnabled = mIsEnabled;
-        final boolean wasTouchExplorationEnabled = mIsTouchExplorationEnabled;
-        final boolean wasHighTextContrastEnabled = mIsHighTextContrastEnabled;
-
-        // Ensure listeners get current state from isZzzEnabled() calls.
-        mIsEnabled = enabled;
-        mIsTouchExplorationEnabled = touchExplorationEnabled;
-        mIsHighTextContrastEnabled = highTextContrastEnabled;
-
-        if (wasEnabled != enabled) {
-            notifyAccessibilityStateChanged();
-        }
-
-        if (wasTouchExplorationEnabled != touchExplorationEnabled) {
-            notifyTouchExplorationStateChanged();
-        }
-
-        if (wasHighTextContrastEnabled != highTextContrastEnabled) {
-            notifyHighTextContrastStateChanged();
-        }
     }
 
-    /**
-     * Find an installed service with the specified {@link ComponentName}.
-     *
-     * @param componentName The name to match to the service.
-     *
-     * @return The info corresponding to the installed service, or {@code null} if no such service
-     * is installed.
-     * @hide
-     */
-    public AccessibilityServiceInfo getInstalledServiceInfoWithComponentName(
-            ComponentName componentName) {
-        final List<AccessibilityServiceInfo> installedServiceInfos =
-                getInstalledAccessibilityServiceList();
-        if ((installedServiceInfos == null) || (componentName == null)) {
-            return null;
-        }
-        for (int i = 0; i < installedServiceInfos.size(); i++) {
-            if (componentName.equals(installedServiceInfos.get(i).getComponentName())) {
-                return installedServiceInfos.get(i);
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Adds an accessibility interaction connection interface for a given window.
-     * @param windowToken The window token to which a connection is added.
-     * @param connection The connection.
-     *
-     * @hide
-     */
     public int addAccessibilityInteractionConnection(IWindow windowToken,
             IAccessibilityInteractionConnection connection) {
-        final IAccessibilityManager service;
-        final int userId;
-        synchronized (mLock) {
-            service = getServiceLocked();
-            if (service == null) {
-                return View.NO_ID;
-            }
-            userId = mUserId;
-        }
-        try {
-            return service.addAccessibilityInteractionConnection(windowToken, connection, userId);
-        } catch (RemoteException re) {
-            Log.e(LOG_TAG, "Error while adding an accessibility interaction connection. ", re);
-        }
         return View.NO_ID;
     }
 
-    /**
-     * Removed an accessibility interaction connection interface for a given window.
-     * @param windowToken The window token to which a connection is removed.
-     *
-     * @hide
-     */
     public void removeAccessibilityInteractionConnection(IWindow windowToken) {
-        final IAccessibilityManager service;
-        synchronized (mLock) {
-            service = getServiceLocked();
-            if (service == null) {
-                return;
-            }
-        }
-        try {
-            service.removeAccessibilityInteractionConnection(windowToken);
-        } catch (RemoteException re) {
-            Log.e(LOG_TAG, "Error while removing an accessibility interaction connection. ", re);
-        }
     }
 
-    /**
-     * Perform the accessibility shortcut if the caller has permission.
-     *
-     * @hide
-     */
-    public void performAccessibilityShortcut() {
-        final IAccessibilityManager service;
-        synchronized (mLock) {
-            service = getServiceLocked();
-            if (service == null) {
-                return;
-            }
-        }
-        try {
-            service.performAccessibilityShortcut();
-        } catch (RemoteException re) {
-            Log.e(LOG_TAG, "Error performing accessibility shortcut. ", re);
-        }
-    }
-
-    /**
-     * Notifies that the accessibility button in the system's navigation area has been clicked
-     *
-     * @hide
-     */
-    public void notifyAccessibilityButtonClicked() {
-        final IAccessibilityManager service;
-        synchronized (mLock) {
-            service = getServiceLocked();
-            if (service == null) {
-                return;
-            }
-        }
-        try {
-            service.notifyAccessibilityButtonClicked();
-        } catch (RemoteException re) {
-            Log.e(LOG_TAG, "Error while dispatching accessibility button click", re);
-        }
-    }
-
-    /**
-     * Notifies that the visibility of the accessibility button in the system's navigation area
-     * has changed.
-     *
-     * @param shown {@code true} if the accessibility button is visible within the system
-     *                  navigation area, {@code false} otherwise
-     * @hide
-     */
-    public void notifyAccessibilityButtonVisibilityChanged(boolean shown) {
-        final IAccessibilityManager service;
-        synchronized (mLock) {
-            service = getServiceLocked();
-            if (service == null) {
-                return;
-            }
-        }
-        try {
-            service.notifyAccessibilityButtonVisibilityChanged(shown);
-        } catch (RemoteException re) {
-            Log.e(LOG_TAG, "Error while dispatching accessibility button visibility change", re);
-        }
-    }
-
-    /**
-     * Set an IAccessibilityInteractionConnection to replace the actions of a picture-in-picture
-     * window. Intended for use by the System UI only.
-     *
-     * @param connection The connection to handle the actions. Set to {@code null} to avoid
-     * affecting the actions.
-     *
-     * @hide
-     */
-    public void setPictureInPictureActionReplacingConnection(
-            @Nullable IAccessibilityInteractionConnection connection) {
-        final IAccessibilityManager service;
-        synchronized (mLock) {
-            service = getServiceLocked();
-            if (service == null) {
-                return;
-            }
-        }
-        try {
-            service.setPictureInPictureActionReplacingConnection(connection);
-        } catch (RemoteException re) {
-            Log.e(LOG_TAG, "Error setting picture in picture action replacement", re);
-        }
-    }
-
-    private IAccessibilityManager getServiceLocked() {
-        if (mService == null) {
-            tryConnectToServiceLocked(null);
-        }
-        return mService;
-    }
-
-    private void tryConnectToServiceLocked(IAccessibilityManager service) {
-        if (service == null) {
-            IBinder iBinder = ServiceManager.getService(Context.ACCESSIBILITY_SERVICE);
-            if (iBinder == null) {
-                return;
-            }
-            service = IAccessibilityManager.Stub.asInterface(iBinder);
-        }
-
-        try {
-            final long userStateAndRelevantEvents = service.addClient(mClient, mUserId);
-            setStateLocked(IntPair.first(userStateAndRelevantEvents));
-            mRelevantEventTypes = IntPair.second(userStateAndRelevantEvents);
-            mService = service;
-        } catch (RemoteException re) {
-            Log.e(LOG_TAG, "AccessibilityManagerService is dead", re);
-        }
-    }
-
-    /**
-     * Notifies the registered {@link AccessibilityStateChangeListener}s.
-     */
-    private void notifyAccessibilityStateChanged() {
-        final boolean isEnabled;
-        final ArrayMap<AccessibilityStateChangeListener, Handler> listeners;
-        synchronized (mLock) {
-            if (mAccessibilityStateChangeListeners.isEmpty()) {
-                return;
-            }
-            isEnabled = mIsEnabled;
-            listeners = new ArrayMap<>(mAccessibilityStateChangeListeners);
-        }
-
-        int numListeners = listeners.size();
-        for (int i = 0; i < numListeners; i++) {
-            final AccessibilityStateChangeListener listener =
-                    mAccessibilityStateChangeListeners.keyAt(i);
-            mAccessibilityStateChangeListeners.valueAt(i)
-                    .post(() -> listener.onAccessibilityStateChanged(isEnabled));
-        }
-    }
-
-    /**
-     * Notifies the registered {@link TouchExplorationStateChangeListener}s.
-     */
-    private void notifyTouchExplorationStateChanged() {
-        final boolean isTouchExplorationEnabled;
-        final ArrayMap<TouchExplorationStateChangeListener, Handler> listeners;
-        synchronized (mLock) {
-            if (mTouchExplorationStateChangeListeners.isEmpty()) {
-                return;
-            }
-            isTouchExplorationEnabled = mIsTouchExplorationEnabled;
-            listeners = new ArrayMap<>(mTouchExplorationStateChangeListeners);
-        }
-
-        int numListeners = listeners.size();
-        for (int i = 0; i < numListeners; i++) {
-            final TouchExplorationStateChangeListener listener =
-                    mTouchExplorationStateChangeListeners.keyAt(i);
-            mTouchExplorationStateChangeListeners.valueAt(i)
-                    .post(() -> listener.onTouchExplorationStateChanged(isTouchExplorationEnabled));
-        }
-    }
-
-    /**
-     * Notifies the registered {@link HighTextContrastChangeListener}s.
-     */
-    private void notifyHighTextContrastStateChanged() {
-        final boolean isHighTextContrastEnabled;
-        final ArrayMap<HighTextContrastChangeListener, Handler> listeners;
-        synchronized (mLock) {
-            if (mHighTextContrastStateChangeListeners.isEmpty()) {
-                return;
-            }
-            isHighTextContrastEnabled = mIsHighTextContrastEnabled;
-            listeners = new ArrayMap<>(mHighTextContrastStateChangeListeners);
-        }
-
-        int numListeners = listeners.size();
-        for (int i = 0; i < numListeners; i++) {
-            final HighTextContrastChangeListener listener =
-                    mHighTextContrastStateChangeListeners.keyAt(i);
-            mHighTextContrastStateChangeListeners.valueAt(i)
-                    .post(() -> listener.onHighTextContrastStateChanged(isHighTextContrastEnabled));
-        }
-    }
-
-    /**
-     * Determines if the accessibility button within the system navigation area is supported.
-     *
-     * @return {@code true} if the accessibility button is supported on this device,
-     * {@code false} otherwise
-     */
-    public static boolean isAccessibilityButtonSupported() {
-        final Resources res = Resources.getSystem();
-        return res.getBoolean(com.android.internal.R.bool.config_showNavigationBar);
-    }
-
-    private final class MyCallback implements Handler.Callback {
-        public static final int MSG_SET_STATE = 1;
-
-        @Override
-        public boolean handleMessage(Message message) {
-            switch (message.what) {
-                case MSG_SET_STATE: {
-                    // See comment at mClient
-                    final int state = message.arg1;
-                    synchronized (mLock) {
-                        setStateLocked(state);
-                    }
-                } break;
-            }
-            return true;
-        }
-    }
 }
diff --git a/android/view/autofill/AutofillManager.java b/android/view/autofill/AutofillManager.java
index 4fb2a99..e564fa3 100644
--- a/android/view/autofill/AutofillManager.java
+++ b/android/view/autofill/AutofillManager.java
@@ -91,10 +91,10 @@
  * </ul>
  *
  * <p>When the service returns datasets, the Android System displays an autofill dataset picker
- * UI affordance associated with the view, when the view is focused on and is part of a dataset.
- * The application can be notified when the affordance is shown by registering an
+ * UI associated with the view, when the view is focused on and is part of a dataset.
+ * The application can be notified when the UI is shown by registering an
  * {@link AutofillCallback} through {@link #registerCallback(AutofillCallback)}. When the user
- * selects a dataset from the affordance, all views present in the dataset are autofilled, through
+ * selects a dataset from the UI, all views present in the dataset are autofilled, through
  * calls to {@link View#autofill(AutofillValue)} or {@link View#autofill(SparseArray)}.
  *
  * <p>When the service returns ids of savable views, the Android System keeps track of changes
@@ -108,7 +108,7 @@
  * </ul>
  *
  * <p>Finally, after the autofill context is commited (i.e., not cancelled), the Android System
- * shows a save UI affordance if the value of savable views have changed. If the user selects the
+ * shows an autofill save UI if the value of savable views have changed. If the user selects the
  * option to Save, the current value of the views is then sent to the autofill service.
  *
  * <p>It is safe to call into its methods from any thread.
@@ -150,6 +150,12 @@
      * service authentication will contain the Bundle set by
      * {@link android.service.autofill.FillResponse.Builder#setClientState(Bundle)} on this extra.
      *
+     * <p>On Android {@link android.os.Build.VERSION_CODES#P} and higher, the autofill service
+     * can also add this bundle to the {@link Intent} set as the
+     * {@link android.app.Activity#setResult(int, Intent) result} for an authentication request,
+     * so the bundle can be recovered later on
+     * {@link android.service.autofill.SaveRequest#getClientState()}.
+     *
      * <p>
      * Type: {@link android.os.Bundle}
      */
@@ -311,6 +317,14 @@
     @GuardedBy("mLock")
     @Nullable private ArraySet<AutofillId> mFillableIds;
 
+    /** If set, session is commited when the field is clicked. */
+    @GuardedBy("mLock")
+    @Nullable private AutofillId mSaveTriggerId;
+
+    /** If set, session is commited when the activity is finished; otherwise session is canceled. */
+    @GuardedBy("mLock")
+    private boolean mSaveOnFinish;
+
     /** @hide */
     public interface AutofillClient {
         /**
@@ -834,6 +848,46 @@
         }
     }
 
+
+    /**
+     * Called when a {@link View} is clicked. Currently only used by views that should trigger save.
+     *
+     * @hide
+     */
+    public void notifyViewClicked(View view) {
+        final AutofillId id = view.getAutofillId();
+
+        if (sVerbose) Log.v(TAG, "notifyViewClicked(): id=" + id + ", trigger=" + mSaveTriggerId);
+
+        synchronized (mLock) {
+            if (mSaveTriggerId != null && mSaveTriggerId.equals(id)) {
+                if (sDebug) Log.d(TAG, "triggering commit by click of " + id);
+                commitLocked();
+                mMetricsLogger.action(MetricsEvent.AUTOFILL_SAVE_EXPLICITLY_TRIGGERED,
+                        mContext.getPackageName());
+            }
+        }
+    }
+
+    /**
+     * Called by {@link android.app.Activity} to commit or cancel the session on finish.
+     *
+     * @hide
+     */
+    public void onActivityFinished() {
+        if (!hasAutofillFeature()) {
+            return;
+        }
+        synchronized (mLock) {
+            if (mSaveOnFinish) {
+                commitLocked();
+            } else {
+                if (sDebug) Log.d(TAG, "Cancelling session on finish() as requested by service");
+                cancelLocked();
+            }
+        }
+    }
+
     /**
      * Called to indicate the current autofill context should be commited.
      *
@@ -850,14 +904,17 @@
             return;
         }
         synchronized (mLock) {
-            if (!mEnabled && !isActiveLocked()) {
-                return;
-            }
-
-            finishSessionLocked();
+            commitLocked();
         }
     }
 
+    private void commitLocked() {
+        if (!mEnabled && !isActiveLocked()) {
+            return;
+        }
+        finishSessionLocked();
+    }
+
     /**
      * Called to indicate the current autofill context should be cancelled.
      *
@@ -874,14 +931,17 @@
             return;
         }
         synchronized (mLock) {
-            if (!mEnabled && !isActiveLocked()) {
-                return;
-            }
-
-            cancelSessionLocked();
+            cancelLocked();
         }
     }
 
+    private void cancelLocked() {
+        if (!mEnabled && !isActiveLocked()) {
+            return;
+        }
+        cancelSessionLocked();
+    }
+
     /** @hide */
     public void disableOwnedAutofillServices() {
         disableAutofillServices();
@@ -937,7 +997,12 @@
     }
 
     private AutofillClient getClientLocked() {
-        return mContext.getAutofillClient();
+        final AutofillClient client = mContext.getAutofillClient();
+        if (client == null && sDebug) {
+            Log.d(TAG, "No AutofillClient for " + mContext.getPackageName() + " on context "
+                    + mContext);
+        }
+        return client;
     }
 
     /** @hide */
@@ -959,6 +1024,10 @@
             final Parcelable result = data.getParcelableExtra(EXTRA_AUTHENTICATION_RESULT);
             final Bundle responseData = new Bundle();
             responseData.putParcelable(EXTRA_AUTHENTICATION_RESULT, result);
+            final Bundle newClientState = data.getBundleExtra(EXTRA_CLIENT_STATE);
+            if (newClientState != null) {
+                responseData.putBundle(EXTRA_CLIENT_STATE, newClientState);
+            }
             try {
                 mService.setAuthenticationResult(responseData, mSessionId, authenticationId,
                         mContext.getUserId());
@@ -1038,6 +1107,7 @@
         mState = STATE_UNKNOWN;
         mTrackedViews = null;
         mFillableIds = null;
+        mSaveTriggerId = null;
     }
 
     private void updateSessionLocked(AutofillId id, Rect bounds, AutofillValue value, int action,
@@ -1289,12 +1359,15 @@
     /**
      *  Set the tracked views.
      *
-     * @param trackedIds The views to be tracked
+     * @param trackedIds The views to be tracked.
      * @param saveOnAllViewsInvisible Finish the session once all tracked views are invisible.
+     * @param saveOnFinish Finish the session once the activity is finished.
      * @param fillableIds Views that might anchor FillUI.
+     * @param saveTriggerId View that when clicked triggers commit().
      */
     private void setTrackedViews(int sessionId, @Nullable AutofillId[] trackedIds,
-            boolean saveOnAllViewsInvisible, @Nullable AutofillId[] fillableIds) {
+            boolean saveOnAllViewsInvisible, boolean saveOnFinish,
+            @Nullable AutofillId[] fillableIds, @Nullable AutofillId saveTriggerId) {
         synchronized (mLock) {
             if (mEnabled && mSessionId == sessionId) {
                 if (saveOnAllViewsInvisible) {
@@ -1302,6 +1375,7 @@
                 } else {
                     mTrackedViews = null;
                 }
+                mSaveOnFinish = saveOnFinish;
                 if (fillableIds != null) {
                     if (mFillableIds == null) {
                         mFillableIds = new ArraySet<>(fillableIds.length);
@@ -1314,10 +1388,30 @@
                                 + ", mFillableIds" + mFillableIds);
                     }
                 }
+
+                if (mSaveTriggerId != null && !mSaveTriggerId.equals(saveTriggerId)) {
+                    // Turn off trigger on previous view id.
+                    setNotifyOnClickLocked(mSaveTriggerId, false);
+                }
+
+                if (saveTriggerId != null && !saveTriggerId.equals(mSaveTriggerId)) {
+                    // Turn on trigger on new view id.
+                    mSaveTriggerId = saveTriggerId;
+                    setNotifyOnClickLocked(mSaveTriggerId, true);
+                }
             }
         }
     }
 
+    private void setNotifyOnClickLocked(@NonNull AutofillId id, boolean notify) {
+        final View view = findView(id);
+        if (view == null) {
+            Log.w(TAG, "setNotifyOnClick(): invalid id: " + id);
+            return;
+        }
+        view.setNotifyAutofillManagerOnClick(notify);
+    }
+
     private void setSaveUiState(int sessionId, boolean shown) {
         if (sDebug) Log.d(TAG, "setSaveUiState(" + sessionId + "): " + shown);
         synchronized (mLock) {
@@ -1490,6 +1584,7 @@
         final String pfx = outerPrefix + "  ";
         pw.print(pfx); pw.print("sessionId: "); pw.println(mSessionId);
         pw.print(pfx); pw.print("state: "); pw.println(getStateAsStringLocked());
+        pw.print(pfx); pw.print("context: "); pw.println(mContext);
         pw.print(pfx); pw.print("enabled: "); pw.println(mEnabled);
         pw.print(pfx); pw.print("hasService: "); pw.println(mService != null);
         pw.print(pfx); pw.print("hasCallback: "); pw.println(mCallback != null);
@@ -1504,6 +1599,8 @@
             pw.print(pfx2); pw.print("invisible:"); pw.println(mTrackedViews.mInvisibleTrackedIds);
         }
         pw.print(pfx); pw.print("fillable ids: "); pw.println(mFillableIds);
+        pw.print(pfx); pw.print("save trigger id: "); pw.println(mSaveTriggerId);
+        pw.print(pfx); pw.print("save on finish(): "); pw.println(mSaveOnFinish);
     }
 
     private String getStateAsStringLocked() {
@@ -1752,7 +1849,7 @@
      * Callback for autofill related events.
      *
      * <p>Typically used for applications that display their own "auto-complete" views, so they can
-     * enable / disable such views when the autofill UI affordance is shown / hidden.
+     * enable / disable such views when the autofill UI is shown / hidden.
      */
     public abstract static class AutofillCallback {
 
@@ -1762,26 +1859,26 @@
         public @interface AutofillEventType {}
 
         /**
-         * The autofill input UI affordance associated with the view was shown.
+         * The autofill input UI associated with the view was shown.
          *
-         * <p>If the view provides its own auto-complete UI affordance and its currently shown, it
+         * <p>If the view provides its own auto-complete UI and its currently shown, it
          * should be hidden upon receiving this event.
          */
         public static final int EVENT_INPUT_SHOWN = 1;
 
         /**
-         * The autofill input UI affordance associated with the view was hidden.
+         * The autofill input UI associated with the view was hidden.
          *
-         * <p>If the view provides its own auto-complete UI affordance that was hidden upon a
+         * <p>If the view provides its own auto-complete UI that was hidden upon a
          * {@link #EVENT_INPUT_SHOWN} event, it could be shown again now.
          */
         public static final int EVENT_INPUT_HIDDEN = 2;
 
         /**
-         * The autofill input UI affordance associated with the view isn't shown because
+         * The autofill input UI associated with the view isn't shown because
          * autofill is not available.
          *
-         * <p>If the view provides its own auto-complete UI affordance but was not displaying it
+         * <p>If the view provides its own auto-complete UI but was not displaying it
          * to avoid flickering, it could shown it upon receiving this event.
          */
         public static final int EVENT_INPUT_UNAVAILABLE = 3;
@@ -1883,12 +1980,12 @@
 
         @Override
         public void setTrackedViews(int sessionId, AutofillId[] ids,
-                boolean saveOnAllViewsInvisible, AutofillId[] fillableIds) {
+                boolean saveOnAllViewsInvisible, boolean saveOnFinish, AutofillId[] fillableIds,
+                AutofillId saveTriggerId) {
             final AutofillManager afm = mAfm.get();
             if (afm != null) {
-                afm.post(() ->
-                        afm.setTrackedViews(sessionId, ids, saveOnAllViewsInvisible, fillableIds)
-                );
+                afm.post(() -> afm.setTrackedViews(sessionId, ids, saveOnAllViewsInvisible,
+                        saveOnFinish, fillableIds, saveTriggerId));
             }
         }
 
diff --git a/android/view/inputmethod/InputMethod.java b/android/view/inputmethod/InputMethod.java
index 0922422..ab8886b 100644
--- a/android/view/inputmethod/InputMethod.java
+++ b/android/view/inputmethod/InputMethod.java
@@ -16,6 +16,7 @@
 
 package android.view.inputmethod;
 
+import android.annotation.MainThread;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SdkConstant;
@@ -90,8 +91,9 @@
      * accept the first token given to you.  Any after that may come from the
      * client.
      */
+    @MainThread
     public void attachToken(IBinder token);
-    
+
     /**
      * Bind a new application environment in to the input method, so that it
      * can later start and stop input processing.
@@ -104,6 +106,7 @@
      * @see InputBinding
      * @see #unbindInput()
      */
+    @MainThread
     public void bindInput(InputBinding binding);
 
     /**
@@ -114,6 +117,7 @@
      * Typically this method is called when the application changes to be
      * non-foreground.
      */
+    @MainThread
     public void unbindInput();
 
     /**
@@ -129,6 +133,7 @@
      * 
      * @see EditorInfo
      */
+    @MainThread
     public void startInput(InputConnection inputConnection, EditorInfo info);
 
     /**
@@ -147,6 +152,7 @@
      * 
      * @see EditorInfo
      */
+    @MainThread
     public void restartInput(InputConnection inputConnection, EditorInfo attribute);
 
     /**
@@ -177,6 +183,7 @@
      * @see EditorInfo
      * @hide
      */
+    @MainThread
     default void dispatchStartInputWithToken(@Nullable InputConnection inputConnection,
             @NonNull EditorInfo editorInfo, boolean restarting,
             @NonNull IBinder startInputToken) {
@@ -195,6 +202,7 @@
      * 
      * @param callback Interface that is called with the newly created session.
      */
+    @MainThread
     public void createSession(SessionCallback callback);
     
     /**
@@ -203,6 +211,7 @@
      * @param session The {@link InputMethodSession} previously provided through
      * SessionCallback.sessionCreated() that is to be changed.
      */
+    @MainThread
     public void setSessionEnabled(InputMethodSession session, boolean enabled);
     
     /**
@@ -214,6 +223,7 @@
      * @param session The {@link InputMethodSession} previously provided through
      * SessionCallback.sessionCreated() that is to be revoked.
      */
+    @MainThread
     public void revokeSession(InputMethodSession session);
     
     /**
@@ -244,6 +254,7 @@
      * {@link InputMethodManager#RESULT_SHOWN InputMethodManager.RESULT_SHOWN}, or
      * {@link InputMethodManager#RESULT_HIDDEN InputMethodManager.RESULT_HIDDEN}.
      */
+    @MainThread
     public void showSoftInput(int flags, ResultReceiver resultReceiver);
     
     /**
@@ -258,11 +269,13 @@
      * {@link InputMethodManager#RESULT_SHOWN InputMethodManager.RESULT_SHOWN}, or
      * {@link InputMethodManager#RESULT_HIDDEN InputMethodManager.RESULT_HIDDEN}.
      */
+    @MainThread
     public void hideSoftInput(int flags, ResultReceiver resultReceiver);
 
     /**
      * Notify that the input method subtype is being changed in the same input method.
      * @param subtype New subtype of the notified input method
      */
+    @MainThread
     public void changeInputMethodSubtype(InputMethodSubtype subtype);
 }
diff --git a/android/view/textclassifier/Log.java b/android/view/textclassifier/Log.java
new file mode 100644
index 0000000..83ca15d
--- /dev/null
+++ b/android/view/textclassifier/Log.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2017 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.view.textclassifier;
+
+import android.util.Slog;
+
+/**
+ * Logging for android.view.textclassifier package.
+ */
+final class Log {
+
+    /**
+     * true: Enables full logging.
+     * false: Limits logging to debug level.
+     */
+    private static final boolean ENABLE_FULL_LOGGING = false;
+
+    private Log() {}
+
+    public static void d(String tag, String msg) {
+        Slog.d(tag, msg);
+    }
+
+    public static void e(String tag, String msg, Throwable tr) {
+        if (ENABLE_FULL_LOGGING) {
+            Slog.e(tag, msg, tr);
+        } else {
+            final String trString = (tr != null) ? tr.getClass().getSimpleName() : "??";
+            Slog.d(tag, String.format("%s (%s)", msg, trString));
+        }
+    }
+}
diff --git a/android/view/textclassifier/SmartSelection.java b/android/view/textclassifier/SmartSelection.java
index f0e83d1..2c93a19 100644
--- a/android/view/textclassifier/SmartSelection.java
+++ b/android/view/textclassifier/SmartSelection.java
@@ -16,6 +16,8 @@
 
 package android.view.textclassifier;
 
+import android.content.res.AssetFileDescriptor;
+
 /**
  *  Java wrapper for SmartSelection native library interface.
  *  This library is used for detecting entities in text.
@@ -42,6 +44,26 @@
     }
 
     /**
+     * Creates a new instance of SmartSelect predictor, using the provided model image, given as a
+     * file path.
+     */
+    SmartSelection(String path) {
+        mCtx = nativeNewFromPath(path);
+    }
+
+    /**
+     * Creates a new instance of SmartSelect predictor, using the provided model image, given as an
+     * AssetFileDescriptor.
+     */
+    SmartSelection(AssetFileDescriptor afd) {
+        mCtx = nativeNewFromAssetFileDescriptor(afd, afd.getStartOffset(), afd.getLength());
+        if (mCtx == 0L) {
+            throw new IllegalArgumentException(
+                "Couldn't initialize TC from given AssetFileDescriptor");
+        }
+    }
+
+    /**
      * Given a string context and current selection, computes the SmartSelection suggestion.
      *
      * The begin and end are character indices into the context UTF8 string. selectionBegin is the
@@ -69,6 +91,15 @@
     }
 
     /**
+     * Annotates given input text. Every word of the input is a part of some annotation.
+     * The annotations are sorted by their position in the context string.
+     * The annotations do not overlap.
+     */
+    public AnnotatedSpan[] annotate(String text) {
+        return nativeAnnotate(mCtx, text);
+    }
+
+    /**
      * Frees up the allocated memory.
      */
     public void close() {
@@ -91,12 +122,19 @@
 
     private static native long nativeNew(int fd);
 
+    private static native long nativeNewFromPath(String path);
+
+    private static native long nativeNewFromAssetFileDescriptor(AssetFileDescriptor afd,
+                                                                long offset, long size);
+
     private static native int[] nativeSuggest(
             long context, String text, int selectionBegin, int selectionEnd);
 
     private static native ClassificationResult[] nativeClassifyText(
             long context, String text, int selectionBegin, int selectionEnd, int hintFlags);
 
+    private static native AnnotatedSpan[] nativeAnnotate(long context, String text);
+
     private static native void nativeClose(long context);
 
     private static native String nativeGetLanguage(int fd);
@@ -114,4 +152,29 @@
             mScore = score;
         }
     }
+
+    /** Represents a result of Annotate call. */
+    public static final class AnnotatedSpan {
+        final int mStartIndex;
+        final int mEndIndex;
+        final ClassificationResult[] mClassification;
+
+        AnnotatedSpan(int startIndex, int endIndex, ClassificationResult[] classification) {
+            mStartIndex = startIndex;
+            mEndIndex = endIndex;
+            mClassification = classification;
+        }
+
+        public int getStartIndex() {
+            return mStartIndex;
+        }
+
+        public int getEndIndex() {
+            return mEndIndex;
+        }
+
+        public ClassificationResult[] getClassification() {
+            return mClassification;
+        }
+    }
 }
diff --git a/android/view/textclassifier/TextClassifier.java b/android/view/textclassifier/TextClassifier.java
index bb1e693..c3601d9 100644
--- a/android/view/textclassifier/TextClassifier.java
+++ b/android/view/textclassifier/TextClassifier.java
@@ -152,4 +152,12 @@
      */
     @WorkerThread
     default void logEvent(String source, String event) {}
+
+    /**
+     * Returns this TextClassifier's settings.
+     * @hide
+     */
+    default TextClassifierConstants getSettings() {
+        return TextClassifierConstants.DEFAULT;
+    }
 }
diff --git a/android/view/textclassifier/TextClassifierConstants.java b/android/view/textclassifier/TextClassifierConstants.java
new file mode 100644
index 0000000..51e6168
--- /dev/null
+++ b/android/view/textclassifier/TextClassifierConstants.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2017 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.view.textclassifier;
+
+import android.annotation.Nullable;
+import android.util.KeyValueListParser;
+import android.util.Slog;
+
+/**
+ * TextClassifier specific settings.
+ * This is encoded as a key=value list, separated by commas. Ex:
+ *
+ * <pre>
+ * smart_selection_dark_launch              (boolean)
+ * smart_selection_enabled_for_edit_text    (boolean)
+ * </pre>
+ *
+ * <p>
+ * Type: string
+ * see also android.provider.Settings.Global.TEXT_CLASSIFIER_CONSTANTS
+ *
+ * Example of setting the values for testing.
+ * adb shell settings put global text_classifier_constants smart_selection_dark_launch=true,smart_selection_enabled_for_edit_text=true
+ * @hide
+ */
+public final class TextClassifierConstants {
+
+    private static final String LOG_TAG = "TextClassifierConstants";
+
+    private static final String SMART_SELECTION_DARK_LAUNCH =
+            "smart_selection_dark_launch";
+    private static final String SMART_SELECTION_ENABLED_FOR_EDIT_TEXT =
+            "smart_selection_enabled_for_edit_text";
+
+    private static final boolean SMART_SELECTION_DARK_LAUNCH_DEFAULT = false;
+    private static final boolean SMART_SELECTION_ENABLED_FOR_EDIT_TEXT_DEFAULT = true;
+
+    /** Default settings. */
+    static final TextClassifierConstants DEFAULT = new TextClassifierConstants();
+
+    private final boolean mDarkLaunch;
+    private final boolean mSuggestSelectionEnabledForEditableText;
+
+    private TextClassifierConstants() {
+        mDarkLaunch = SMART_SELECTION_DARK_LAUNCH_DEFAULT;
+        mSuggestSelectionEnabledForEditableText = SMART_SELECTION_ENABLED_FOR_EDIT_TEXT_DEFAULT;
+    }
+
+    private TextClassifierConstants(@Nullable String settings) {
+        final KeyValueListParser parser = new KeyValueListParser(',');
+        try {
+            parser.setString(settings);
+        } catch (IllegalArgumentException e) {
+            // Failed to parse the settings string, log this and move on with defaults.
+            Slog.e(LOG_TAG, "Bad TextClassifier settings: " + settings);
+        }
+        mDarkLaunch = parser.getBoolean(
+                SMART_SELECTION_DARK_LAUNCH,
+                SMART_SELECTION_DARK_LAUNCH_DEFAULT);
+        mSuggestSelectionEnabledForEditableText = parser.getBoolean(
+                SMART_SELECTION_ENABLED_FOR_EDIT_TEXT,
+                SMART_SELECTION_ENABLED_FOR_EDIT_TEXT_DEFAULT);
+    }
+
+    static TextClassifierConstants loadFromString(String settings) {
+        return new TextClassifierConstants(settings);
+    }
+
+    public boolean isDarkLaunch() {
+        return mDarkLaunch;
+    }
+
+    public boolean isSuggestSelectionEnabledForEditableText() {
+        return mSuggestSelectionEnabledForEditableText;
+    }
+}
diff --git a/android/view/textclassifier/TextClassifierImpl.java b/android/view/textclassifier/TextClassifierImpl.java
index 2aa81a2..1c07be4 100644
--- a/android/view/textclassifier/TextClassifierImpl.java
+++ b/android/view/textclassifier/TextClassifierImpl.java
@@ -24,18 +24,17 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.graphics.drawable.Drawable;
-import android.icu.text.BreakIterator;
 import android.net.Uri;
 import android.os.LocaleList;
 import android.os.ParcelFileDescriptor;
 import android.provider.Browser;
 import android.provider.ContactsContract;
+import android.provider.Settings;
 import android.text.Spannable;
 import android.text.TextUtils;
 import android.text.method.WordIterator;
 import android.text.style.ClickableSpan;
 import android.text.util.Linkify;
-import android.util.Log;
 import android.util.Patterns;
 import android.view.View;
 import android.widget.TextViewMetrics;
@@ -47,6 +46,7 @@
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.IOException;
+import java.text.BreakIterator;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
@@ -91,6 +91,8 @@
     @GuardedBy("mSmartSelectionLock") // Do not access outside this lock.
     private SmartSelection mSmartSelection;
 
+    private TextClassifierConstants mSettings;
+
     TextClassifierImpl(Context context) {
         mContext = Preconditions.checkNotNull(context);
     }
@@ -160,7 +162,7 @@
             }
         } catch (Throwable t) {
             // Avoid throwing from this method. Log the error.
-            Log.e(LOG_TAG, "Error getting assist info.", t);
+            Log.e(LOG_TAG, "Error getting text classification info.", t);
         }
         // Getting here means something went wrong, return a NO_OP result.
         return TextClassifier.NO_OP.classifyText(
@@ -189,6 +191,15 @@
         }
     }
 
+    @Override
+    public TextClassifierConstants getSettings() {
+        if (mSettings == null) {
+            mSettings = TextClassifierConstants.loadFromString(Settings.Global.getString(
+                    mContext.getContentResolver(), Settings.Global.TEXT_CLASSIFIER_CONSTANTS));
+        }
+        return mSettings;
+    }
+
     private SmartSelection getSmartSelection(LocaleList localeList) throws FileNotFoundException {
         synchronized (mSmartSelectionLock) {
             localeList = localeList == null ? LocaleList.getEmptyLocaleList() : localeList;
diff --git a/android/view/textservice/TextServicesManager.java b/android/view/textservice/TextServicesManager.java
index f368c74..8e1f218 100644
--- a/android/view/textservice/TextServicesManager.java
+++ b/android/view/textservice/TextServicesManager.java
@@ -1,213 +1,58 @@
 /*
- * Copyright (C) 2011 The Android Open Source Project
+ * Copyright (C) 2016 The Android Open Source Project
  *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
+ * 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
+ *      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.
+ * 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.view.textservice;
 
-import android.annotation.SystemService;
-import android.content.Context;
 import android.os.Bundle;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.ServiceManager.ServiceNotFoundException;
-import android.util.Log;
 import android.view.textservice.SpellCheckerSession.SpellCheckerSessionListener;
 
-import com.android.internal.textservice.ITextServicesManager;
-
 import java.util.Locale;
 
 /**
- * System API to the overall text services, which arbitrates interaction between applications
- * and text services.
- *
- * The user can change the current text services in Settings. And also applications can specify
- * the target text services.
- *
- * <h3>Architecture Overview</h3>
- *
- * <p>There are three primary parties involved in the text services
- * framework (TSF) architecture:</p>
- *
- * <ul>
- * <li> The <strong>text services manager</strong> as expressed by this class
- * is the central point of the system that manages interaction between all
- * other parts.  It is expressed as the client-side API here which exists
- * in each application context and communicates with a global system service
- * that manages the interaction across all processes.
- * <li> A <strong>text service</strong> implements a particular
- * interaction model allowing the client application to retrieve information of text.
- * The system binds to the current text service that is in use, causing it to be created and run.
- * <li> Multiple <strong>client applications</strong> arbitrate with the text service
- * manager for connections to text services.
- * </ul>
- *
- * <h3>Text services sessions</h3>
- * <ul>
- * <li>The <strong>spell checker session</strong> is one of the text services.
- * {@link android.view.textservice.SpellCheckerSession}</li>
- * </ul>
- *
+ * A stub class of TextServicesManager for Layout-Lib.
  */
-@SystemService(Context.TEXT_SERVICES_MANAGER_SERVICE)
 public final class TextServicesManager {
-    private static final String TAG = TextServicesManager.class.getSimpleName();
-    private static final boolean DBG = false;
-
-    private static TextServicesManager sInstance;
-
-    private final ITextServicesManager mService;
-
-    private TextServicesManager() throws ServiceNotFoundException {
-        mService = ITextServicesManager.Stub.asInterface(
-                ServiceManager.getServiceOrThrow(Context.TEXT_SERVICES_MANAGER_SERVICE));
-    }
+    private static final TextServicesManager sInstance = new TextServicesManager();
+    private static final SpellCheckerInfo[] EMPTY_SPELL_CHECKER_INFO = new SpellCheckerInfo[0];
 
     /**
      * Retrieve the global TextServicesManager instance, creating it if it doesn't already exist.
      * @hide
      */
     public static TextServicesManager getInstance() {
-        synchronized (TextServicesManager.class) {
-            if (sInstance == null) {
-                try {
-                    sInstance = new TextServicesManager();
-                } catch (ServiceNotFoundException e) {
-                    throw new IllegalStateException(e);
-                }
-            }
-            return sInstance;
-        }
+        return sInstance;
     }
 
-    /**
-     * Returns the language component of a given locale string.
-     */
-    private static String parseLanguageFromLocaleString(String locale) {
-        final int idx = locale.indexOf('_');
-        if (idx < 0) {
-            return locale;
-        } else {
-            return locale.substring(0, idx);
-        }
-    }
-
-    /**
-     * Get a spell checker session for the specified spell checker
-     * @param locale the locale for the spell checker. If {@code locale} is null and
-     * referToSpellCheckerLanguageSettings is true, the locale specified in Settings will be
-     * returned. If {@code locale} is not null and referToSpellCheckerLanguageSettings is true,
-     * the locale specified in Settings will be returned only when it is same as {@code locale}.
-     * Exceptionally, when referToSpellCheckerLanguageSettings is true and {@code locale} is
-     * only language (e.g. "en"), the specified locale in Settings (e.g. "en_US") will be
-     * selected.
-     * @param listener a spell checker session lister for getting results from a spell checker.
-     * @param referToSpellCheckerLanguageSettings if true, the session for one of enabled
-     * languages in settings will be returned.
-     * @return the spell checker session of the spell checker
-     */
     public SpellCheckerSession newSpellCheckerSession(Bundle bundle, Locale locale,
             SpellCheckerSessionListener listener, boolean referToSpellCheckerLanguageSettings) {
-        if (listener == null) {
-            throw new NullPointerException();
-        }
-        if (!referToSpellCheckerLanguageSettings && locale == null) {
-            throw new IllegalArgumentException("Locale should not be null if you don't refer"
-                    + " settings.");
-        }
-
-        if (referToSpellCheckerLanguageSettings && !isSpellCheckerEnabled()) {
-            return null;
-        }
-
-        final SpellCheckerInfo sci;
-        try {
-            sci = mService.getCurrentSpellChecker(null);
-        } catch (RemoteException e) {
-            return null;
-        }
-        if (sci == null) {
-            return null;
-        }
-        SpellCheckerSubtype subtypeInUse = null;
-        if (referToSpellCheckerLanguageSettings) {
-            subtypeInUse = getCurrentSpellCheckerSubtype(true);
-            if (subtypeInUse == null) {
-                return null;
-            }
-            if (locale != null) {
-                final String subtypeLocale = subtypeInUse.getLocale();
-                final String subtypeLanguage = parseLanguageFromLocaleString(subtypeLocale);
-                if (subtypeLanguage.length() < 2 || !locale.getLanguage().equals(subtypeLanguage)) {
-                    return null;
-                }
-            }
-        } else {
-            final String localeStr = locale.toString();
-            for (int i = 0; i < sci.getSubtypeCount(); ++i) {
-                final SpellCheckerSubtype subtype = sci.getSubtypeAt(i);
-                final String tempSubtypeLocale = subtype.getLocale();
-                final String tempSubtypeLanguage = parseLanguageFromLocaleString(tempSubtypeLocale);
-                if (tempSubtypeLocale.equals(localeStr)) {
-                    subtypeInUse = subtype;
-                    break;
-                } else if (tempSubtypeLanguage.length() >= 2 &&
-                        locale.getLanguage().equals(tempSubtypeLanguage)) {
-                    subtypeInUse = subtype;
-                }
-            }
-        }
-        if (subtypeInUse == null) {
-            return null;
-        }
-        final SpellCheckerSession session = new SpellCheckerSession(sci, mService, listener);
-        try {
-            mService.getSpellCheckerService(sci.getId(), subtypeInUse.getLocale(),
-                    session.getTextServicesSessionListener(),
-                    session.getSpellCheckerSessionListener(), bundle);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-        return session;
+        return null;
     }
 
     /**
      * @hide
      */
     public SpellCheckerInfo[] getEnabledSpellCheckers() {
-        try {
-            final SpellCheckerInfo[] retval = mService.getEnabledSpellCheckers();
-            if (DBG) {
-                Log.d(TAG, "getEnabledSpellCheckers: " + (retval != null ? retval.length : "null"));
-            }
-            return retval;
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        return EMPTY_SPELL_CHECKER_INFO;
     }
 
     /**
      * @hide
      */
     public SpellCheckerInfo getCurrentSpellChecker() {
-        try {
-            // Passing null as a locale for ICS
-            return mService.getCurrentSpellChecker(null);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        return null;
     }
 
     /**
@@ -215,22 +60,13 @@
      */
     public SpellCheckerSubtype getCurrentSpellCheckerSubtype(
             boolean allowImplicitlySelectedSubtype) {
-        try {
-            // Passing null as a locale until we support multiple enabled spell checker subtypes.
-            return mService.getCurrentSpellCheckerSubtype(null, allowImplicitlySelectedSubtype);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        return null;
     }
 
     /**
      * @hide
      */
     public boolean isSpellCheckerEnabled() {
-        try {
-            return mService.isSpellCheckerEnabled();
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        return false;
     }
 }
diff --git a/android/webkit/WebView.java b/android/webkit/WebView.java
index dfc81b2..202f204 100644
--- a/android/webkit/WebView.java
+++ b/android/webkit/WebView.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2006 The Android Open Source Project
+ * Copyright (C) 2008 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.
@@ -16,3001 +16,223 @@
 
 package android.webkit;
 
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.SystemApi;
-import android.annotation.Widget;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageInfo;
-import android.content.res.Configuration;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.Picture;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.net.http.SslCertificate;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.os.RemoteException;
-import android.os.StrictMode;
-import android.print.PrintDocumentAdapter;
-import android.security.KeyChain;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.util.SparseArray;
-import android.view.DragEvent;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewDebug;
-import android.view.ViewGroup;
-import android.view.ViewHierarchyEncoder;
-import android.view.ViewStructure;
-import android.view.ViewTreeObserver;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityNodeInfo;
-import android.view.accessibility.AccessibilityNodeProvider;
-import android.view.autofill.AutofillValue;
-import android.view.inputmethod.EditorInfo;
-import android.view.inputmethod.InputConnection;
-import android.view.textclassifier.TextClassifier;
-import android.widget.AbsoluteLayout;
+import com.android.layoutlib.bridge.MockView;
 
-import java.io.BufferedWriter;
-import java.io.File;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.List;
-import java.util.Map;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Picture;
+import android.os.Bundle;
+import android.os.Message;
+import android.util.AttributeSet;
+import android.view.View;
 
 /**
- * <p>A View that displays web pages. This class is the basis upon which you
- * can roll your own web browser or simply display some online content within your Activity.
- * It uses the WebKit rendering engine to display
- * web pages and includes methods to navigate forward and backward
- * through a history, zoom in and out, perform text searches and more.
- *
- * <p>Note that, in order for your Activity to access the Internet and load web pages
- * in a WebView, you must add the {@code INTERNET} permissions to your
- * Android Manifest file:
- *
- * <pre>
- * {@code <uses-permission android:name="android.permission.INTERNET" />}
- * </pre>
- *
- * <p>This must be a child of the <a
- * href="{@docRoot}guide/topics/manifest/manifest-element.html">{@code <manifest>}</a>
- * element.
- *
- * <p>For more information, read
- * <a href="{@docRoot}guide/webapps/webview.html">Building Web Apps in WebView</a>.
- *
- * <h3>Basic usage</h3>
- *
- * <p>By default, a WebView provides no browser-like widgets, does not
- * enable JavaScript and web page errors are ignored. If your goal is only
- * to display some HTML as a part of your UI, this is probably fine;
- * the user won't need to interact with the web page beyond reading
- * it, and the web page won't need to interact with the user. If you
- * actually want a full-blown web browser, then you probably want to
- * invoke the Browser application with a URL Intent rather than show it
- * with a WebView. For example:
- * <pre>
- * Uri uri = Uri.parse("https://www.example.com");
- * Intent intent = new Intent(Intent.ACTION_VIEW, uri);
- * startActivity(intent);
- * </pre>
- * <p>See {@link android.content.Intent} for more information.
- *
- * <p>To provide a WebView in your own Activity, include a {@code <WebView>} in your layout,
- * or set the entire Activity window as a WebView during {@link
- * android.app.Activity#onCreate(Bundle) onCreate()}:
- *
- * <pre class="prettyprint">
- * WebView webview = new WebView(this);
- * setContentView(webview);
- * </pre>
- *
- * <p>Then load the desired web page:
- *
- * <pre>
- * // Simplest usage: note that an exception will NOT be thrown
- * // if there is an error loading this page (see below).
- * webview.loadUrl("https://example.com/");
- *
- * // OR, you can also load from an HTML string:
- * String summary = "&lt;html>&lt;body>You scored &lt;b>192&lt;/b> points.&lt;/body>&lt;/html>";
- * webview.loadData(summary, "text/html", null);
- * // ... although note that there are restrictions on what this HTML can do.
- * // See the JavaDocs for {@link #loadData(String,String,String) loadData()} and {@link
- * #loadDataWithBaseURL(String,String,String,String,String) loadDataWithBaseURL()} for more info.
- * </pre>
- *
- * <p>A WebView has several customization points where you can add your
- * own behavior. These are:
- *
- * <ul>
- *   <li>Creating and setting a {@link android.webkit.WebChromeClient} subclass.
- *       This class is called when something that might impact a
- *       browser UI happens, for instance, progress updates and
- *       JavaScript alerts are sent here (see <a
- * href="{@docRoot}guide/developing/debug-tasks.html#DebuggingWebPages">Debugging Tasks</a>).
- *   </li>
- *   <li>Creating and setting a {@link android.webkit.WebViewClient} subclass.
- *       It will be called when things happen that impact the
- *       rendering of the content, eg, errors or form submissions. You
- *       can also intercept URL loading here (via {@link
- * android.webkit.WebViewClient#shouldOverrideUrlLoading(WebView,String)
- * shouldOverrideUrlLoading()}).</li>
- *   <li>Modifying the {@link android.webkit.WebSettings}, such as
- * enabling JavaScript with {@link android.webkit.WebSettings#setJavaScriptEnabled(boolean)
- * setJavaScriptEnabled()}. </li>
- *   <li>Injecting Java objects into the WebView using the
- *       {@link android.webkit.WebView#addJavascriptInterface} method. This
- *       method allows you to inject Java objects into a page's JavaScript
- *       context, so that they can be accessed by JavaScript in the page.</li>
- * </ul>
- *
- * <p>Here's a more complicated example, showing error handling,
- *    settings, and progress notification:
- *
- * <pre class="prettyprint">
- * // Let's display the progress in the activity title bar, like the
- * // browser app does.
- * getWindow().requestFeature(Window.FEATURE_PROGRESS);
- *
- * webview.getSettings().setJavaScriptEnabled(true);
- *
- * final Activity activity = this;
- * webview.setWebChromeClient(new WebChromeClient() {
- *   public void onProgressChanged(WebView view, int progress) {
- *     // Activities and WebViews measure progress with different scales.
- *     // The progress meter will automatically disappear when we reach 100%
- *     activity.setProgress(progress * 1000);
- *   }
- * });
- * webview.setWebViewClient(new WebViewClient() {
- *   public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
- *     Toast.makeText(activity, "Oh no! " + description, Toast.LENGTH_SHORT).show();
- *   }
- * });
- *
- * webview.loadUrl("https://developer.android.com/");
- * </pre>
- *
- * <h3>Zoom</h3>
- *
- * <p>To enable the built-in zoom, set
- * {@link #getSettings() WebSettings}.{@link WebSettings#setBuiltInZoomControls(boolean)}
- * (introduced in API level {@link android.os.Build.VERSION_CODES#CUPCAKE}).
- *
- * <p>NOTE: Using zoom if either the height or width is set to
- * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} may lead to undefined behavior
- * and should be avoided.
- *
- * <h3>Cookie and window management</h3>
- *
- * <p>For obvious security reasons, your application has its own
- * cache, cookie store etc.&mdash;it does not share the Browser
- * application's data.
- *
- * <p>By default, requests by the HTML to open new windows are
- * ignored. This is {@code true} whether they be opened by JavaScript or by
- * the target attribute on a link. You can customize your
- * {@link WebChromeClient} to provide your own behavior for opening multiple windows,
- * and render them in whatever manner you want.
- *
- * <p>The standard behavior for an Activity is to be destroyed and
- * recreated when the device orientation or any other configuration changes. This will cause
- * the WebView to reload the current page. If you don't want that, you
- * can set your Activity to handle the {@code orientation} and {@code keyboardHidden}
- * changes, and then just leave the WebView alone. It'll automatically
- * re-orient itself as appropriate. Read <a
- * href="{@docRoot}guide/topics/resources/runtime-changes.html">Handling Runtime Changes</a> for
- * more information about how to handle configuration changes during runtime.
- *
- *
- * <h3>Building web pages to support different screen densities</h3>
- *
- * <p>The screen density of a device is based on the screen resolution. A screen with low density
- * has fewer available pixels per inch, where a screen with high density
- * has more &mdash; sometimes significantly more &mdash; pixels per inch. The density of a
- * screen is important because, other things being equal, a UI element (such as a button) whose
- * height and width are defined in terms of screen pixels will appear larger on the lower density
- * screen and smaller on the higher density screen.
- * For simplicity, Android collapses all actual screen densities into three generalized densities:
- * high, medium, and low.
- * <p>By default, WebView scales a web page so that it is drawn at a size that matches the default
- * appearance on a medium density screen. So, it applies 1.5x scaling on a high density screen
- * (because its pixels are smaller) and 0.75x scaling on a low density screen (because its pixels
- * are bigger).
- * Starting with API level {@link android.os.Build.VERSION_CODES#ECLAIR}, WebView supports DOM, CSS,
- * and meta tag features to help you (as a web developer) target screens with different screen
- * densities.
- * <p>Here's a summary of the features you can use to handle different screen densities:
- * <ul>
- * <li>The {@code window.devicePixelRatio} DOM property. The value of this property specifies the
- * default scaling factor used for the current device. For example, if the value of {@code
- * window.devicePixelRatio} is "1.0", then the device is considered a medium density (mdpi) device
- * and default scaling is not applied to the web page; if the value is "1.5", then the device is
- * considered a high density device (hdpi) and the page content is scaled 1.5x; if the
- * value is "0.75", then the device is considered a low density device (ldpi) and the content is
- * scaled 0.75x.</li>
- * <li>The {@code -webkit-device-pixel-ratio} CSS media query. Use this to specify the screen
- * densities for which this style sheet is to be used. The corresponding value should be either
- * "0.75", "1", or "1.5", to indicate that the styles are for devices with low density, medium
- * density, or high density screens, respectively. For example:
- * <pre>
- * &lt;link rel="stylesheet" media="screen and (-webkit-device-pixel-ratio:1.5)" href="hdpi.css" /&gt;</pre>
- * <p>The {@code hdpi.css} stylesheet is only used for devices with a screen pixel ration of 1.5,
- * which is the high density pixel ratio.
- * </li>
- * </ul>
- *
- * <h3>HTML5 Video support</h3>
- *
- * <p>In order to support inline HTML5 video in your application you need to have hardware
- * acceleration turned on.
- *
- * <h3>Full screen support</h3>
- *
- * <p>In order to support full screen &mdash; for video or other HTML content &mdash; you need to set a
- * {@link android.webkit.WebChromeClient} and implement both
- * {@link WebChromeClient#onShowCustomView(View, WebChromeClient.CustomViewCallback)}
- * and {@link WebChromeClient#onHideCustomView()}. If the implementation of either of these two methods is
- * missing then the web contents will not be allowed to enter full screen. Optionally you can implement
- * {@link WebChromeClient#getVideoLoadingProgressView()} to customize the View displayed whilst a video
- * is loading.
- *
- * <h3>HTML5 Geolocation API support</h3>
- *
- * <p>For applications targeting Android N and later releases
- * (API level > {@link android.os.Build.VERSION_CODES#M}) the geolocation api is only supported on
- * secure origins such as https. For such applications requests to geolocation api on non-secure
- * origins are automatically denied without invoking the corresponding
- * {@link WebChromeClient#onGeolocationPermissionsShowPrompt(String, GeolocationPermissions.Callback)}
- * method.
- *
- * <h3>Layout size</h3>
- * <p>
- * It is recommended to set the WebView layout height to a fixed value or to
- * {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT} instead of using
- * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}.
- * When using {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT}
- * for the height none of the WebView's parents should use a
- * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} layout height since that could result in
- * incorrect sizing of the views.
- *
- * <p>Setting the WebView's height to {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}
- * enables the following behaviors:
- * <ul>
- * <li>The HTML body layout height is set to a fixed value. This means that elements with a height
- * relative to the HTML body may not be sized correctly. </li>
- * <li>For applications targeting {@link android.os.Build.VERSION_CODES#KITKAT} and earlier SDKs the
- * HTML viewport meta tag will be ignored in order to preserve backwards compatibility. </li>
- * </ul>
- *
- * <p>
- * Using a layout width of {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} is not
- * supported. If such a width is used the WebView will attempt to use the width of the parent
- * instead.
- *
- * <h3>Metrics</h3>
- *
- * <p>
- * WebView may upload anonymous diagnostic data to Google when the user has consented. This data
- * helps Google improve WebView. Data is collected on a per-app basis for each app which has
- * instantiated a WebView. An individual app can opt out of this feature by putting the following
- * tag in its manifest:
- * <pre>
- * &lt;meta-data android:name="android.webkit.WebView.MetricsOptOut"
- *            android:value="true" /&gt;
- * </pre>
- * <p>
- * Data will only be uploaded for a given app if the user has consented AND the app has not opted
- * out.
- *
- * <h3>Safe Browsing</h3>
- *
- * <p>
- * If Safe Browsing is enabled, WebView will block malicious URLs and present a warning UI to the
- * user to allow them to navigate back safely or proceed to the malicious page.
- * <p>
- * The recommended way for apps to enable the feature is putting the following tag in the manifest:
- * <p>
- * <pre>
- * &lt;meta-data android:name="android.webkit.WebView.EnableSafeBrowsing"
- *            android:value="true" /&gt;
- * </pre>
+ * Mock version of the WebView.
+ * Only non override public methods from the real WebView have been added in there.
+ * Methods that take an unknown class as parameter or as return object, have been removed for now.
+ * 
+ * TODO: generate automatically.
  *
  */
-// Implementation notes.
-// The WebView is a thin API class that delegates its public API to a backend WebViewProvider
-// class instance. WebView extends {@link AbsoluteLayout} for backward compatibility reasons.
-// Methods are delegated to the provider implementation: all public API methods introduced in this
-// file are fully delegated, whereas public and protected methods from the View base classes are
-// only delegated where a specific need exists for them to do so.
-@Widget
-public class WebView extends AbsoluteLayout
-        implements ViewTreeObserver.OnGlobalFocusChangeListener,
-        ViewGroup.OnHierarchyChangeListener, ViewDebug.HierarchyHandler {
-
-    private static final String LOGTAG = "WebView";
-
-    // Throwing an exception for incorrect thread usage if the
-    // build target is JB MR2 or newer. Defaults to false, and is
-    // set in the WebView constructor.
-    private static volatile boolean sEnforceThreadChecking = false;
+public class WebView extends MockView {
 
     /**
-     *  Transportation object for returning WebView across thread boundaries.
-     */
-    public class WebViewTransport {
-        private WebView mWebview;
-
-        /**
-         * Sets the WebView to the transportation object.
-         *
-         * @param webview the WebView to transport
-         */
-        public synchronized void setWebView(WebView webview) {
-            mWebview = webview;
-        }
-
-        /**
-         * Gets the WebView object.
-         *
-         * @return the transported WebView object
-         */
-        public synchronized WebView getWebView() {
-            return mWebview;
-        }
-    }
-
-    /**
-     * URI scheme for telephone number.
-     */
-    public static final String SCHEME_TEL = "tel:";
-    /**
-     * URI scheme for email address.
-     */
-    public static final String SCHEME_MAILTO = "mailto:";
-    /**
-     * URI scheme for map address.
-     */
-    public static final String SCHEME_GEO = "geo:0,0?q=";
-
-    /**
-     * Interface to listen for find results.
-     */
-    public interface FindListener {
-        /**
-         * Notifies the listener about progress made by a find operation.
-         *
-         * @param activeMatchOrdinal the zero-based ordinal of the currently selected match
-         * @param numberOfMatches how many matches have been found
-         * @param isDoneCounting whether the find operation has actually completed. The listener
-         *                       may be notified multiple times while the
-         *                       operation is underway, and the numberOfMatches
-         *                       value should not be considered final unless
-         *                       isDoneCounting is {@code true}.
-         */
-        public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches,
-            boolean isDoneCounting);
-    }
-
-    /**
-     * Callback interface supplied to {@link #postVisualStateCallback} for receiving
-     * notifications about the visual state.
-     */
-    public static abstract class VisualStateCallback {
-        /**
-         * Invoked when the visual state is ready to be drawn in the next {@link #onDraw}.
-         *
-         * @param requestId The identifier passed to {@link #postVisualStateCallback} when this
-         *                  callback was posted.
-         */
-        public abstract void onComplete(long requestId);
-    }
-
-    /**
-     * Interface to listen for new pictures as they change.
-     *
-     * @deprecated This interface is now obsolete.
-     */
-    @Deprecated
-    public interface PictureListener {
-        /**
-         * Used to provide notification that the WebView's picture has changed.
-         * See {@link WebView#capturePicture} for details of the picture.
-         *
-         * @param view the WebView that owns the picture
-         * @param picture the new picture. Applications targeting
-         *     {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2} or above
-         *     will always receive a {@code null} Picture.
-         * @deprecated Deprecated due to internal changes.
-         */
-        @Deprecated
-        void onNewPicture(WebView view, @Nullable Picture picture);
-    }
-
-    public static class HitTestResult {
-        /**
-         * Default HitTestResult, where the target is unknown.
-         */
-        public static final int UNKNOWN_TYPE = 0;
-        /**
-         * @deprecated This type is no longer used.
-         */
-        @Deprecated
-        public static final int ANCHOR_TYPE = 1;
-        /**
-         * HitTestResult for hitting a phone number.
-         */
-        public static final int PHONE_TYPE = 2;
-        /**
-         * HitTestResult for hitting a map address.
-         */
-        public static final int GEO_TYPE = 3;
-        /**
-         * HitTestResult for hitting an email address.
-         */
-        public static final int EMAIL_TYPE = 4;
-        /**
-         * HitTestResult for hitting an HTML::img tag.
-         */
-        public static final int IMAGE_TYPE = 5;
-        /**
-         * @deprecated This type is no longer used.
-         */
-        @Deprecated
-        public static final int IMAGE_ANCHOR_TYPE = 6;
-        /**
-         * HitTestResult for hitting a HTML::a tag with src=http.
-         */
-        public static final int SRC_ANCHOR_TYPE = 7;
-        /**
-         * HitTestResult for hitting a HTML::a tag with src=http + HTML::img.
-         */
-        public static final int SRC_IMAGE_ANCHOR_TYPE = 8;
-        /**
-         * HitTestResult for hitting an edit text area.
-         */
-        public static final int EDIT_TEXT_TYPE = 9;
-
-        private int mType;
-        private String mExtra;
-
-        /**
-         * @hide Only for use by WebViewProvider implementations
-         */
-        @SystemApi
-        public HitTestResult() {
-            mType = UNKNOWN_TYPE;
-        }
-
-        /**
-         * @hide Only for use by WebViewProvider implementations
-         */
-        @SystemApi
-        public void setType(int type) {
-            mType = type;
-        }
-
-        /**
-         * @hide Only for use by WebViewProvider implementations
-         */
-        @SystemApi
-        public void setExtra(String extra) {
-            mExtra = extra;
-        }
-
-        /**
-         * Gets the type of the hit test result. See the XXX_TYPE constants
-         * defined in this class.
-         *
-         * @return the type of the hit test result
-         */
-        public int getType() {
-            return mType;
-        }
-
-        /**
-         * Gets additional type-dependant information about the result. See
-         * {@link WebView#getHitTestResult()} for details. May either be {@code null}
-         * or contain extra information about this result.
-         *
-         * @return additional type-dependant information about the result
-         */
-        @Nullable
-        public String getExtra() {
-            return mExtra;
-        }
-    }
-
-    /**
-     * Constructs a new WebView with a Context object.
-     *
-     * @param context a Context object used to access application assets
+     * Construct a new WebView with a Context object.
+     * @param context A Context object used to access application assets.
      */
     public WebView(Context context) {
         this(context, null);
     }
 
     /**
-     * Constructs a new WebView with layout parameters.
-     *
-     * @param context a Context object used to access application assets
-     * @param attrs an AttributeSet passed to our parent
+     * Construct a new WebView with layout parameters.
+     * @param context A Context object used to access application assets.
+     * @param attrs An AttributeSet passed to our parent.
      */
     public WebView(Context context, AttributeSet attrs) {
         this(context, attrs, com.android.internal.R.attr.webViewStyle);
     }
 
     /**
-     * Constructs a new WebView with layout parameters and a default style.
-     *
-     * @param context a Context object used to access application assets
-     * @param attrs an AttributeSet passed to our parent
-     * @param defStyleAttr an attribute in the current theme that contains a
-     *        reference to a style resource that supplies default values for
-     *        the view. Can be 0 to not look for defaults.
+     * Construct a new WebView with layout parameters and a default style.
+     * @param context A Context object used to access application assets.
+     * @param attrs An AttributeSet passed to our parent.
+     * @param defStyle The default style resource ID.
      */
-    public WebView(Context context, AttributeSet attrs, int defStyleAttr) {
-        this(context, attrs, defStyleAttr, 0);
+    public WebView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
     }
-
-    /**
-     * Constructs a new WebView with layout parameters and a default style.
-     *
-     * @param context a Context object used to access application assets
-     * @param attrs an AttributeSet passed to our parent
-     * @param defStyleAttr an attribute in the current theme that contains a
-     *        reference to a style resource that supplies default values for
-     *        the view. Can be 0 to not look for defaults.
-     * @param defStyleRes a resource identifier of a style resource that
-     *        supplies default values for the view, used only if
-     *        defStyleAttr is 0 or can not be found in the theme. Can be 0
-     *        to not look for defaults.
-     */
-    public WebView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
-        this(context, attrs, defStyleAttr, defStyleRes, null, false);
-    }
-
-    /**
-     * Constructs a new WebView with layout parameters and a default style.
-     *
-     * @param context a Context object used to access application assets
-     * @param attrs an AttributeSet passed to our parent
-     * @param defStyleAttr an attribute in the current theme that contains a
-     *        reference to a style resource that supplies default values for
-     *        the view. Can be 0 to not look for defaults.
-     * @param privateBrowsing whether this WebView will be initialized in
-     *                        private mode
-     *
-     * @deprecated Private browsing is no longer supported directly via
-     * WebView and will be removed in a future release. Prefer using
-     * {@link WebSettings}, {@link WebViewDatabase}, {@link CookieManager}
-     * and {@link WebStorage} for fine-grained control of privacy data.
-     */
-    @Deprecated
-    public WebView(Context context, AttributeSet attrs, int defStyleAttr,
-            boolean privateBrowsing) {
-        this(context, attrs, defStyleAttr, 0, null, privateBrowsing);
-    }
-
-    /**
-     * Constructs a new WebView with layout parameters, a default style and a set
-     * of custom JavaScript interfaces to be added to this WebView at initialization
-     * time. This guarantees that these interfaces will be available when the JS
-     * context is initialized.
-     *
-     * @param context a Context object used to access application assets
-     * @param attrs an AttributeSet passed to our parent
-     * @param defStyleAttr an attribute in the current theme that contains a
-     *        reference to a style resource that supplies default values for
-     *        the view. Can be 0 to not look for defaults.
-     * @param javaScriptInterfaces a Map of interface names, as keys, and
-     *                             object implementing those interfaces, as
-     *                             values
-     * @param privateBrowsing whether this WebView will be initialized in
-     *                        private mode
-     * @hide This is used internally by dumprendertree, as it requires the JavaScript interfaces to
-     *       be added synchronously, before a subsequent loadUrl call takes effect.
-     */
-    protected WebView(Context context, AttributeSet attrs, int defStyleAttr,
-            Map<String, Object> javaScriptInterfaces, boolean privateBrowsing) {
-        this(context, attrs, defStyleAttr, 0, javaScriptInterfaces, privateBrowsing);
-    }
-
-    /**
-     * @hide
-     */
-    @SuppressWarnings("deprecation")  // for super() call into deprecated base class constructor.
-    protected WebView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes,
-            Map<String, Object> javaScriptInterfaces, boolean privateBrowsing) {
-        super(context, attrs, defStyleAttr, defStyleRes);
-
-        // WebView is important by default, unless app developer overrode attribute.
-        if (getImportantForAutofill() == IMPORTANT_FOR_AUTOFILL_AUTO) {
-            setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_YES);
-        }
-
-        if (context == null) {
-            throw new IllegalArgumentException("Invalid context argument");
-        }
-        sEnforceThreadChecking = context.getApplicationInfo().targetSdkVersion >=
-                Build.VERSION_CODES.JELLY_BEAN_MR2;
-        checkThread();
-
-        ensureProviderCreated();
-        mProvider.init(javaScriptInterfaces, privateBrowsing);
-        // Post condition of creating a webview is the CookieSyncManager.getInstance() is allowed.
-        CookieSyncManager.setGetInstanceIsAllowed();
-    }
-
-    /**
-     * Specifies whether the horizontal scrollbar has overlay style.
-     *
-     * @deprecated This method has no effect.
-     * @param overlay {@code true} if horizontal scrollbar should have overlay style
-     */
-    @Deprecated
+    
+    // START FAKE PUBLIC METHODS
+    
     public void setHorizontalScrollbarOverlay(boolean overlay) {
     }
 
-    /**
-     * Specifies whether the vertical scrollbar has overlay style.
-     *
-     * @deprecated This method has no effect.
-     * @param overlay {@code true} if vertical scrollbar should have overlay style
-     */
-    @Deprecated
     public void setVerticalScrollbarOverlay(boolean overlay) {
     }
 
-    /**
-     * Gets whether horizontal scrollbar has overlay style.
-     *
-     * @deprecated This method is now obsolete.
-     * @return {@code true}
-     */
-    @Deprecated
     public boolean overlayHorizontalScrollbar() {
-        // The old implementation defaulted to true, so return true for consistency
-        return true;
-    }
-
-    /**
-     * Gets whether vertical scrollbar has overlay style.
-     *
-     * @deprecated This method is now obsolete.
-     * @return {@code false}
-     */
-    @Deprecated
-    public boolean overlayVerticalScrollbar() {
-        // The old implementation defaulted to false, so return false for consistency
         return false;
     }
 
-    /**
-     * Gets the visible height (in pixels) of the embedded title bar (if any).
-     *
-     * @deprecated This method is now obsolete.
-     * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
-     */
-    @Deprecated
-    public int getVisibleTitleHeight() {
-        checkThread();
-        return mProvider.getVisibleTitleHeight();
+    public boolean overlayVerticalScrollbar() {
+        return false;
     }
 
-    /**
-     * Gets the SSL certificate for the main top-level page or {@code null} if there is
-     * no certificate (the site is not secure).
-     *
-     * @return the SSL certificate for the main top-level page
-     */
-    @Nullable
-    public SslCertificate getCertificate() {
-        checkThread();
-        return mProvider.getCertificate();
-    }
-
-    /**
-     * Sets the SSL certificate for the main top-level page.
-     *
-     * @deprecated Calling this function has no useful effect, and will be
-     * ignored in future releases.
-     */
-    @Deprecated
-    public void setCertificate(SslCertificate certificate) {
-        checkThread();
-        mProvider.setCertificate(certificate);
-    }
-
-    //-------------------------------------------------------------------------
-    // Methods called by activity
-    //-------------------------------------------------------------------------
-
-    /**
-     * Sets a username and password pair for the specified host. This data is
-     * used by the WebView to autocomplete username and password fields in web
-     * forms. Note that this is unrelated to the credentials used for HTTP
-     * authentication.
-     *
-     * @param host the host that required the credentials
-     * @param username the username for the given host
-     * @param password the password for the given host
-     * @see WebViewDatabase#clearUsernamePassword
-     * @see WebViewDatabase#hasUsernamePassword
-     * @deprecated Saving passwords in WebView will not be supported in future versions.
-     */
-    @Deprecated
     public void savePassword(String host, String username, String password) {
-        checkThread();
-        mProvider.savePassword(host, username, password);
     }
 
-    /**
-     * Stores HTTP authentication credentials for a given host and realm to the {@link WebViewDatabase}
-     * instance.
-     *
-     * @param host the host to which the credentials apply
-     * @param realm the realm to which the credentials apply
-     * @param username the username
-     * @param password the password
-     * @deprecated Use {@link WebViewDatabase#setHttpAuthUsernamePassword} instead
-     */
-    @Deprecated
     public void setHttpAuthUsernamePassword(String host, String realm,
             String username, String password) {
-        checkThread();
-        mProvider.setHttpAuthUsernamePassword(host, realm, username, password);
     }
 
-    /**
-     * Retrieves HTTP authentication credentials for a given host and realm from the {@link
-     * WebViewDatabase} instance.
-     * @param host the host to which the credentials apply
-     * @param realm the realm to which the credentials apply
-     * @return the credentials as a String array, if found. The first element
-     *         is the username and the second element is the password. {@code null} if
-     *         no credentials are found.
-     * @deprecated Use {@link WebViewDatabase#getHttpAuthUsernamePassword} instead
-     */
-    @Deprecated
-    @Nullable
     public String[] getHttpAuthUsernamePassword(String host, String realm) {
-        checkThread();
-        return mProvider.getHttpAuthUsernamePassword(host, realm);
+        return null;
     }
 
-    /**
-     * Destroys the internal state of this WebView. This method should be called
-     * after this WebView has been removed from the view system. No other
-     * methods may be called on this WebView after destroy.
-     */
     public void destroy() {
-        checkThread();
-        mProvider.destroy();
     }
 
-    /**
-     * Enables platform notifications of data state and proxy changes.
-     * Notifications are enabled by default.
-     *
-     * @deprecated This method is now obsolete.
-     * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
-     */
-    @Deprecated
     public static void enablePlatformNotifications() {
-        // noop
     }
 
-    /**
-     * Disables platform notifications of data state and proxy changes.
-     * Notifications are enabled by default.
-     *
-     * @deprecated This method is now obsolete.
-     * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
-     */
-    @Deprecated
     public static void disablePlatformNotifications() {
-        // noop
     }
 
-    /**
-     * Used only by internal tests to free up memory.
-     *
-     * @hide
-     */
-    public static void freeMemoryForTests() {
-        getFactory().getStatics().freeMemoryForTests();
-    }
-
-    /**
-     * Informs WebView of the network state. This is used to set
-     * the JavaScript property window.navigator.isOnline and
-     * generates the online/offline event as specified in HTML5, sec. 5.7.7
-     *
-     * @param networkUp a boolean indicating if network is available
-     */
-    public void setNetworkAvailable(boolean networkUp) {
-        checkThread();
-        mProvider.setNetworkAvailable(networkUp);
-    }
-
-    /**
-     * Saves the state of this WebView used in
-     * {@link android.app.Activity#onSaveInstanceState}. Please note that this
-     * method no longer stores the display data for this WebView. The previous
-     * behavior could potentially leak files if {@link #restoreState} was never
-     * called.
-     *
-     * @param outState the Bundle to store this WebView's state
-     * @return the same copy of the back/forward list used to save the state, {@code null} if the
-     *         method fails.
-     */
-    @Nullable
-    public WebBackForwardList saveState(Bundle outState) {
-        checkThread();
-        return mProvider.saveState(outState);
-    }
-
-    /**
-     * Saves the current display data to the Bundle given. Used in conjunction
-     * with {@link #saveState}.
-     * @param b a Bundle to store the display data
-     * @param dest the file to store the serialized picture data. Will be
-     *             overwritten with this WebView's picture data.
-     * @return {@code true} if the picture was successfully saved
-     * @deprecated This method is now obsolete.
-     * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
-     */
-    @Deprecated
-    public boolean savePicture(Bundle b, final File dest) {
-        checkThread();
-        return mProvider.savePicture(b, dest);
-    }
-
-    /**
-     * Restores the display data that was saved in {@link #savePicture}. Used in
-     * conjunction with {@link #restoreState}. Note that this will not work if
-     * this WebView is hardware accelerated.
-     *
-     * @param b a Bundle containing the saved display data
-     * @param src the file where the picture data was stored
-     * @return {@code true} if the picture was successfully restored
-     * @deprecated This method is now obsolete.
-     * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
-     */
-    @Deprecated
-    public boolean restorePicture(Bundle b, File src) {
-        checkThread();
-        return mProvider.restorePicture(b, src);
-    }
-
-    /**
-     * Restores the state of this WebView from the given Bundle. This method is
-     * intended for use in {@link android.app.Activity#onRestoreInstanceState}
-     * and should be called to restore the state of this WebView. If
-     * it is called after this WebView has had a chance to build state (load
-     * pages, create a back/forward list, etc.) there may be undesirable
-     * side-effects. Please note that this method no longer restores the
-     * display data for this WebView.
-     *
-     * @param inState the incoming Bundle of state
-     * @return the restored back/forward list or {@code null} if restoreState failed
-     */
-    @Nullable
-    public WebBackForwardList restoreState(Bundle inState) {
-        checkThread();
-        return mProvider.restoreState(inState);
-    }
-
-    /**
-     * Loads the given URL with the specified additional HTTP headers.
-     * <p>
-     * Also see compatibility note on {@link #evaluateJavascript}.
-     *
-     * @param url the URL of the resource to load
-     * @param additionalHttpHeaders the additional headers to be used in the
-     *            HTTP request for this URL, specified as a map from name to
-     *            value. Note that if this map contains any of the headers
-     *            that are set by default by this WebView, such as those
-     *            controlling caching, accept types or the User-Agent, their
-     *            values may be overridden by this WebView's defaults.
-     */
-    public void loadUrl(String url, Map<String, String> additionalHttpHeaders) {
-        checkThread();
-        mProvider.loadUrl(url, additionalHttpHeaders);
-    }
-
-    /**
-     * Loads the given URL.
-     * <p>
-     * Also see compatibility note on {@link #evaluateJavascript}.
-     *
-     * @param url the URL of the resource to load
-     */
     public void loadUrl(String url) {
-        checkThread();
-        mProvider.loadUrl(url);
     }
 
-    /**
-     * Loads the URL with postData using "POST" method into this WebView. If url
-     * is not a network URL, it will be loaded with {@link #loadUrl(String)}
-     * instead, ignoring the postData param.
-     *
-     * @param url the URL of the resource to load
-     * @param postData the data will be passed to "POST" request, which must be
-     *     be "application/x-www-form-urlencoded" encoded.
-     */
-    public void postUrl(String url, byte[] postData) {
-        checkThread();
-        if (URLUtil.isNetworkUrl(url)) {
-            mProvider.postUrl(url, postData);
-        } else {
-            mProvider.loadUrl(url);
-        }
+    public void loadData(String data, String mimeType, String encoding) {
     }
 
-    /**
-     * Loads the given data into this WebView using a 'data' scheme URL.
-     * <p>
-     * Note that JavaScript's same origin policy means that script running in a
-     * page loaded using this method will be unable to access content loaded
-     * using any scheme other than 'data', including 'http(s)'. To avoid this
-     * restriction, use {@link
-     * #loadDataWithBaseURL(String,String,String,String,String)
-     * loadDataWithBaseURL()} with an appropriate base URL.
-     * <p>
-     * The encoding parameter specifies whether the data is base64 or URL
-     * encoded. If the data is base64 encoded, the value of the encoding
-     * parameter must be 'base64'. For all other values of the parameter,
-     * including {@code null}, it is assumed that the data uses ASCII encoding for
-     * octets inside the range of safe URL characters and use the standard %xx
-     * hex encoding of URLs for octets outside that range. For example, '#',
-     * '%', '\', '?' should be replaced by %23, %25, %27, %3f respectively.
-     * <p>
-     * The 'data' scheme URL formed by this method uses the default US-ASCII
-     * charset. If you need need to set a different charset, you should form a
-     * 'data' scheme URL which explicitly specifies a charset parameter in the
-     * mediatype portion of the URL and call {@link #loadUrl(String)} instead.
-     * Note that the charset obtained from the mediatype portion of a data URL
-     * always overrides that specified in the HTML or XML document itself.
-     *
-     * @param data a String of data in the given encoding
-     * @param mimeType the MIMEType of the data, e.g. 'text/html'. If {@code null},
-     *                 defaults to 'text/html'.
-     * @param encoding the encoding of the data
-     */
-    public void loadData(String data, @Nullable String mimeType, @Nullable String encoding) {
-        checkThread();
-        mProvider.loadData(data, mimeType, encoding);
+    public void loadDataWithBaseURL(String baseUrl, String data,
+            String mimeType, String encoding, String failUrl) {
     }
 
-    /**
-     * Loads the given data into this WebView, using baseUrl as the base URL for
-     * the content. The base URL is used both to resolve relative URLs and when
-     * applying JavaScript's same origin policy. The historyUrl is used for the
-     * history entry.
-     * <p>
-     * Note that content specified in this way can access local device files
-     * (via 'file' scheme URLs) only if baseUrl specifies a scheme other than
-     * 'http', 'https', 'ftp', 'ftps', 'about' or 'javascript'.
-     * <p>
-     * If the base URL uses the data scheme, this method is equivalent to
-     * calling {@link #loadData(String,String,String) loadData()} and the
-     * historyUrl is ignored, and the data will be treated as part of a data: URL.
-     * If the base URL uses any other scheme, then the data will be loaded into
-     * the WebView as a plain string (i.e. not part of a data URL) and any URL-encoded
-     * entities in the string will not be decoded.
-     * <p>
-     * Note that the baseUrl is sent in the 'Referer' HTTP header when
-     * requesting subresources (images, etc.) of the page loaded using this method.
-     *
-     * @param baseUrl the URL to use as the page's base URL. If {@code null} defaults to
-     *                'about:blank'.
-     * @param data a String of data in the given encoding
-     * @param mimeType the MIMEType of the data, e.g. 'text/html'. If {@code null},
-     *                 defaults to 'text/html'.
-     * @param encoding the encoding of the data
-     * @param historyUrl the URL to use as the history entry. If {@code null} defaults
-     *                   to 'about:blank'. If non-null, this must be a valid URL.
-     */
-    public void loadDataWithBaseURL(@Nullable String baseUrl, String data,
-            @Nullable String mimeType, @Nullable String encoding, @Nullable String historyUrl) {
-        checkThread();
-        mProvider.loadDataWithBaseURL(baseUrl, data, mimeType, encoding, historyUrl);
-    }
-
-    /**
-     * Asynchronously evaluates JavaScript in the context of the currently displayed page.
-     * If non-null, |resultCallback| will be invoked with any result returned from that
-     * execution. This method must be called on the UI thread and the callback will
-     * be made on the UI thread.
-     * <p>
-     * Compatibility note. Applications targeting {@link android.os.Build.VERSION_CODES#N} or
-     * later, JavaScript state from an empty WebView is no longer persisted across navigations like
-     * {@link #loadUrl(String)}. For example, global variables and functions defined before calling
-     * {@link #loadUrl(String)} will not exist in the loaded page. Applications should use
-     * {@link #addJavascriptInterface} instead to persist JavaScript objects across navigations.
-     *
-     * @param script the JavaScript to execute.
-     * @param resultCallback A callback to be invoked when the script execution
-     *                       completes with the result of the execution (if any).
-     *                       May be {@code null} if no notification of the result is required.
-     */
-    public void evaluateJavascript(String script, @Nullable ValueCallback<String> resultCallback) {
-        checkThread();
-        mProvider.evaluateJavaScript(script, resultCallback);
-    }
-
-    /**
-     * Saves the current view as a web archive.
-     *
-     * @param filename the filename where the archive should be placed
-     */
-    public void saveWebArchive(String filename) {
-        checkThread();
-        mProvider.saveWebArchive(filename);
-    }
-
-    /**
-     * Saves the current view as a web archive.
-     *
-     * @param basename the filename where the archive should be placed
-     * @param autoname if {@code false}, takes basename to be a file. If {@code true}, basename
-     *                 is assumed to be a directory in which a filename will be
-     *                 chosen according to the URL of the current page.
-     * @param callback called after the web archive has been saved. The
-     *                 parameter for onReceiveValue will either be the filename
-     *                 under which the file was saved, or {@code null} if saving the
-     *                 file failed.
-     */
-    public void saveWebArchive(String basename, boolean autoname, @Nullable ValueCallback<String>
-            callback) {
-        checkThread();
-        mProvider.saveWebArchive(basename, autoname, callback);
-    }
-
-    /**
-     * Stops the current load.
-     */
     public void stopLoading() {
-        checkThread();
-        mProvider.stopLoading();
     }
 
-    /**
-     * Reloads the current URL.
-     */
     public void reload() {
-        checkThread();
-        mProvider.reload();
     }
 
-    /**
-     * Gets whether this WebView has a back history item.
-     *
-     * @return {@code true} iff this WebView has a back history item
-     */
     public boolean canGoBack() {
-        checkThread();
-        return mProvider.canGoBack();
+        return false;
     }
 
-    /**
-     * Goes back in the history of this WebView.
-     */
     public void goBack() {
-        checkThread();
-        mProvider.goBack();
     }
 
-    /**
-     * Gets whether this WebView has a forward history item.
-     *
-     * @return {@code true} iff this WebView has a forward history item
-     */
     public boolean canGoForward() {
-        checkThread();
-        return mProvider.canGoForward();
+        return false;
     }
 
-    /**
-     * Goes forward in the history of this WebView.
-     */
     public void goForward() {
-        checkThread();
-        mProvider.goForward();
     }
 
-    /**
-     * Gets whether the page can go back or forward the given
-     * number of steps.
-     *
-     * @param steps the negative or positive number of steps to move the
-     *              history
-     */
     public boolean canGoBackOrForward(int steps) {
-        checkThread();
-        return mProvider.canGoBackOrForward(steps);
+        return false;
     }
 
-    /**
-     * Goes to the history item that is the number of steps away from
-     * the current item. Steps is negative if backward and positive
-     * if forward.
-     *
-     * @param steps the number of steps to take back or forward in the back
-     *              forward list
-     */
     public void goBackOrForward(int steps) {
-        checkThread();
-        mProvider.goBackOrForward(steps);
     }
 
-    /**
-     * Gets whether private browsing is enabled in this WebView.
-     */
-    public boolean isPrivateBrowsingEnabled() {
-        checkThread();
-        return mProvider.isPrivateBrowsingEnabled();
-    }
-
-    /**
-     * Scrolls the contents of this WebView up by half the view size.
-     *
-     * @param top {@code true} to jump to the top of the page
-     * @return {@code true} if the page was scrolled
-     */
     public boolean pageUp(boolean top) {
-        checkThread();
-        return mProvider.pageUp(top);
+        return false;
     }
-
-    /**
-     * Scrolls the contents of this WebView down by half the page size.
-     *
-     * @param bottom {@code true} to jump to bottom of page
-     * @return {@code true} if the page was scrolled
-     */
+    
     public boolean pageDown(boolean bottom) {
-        checkThread();
-        return mProvider.pageDown(bottom);
+        return false;
     }
 
-    /**
-     * Posts a {@link VisualStateCallback}, which will be called when
-     * the current state of the WebView is ready to be drawn.
-     *
-     * <p>Because updates to the DOM are processed asynchronously, updates to the DOM may not
-     * immediately be reflected visually by subsequent {@link WebView#onDraw} invocations. The
-     * {@link VisualStateCallback} provides a mechanism to notify the caller when the contents of
-     * the DOM at the current time are ready to be drawn the next time the {@link WebView}
-     * draws.
-     *
-     * <p>The next draw after the callback completes is guaranteed to reflect all the updates to the
-     * DOM up to the point at which the {@link VisualStateCallback} was posted, but it may also
-     * contain updates applied after the callback was posted.
-     *
-     * <p>The state of the DOM covered by this API includes the following:
-     * <ul>
-     * <li>primitive HTML elements (div, img, span, etc..)</li>
-     * <li>images</li>
-     * <li>CSS animations</li>
-     * <li>WebGL</li>
-     * <li>canvas</li>
-     * </ul>
-     * It does not include the state of:
-     * <ul>
-     * <li>the video tag</li>
-     * </ul>
-     *
-     * <p>To guarantee that the {@link WebView} will successfully render the first frame
-     * after the {@link VisualStateCallback#onComplete} method has been called a set of conditions
-     * must be met:
-     * <ul>
-     * <li>If the {@link WebView}'s visibility is set to {@link View#VISIBLE VISIBLE} then
-     * the {@link WebView} must be attached to the view hierarchy.</li>
-     * <li>If the {@link WebView}'s visibility is set to {@link View#INVISIBLE INVISIBLE}
-     * then the {@link WebView} must be attached to the view hierarchy and must be made
-     * {@link View#VISIBLE VISIBLE} from the {@link VisualStateCallback#onComplete} method.</li>
-     * <li>If the {@link WebView}'s visibility is set to {@link View#GONE GONE} then the
-     * {@link WebView} must be attached to the view hierarchy and its
-     * {@link AbsoluteLayout.LayoutParams LayoutParams}'s width and height need to be set to fixed
-     * values and must be made {@link View#VISIBLE VISIBLE} from the
-     * {@link VisualStateCallback#onComplete} method.</li>
-     * </ul>
-     *
-     * <p>When using this API it is also recommended to enable pre-rasterization if the {@link
-     * WebView} is off screen to avoid flickering. See {@link WebSettings#setOffscreenPreRaster} for
-     * more details and do consider its caveats.
-     *
-     * @param requestId An id that will be returned in the callback to allow callers to match
-     *                  requests with callbacks.
-     * @param callback  The callback to be invoked.
-     */
-    public void postVisualStateCallback(long requestId, VisualStateCallback callback) {
-        checkThread();
-        mProvider.insertVisualStateCallback(requestId, callback);
-    }
-
-    /**
-     * Clears this WebView so that onDraw() will draw nothing but white background,
-     * and onMeasure() will return 0 if MeasureSpec is not MeasureSpec.EXACTLY.
-     * @deprecated Use WebView.loadUrl("about:blank") to reliably reset the view state
-     *             and release page resources (including any running JavaScript).
-     */
-    @Deprecated
     public void clearView() {
-        checkThread();
-        mProvider.clearView();
     }
-
-    /**
-     * Gets a new picture that captures the current contents of this WebView.
-     * The picture is of the entire document being displayed, and is not
-     * limited to the area currently displayed by this WebView. Also, the
-     * picture is a static copy and is unaffected by later changes to the
-     * content being displayed.
-     * <p>
-     * Note that due to internal changes, for API levels between
-     * {@link android.os.Build.VERSION_CODES#HONEYCOMB} and
-     * {@link android.os.Build.VERSION_CODES#ICE_CREAM_SANDWICH} inclusive, the
-     * picture does not include fixed position elements or scrollable divs.
-     * <p>
-     * Note that from {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1} the returned picture
-     * should only be drawn into bitmap-backed Canvas - using any other type of Canvas will involve
-     * additional conversion at a cost in memory and performance. Also the
-     * {@link android.graphics.Picture#createFromStream} and
-     * {@link android.graphics.Picture#writeToStream} methods are not supported on the
-     * returned object.
-     *
-     * @deprecated Use {@link #onDraw} to obtain a bitmap snapshot of the WebView, or
-     * {@link #saveWebArchive} to save the content to a file.
-     *
-     * @return a picture that captures the current contents of this WebView
-     */
-    @Deprecated
+    
     public Picture capturePicture() {
-        checkThread();
-        return mProvider.capturePicture();
+        return null;
     }
 
-    /**
-     * @deprecated Use {@link #createPrintDocumentAdapter(String)} which requires user
-     *             to provide a print document name.
-     */
-    @Deprecated
-    public PrintDocumentAdapter createPrintDocumentAdapter() {
-        checkThread();
-        return mProvider.createPrintDocumentAdapter("default");
-    }
-
-    /**
-     * Creates a PrintDocumentAdapter that provides the content of this WebView for printing.
-     *
-     * The adapter works by converting the WebView contents to a PDF stream. The WebView cannot
-     * be drawn during the conversion process - any such draws are undefined. It is recommended
-     * to use a dedicated off screen WebView for the printing. If necessary, an application may
-     * temporarily hide a visible WebView by using a custom PrintDocumentAdapter instance
-     * wrapped around the object returned and observing the onStart and onFinish methods. See
-     * {@link android.print.PrintDocumentAdapter} for more information.
-     *
-     * @param documentName  The user-facing name of the printed document. See
-     *                      {@link android.print.PrintDocumentInfo}
-     */
-    public PrintDocumentAdapter createPrintDocumentAdapter(String documentName) {
-        checkThread();
-        return mProvider.createPrintDocumentAdapter(documentName);
-    }
-
-    /**
-     * Gets the current scale of this WebView.
-     *
-     * @return the current scale
-     *
-     * @deprecated This method is prone to inaccuracy due to race conditions
-     * between the web rendering and UI threads; prefer
-     * {@link WebViewClient#onScaleChanged}.
-     */
-    @Deprecated
-    @ViewDebug.ExportedProperty(category = "webview")
     public float getScale() {
-        checkThread();
-        return mProvider.getScale();
+        return 0;
     }
 
-    /**
-     * Sets the initial scale for this WebView. 0 means default.
-     * The behavior for the default scale depends on the state of
-     * {@link WebSettings#getUseWideViewPort()} and
-     * {@link WebSettings#getLoadWithOverviewMode()}.
-     * If the content fits into the WebView control by width, then
-     * the zoom is set to 100%. For wide content, the behavior
-     * depends on the state of {@link WebSettings#getLoadWithOverviewMode()}.
-     * If its value is {@code true}, the content will be zoomed out to be fit
-     * by width into the WebView control, otherwise not.
-     *
-     * If initial scale is greater than 0, WebView starts with this value
-     * as initial scale.
-     * Please note that unlike the scale properties in the viewport meta tag,
-     * this method doesn't take the screen density into account.
-     *
-     * @param scaleInPercent the initial scale in percent
-     */
     public void setInitialScale(int scaleInPercent) {
-        checkThread();
-        mProvider.setInitialScale(scaleInPercent);
     }
 
-    /**
-     * Invokes the graphical zoom picker widget for this WebView. This will
-     * result in the zoom widget appearing on the screen to control the zoom
-     * level of this WebView.
-     */
     public void invokeZoomPicker() {
-        checkThread();
-        mProvider.invokeZoomPicker();
     }
 
-    /**
-     * Gets a HitTestResult based on the current cursor node. If a HTML::a
-     * tag is found and the anchor has a non-JavaScript URL, the HitTestResult
-     * type is set to SRC_ANCHOR_TYPE and the URL is set in the "extra" field.
-     * If the anchor does not have a URL or if it is a JavaScript URL, the type
-     * will be UNKNOWN_TYPE and the URL has to be retrieved through
-     * {@link #requestFocusNodeHref} asynchronously. If a HTML::img tag is
-     * found, the HitTestResult type is set to IMAGE_TYPE and the URL is set in
-     * the "extra" field. A type of
-     * SRC_IMAGE_ANCHOR_TYPE indicates an anchor with a URL that has an image as
-     * a child node. If a phone number is found, the HitTestResult type is set
-     * to PHONE_TYPE and the phone number is set in the "extra" field of
-     * HitTestResult. If a map address is found, the HitTestResult type is set
-     * to GEO_TYPE and the address is set in the "extra" field of HitTestResult.
-     * If an email address is found, the HitTestResult type is set to EMAIL_TYPE
-     * and the email is set in the "extra" field of HitTestResult. Otherwise,
-     * HitTestResult type is set to UNKNOWN_TYPE.
-     */
-    public HitTestResult getHitTestResult() {
-        checkThread();
-        return mProvider.getHitTestResult();
+    public void requestFocusNodeHref(Message hrefMsg) {
     }
 
-    /**
-     * Requests the anchor or image element URL at the last tapped point.
-     * If hrefMsg is {@code null}, this method returns immediately and does not
-     * dispatch hrefMsg to its target. If the tapped point hits an image,
-     * an anchor, or an image in an anchor, the message associates
-     * strings in named keys in its data. The value paired with the key
-     * may be an empty string.
-     *
-     * @param hrefMsg the message to be dispatched with the result of the
-     *                request. The message data contains three keys. "url"
-     *                returns the anchor's href attribute. "title" returns the
-     *                anchor's text. "src" returns the image's src attribute.
-     */
-    public void requestFocusNodeHref(@Nullable Message hrefMsg) {
-        checkThread();
-        mProvider.requestFocusNodeHref(hrefMsg);
-    }
-
-    /**
-     * Requests the URL of the image last touched by the user. msg will be sent
-     * to its target with a String representing the URL as its object.
-     *
-     * @param msg the message to be dispatched with the result of the request
-     *            as the data member with "url" as key. The result can be {@code null}.
-     */
     public void requestImageRef(Message msg) {
-        checkThread();
-        mProvider.requestImageRef(msg);
     }
 
-    /**
-     * Gets the URL for the current page. This is not always the same as the URL
-     * passed to WebViewClient.onPageStarted because although the load for
-     * that URL has begun, the current page may not have changed.
-     *
-     * @return the URL for the current page
-     */
-    @ViewDebug.ExportedProperty(category = "webview")
     public String getUrl() {
-        checkThread();
-        return mProvider.getUrl();
+        return null;
     }
 
-    /**
-     * Gets the original URL for the current page. This is not always the same
-     * as the URL passed to WebViewClient.onPageStarted because although the
-     * load for that URL has begun, the current page may not have changed.
-     * Also, there may have been redirects resulting in a different URL to that
-     * originally requested.
-     *
-     * @return the URL that was originally requested for the current page
-     */
-    @ViewDebug.ExportedProperty(category = "webview")
-    public String getOriginalUrl() {
-        checkThread();
-        return mProvider.getOriginalUrl();
-    }
-
-    /**
-     * Gets the title for the current page. This is the title of the current page
-     * until WebViewClient.onReceivedTitle is called.
-     *
-     * @return the title for the current page
-     */
-    @ViewDebug.ExportedProperty(category = "webview")
     public String getTitle() {
-        checkThread();
-        return mProvider.getTitle();
+        return null;
     }
 
-    /**
-     * Gets the favicon for the current page. This is the favicon of the current
-     * page until WebViewClient.onReceivedIcon is called.
-     *
-     * @return the favicon for the current page
-     */
     public Bitmap getFavicon() {
-        checkThread();
-        return mProvider.getFavicon();
+        return null;
     }
 
-    /**
-     * Gets the touch icon URL for the apple-touch-icon <link> element, or
-     * a URL on this site's server pointing to the standard location of a
-     * touch icon.
-     *
-     * @hide
-     */
-    public String getTouchIconUrl() {
-        return mProvider.getTouchIconUrl();
-    }
-
-    /**
-     * Gets the progress for the current page.
-     *
-     * @return the progress for the current page between 0 and 100
-     */
     public int getProgress() {
-        checkThread();
-        return mProvider.getProgress();
+        return 0;
     }
-
-    /**
-     * Gets the height of the HTML content.
-     *
-     * @return the height of the HTML content
-     */
-    @ViewDebug.ExportedProperty(category = "webview")
+    
     public int getContentHeight() {
-        checkThread();
-        return mProvider.getContentHeight();
+        return 0;
     }
 
-    /**
-     * Gets the width of the HTML content.
-     *
-     * @return the width of the HTML content
-     * @hide
-     */
-    @ViewDebug.ExportedProperty(category = "webview")
-    public int getContentWidth() {
-        return mProvider.getContentWidth();
-    }
-
-    /**
-     * Pauses all layout, parsing, and JavaScript timers for all WebViews. This
-     * is a global requests, not restricted to just this WebView. This can be
-     * useful if the application has been paused.
-     */
     public void pauseTimers() {
-        checkThread();
-        mProvider.pauseTimers();
     }
 
-    /**
-     * Resumes all layout, parsing, and JavaScript timers for all WebViews.
-     * This will resume dispatching all timers.
-     */
     public void resumeTimers() {
-        checkThread();
-        mProvider.resumeTimers();
     }
 
-    /**
-     * Does a best-effort attempt to pause any processing that can be paused
-     * safely, such as animations and geolocation. Note that this call
-     * does not pause JavaScript. To pause JavaScript globally, use
-     * {@link #pauseTimers}.
-     *
-     * To resume WebView, call {@link #onResume}.
-     */
-    public void onPause() {
-        checkThread();
-        mProvider.onPause();
+    public void clearCache() {
     }
 
-    /**
-     * Resumes a WebView after a previous call to {@link #onPause}.
-     */
-    public void onResume() {
-        checkThread();
-        mProvider.onResume();
-    }
-
-    /**
-     * Gets whether this WebView is paused, meaning onPause() was called.
-     * Calling onResume() sets the paused state back to {@code false}.
-     *
-     * @hide
-     */
-    public boolean isPaused() {
-        return mProvider.isPaused();
-    }
-
-    /**
-     * Informs this WebView that memory is low so that it can free any available
-     * memory.
-     * @deprecated Memory caches are automatically dropped when no longer needed, and in response
-     *             to system memory pressure.
-     */
-    @Deprecated
-    public void freeMemory() {
-        checkThread();
-        mProvider.freeMemory();
-    }
-
-    /**
-     * Clears the resource cache. Note that the cache is per-application, so
-     * this will clear the cache for all WebViews used.
-     *
-     * @param includeDiskFiles if {@code false}, only the RAM cache is cleared
-     */
-    public void clearCache(boolean includeDiskFiles) {
-        checkThread();
-        mProvider.clearCache(includeDiskFiles);
-    }
-
-    /**
-     * Removes the autocomplete popup from the currently focused form field, if
-     * present. Note this only affects the display of the autocomplete popup,
-     * it does not remove any saved form data from this WebView's store. To do
-     * that, use {@link WebViewDatabase#clearFormData}.
-     */
     public void clearFormData() {
-        checkThread();
-        mProvider.clearFormData();
     }
 
-    /**
-     * Tells this WebView to clear its internal back/forward list.
-     */
     public void clearHistory() {
-        checkThread();
-        mProvider.clearHistory();
     }
 
-    /**
-     * Clears the SSL preferences table stored in response to proceeding with
-     * SSL certificate errors.
-     */
     public void clearSslPreferences() {
-        checkThread();
-        mProvider.clearSslPreferences();
     }
 
-    /**
-     * Clears the client certificate preferences stored in response
-     * to proceeding/cancelling client cert requests. Note that WebView
-     * automatically clears these preferences when it receives a
-     * {@link KeyChain#ACTION_STORAGE_CHANGED} intent. The preferences are
-     * shared by all the WebViews that are created by the embedder application.
-     *
-     * @param onCleared  A runnable to be invoked when client certs are cleared.
-     *                   The runnable will be called in UI thread.
-     */
-    public static void clearClientCertPreferences(@Nullable Runnable onCleared) {
-        getFactory().getStatics().clearClientCertPreferences(onCleared);
-    }
-
-    /**
-     * Starts Safe Browsing initialization.
-     * <p>
-     * URL loads are not guaranteed to be protected by Safe Browsing until after {@code callback} is
-     * invoked with {@code true}. Safe Browsing is not fully supported on all devices. For those
-     * devices {@code callback} will receive {@code false}.
-     * <p>
-     * This does not enable the Safe Browsing feature itself, and should only be called if Safe
-     * Browsing is enabled by the manifest tag or {@link WebSettings#setSafeBrowsingEnabled}. This
-     * prepares resources used for Safe Browsing.
-     * <p>
-     * This should be called with the Application Context (and will always use the Application
-     * context to do its work regardless).
-     *
-     * @param context Application Context.
-     * @param callback will be called on the UI thread with {@code true} if initialization is
-     * successful, {@code false} otherwise.
-     */
-    public static void startSafeBrowsing(Context context,
-            @Nullable ValueCallback<Boolean> callback) {
-        getFactory().getStatics().initSafeBrowsing(context, callback);
-    }
-
-    /**
-     * Sets the list of domains that are exempt from SafeBrowsing checks. The list is
-     * global for all the WebViews.
-     * <p>
-     * Each rule should take one of these:
-     * <table>
-     * <tr><th> Rule </th> <th> Example </th> <th> Matches Subdomain</th> </tr>
-     * <tr><td> HOSTNAME </td> <td> example.com </td> <td> Yes </td> </tr>
-     * <tr><td> .HOSTNAME </td> <td> .example.com </td> <td> No </td> </tr>
-     * <tr><td> IPV4_LITERAL </td> <td> 192.168.1.1 </td> <td> No </td></tr>
-     * <tr><td> IPV6_LITERAL_WITH_BRACKETS </td><td>[10:20:30:40:50:60:70:80]</td><td>No</td></tr>
-     * </table>
-     * <p>
-     * All other rules, including wildcards, are invalid.
-     *
-     * @param urls the list of URLs
-     * @param callback will be called with {@code true} if URLs are successfully added to the
-     * whitelist. It will be called with {@code false} if any URLs are malformed. The callback will
-     * be run on the UI thread
-     */
-    public static void setSafeBrowsingWhitelist(@NonNull List<String> urls,
-            @Nullable ValueCallback<Boolean> callback) {
-        getFactory().getStatics().setSafeBrowsingWhitelist(urls, callback);
-    }
-
-    /**
-     * Returns a URL pointing to the privacy policy for Safe Browsing reporting.
-     *
-     * @return the url pointing to a privacy policy document which can be displayed to users.
-     */
-    @NonNull
-    public static Uri getSafeBrowsingPrivacyPolicyUrl() {
-        return getFactory().getStatics().getSafeBrowsingPrivacyPolicyUrl();
-    }
-
-    /**
-     * Gets the WebBackForwardList for this WebView. This contains the
-     * back/forward list for use in querying each item in the history stack.
-     * This is a copy of the private WebBackForwardList so it contains only a
-     * snapshot of the current state. Multiple calls to this method may return
-     * different objects. The object returned from this method will not be
-     * updated to reflect any new state.
-     */
-    public WebBackForwardList copyBackForwardList() {
-        checkThread();
-        return mProvider.copyBackForwardList();
-
-    }
-
-    /**
-     * Registers the listener to be notified as find-on-page operations
-     * progress. This will replace the current listener.
-     *
-     * @param listener an implementation of {@link FindListener}
-     */
-    public void setFindListener(FindListener listener) {
-        checkThread();
-        setupFindListenerIfNeeded();
-        mFindListener.mUserFindListener = listener;
-    }
-
-    /**
-     * Highlights and scrolls to the next match found by
-     * {@link #findAllAsync}, wrapping around page boundaries as necessary.
-     * Notifies any registered {@link FindListener}. If {@link #findAllAsync(String)}
-     * has not been called yet, or if {@link #clearMatches} has been called since the
-     * last find operation, this function does nothing.
-     *
-     * @param forward the direction to search
-     * @see #setFindListener
-     */
-    public void findNext(boolean forward) {
-        checkThread();
-        mProvider.findNext(forward);
-    }
-
-    /**
-     * Finds all instances of find on the page and highlights them.
-     * Notifies any registered {@link FindListener}.
-     *
-     * @param find the string to find
-     * @return the number of occurrences of the String "find" that were found
-     * @deprecated {@link #findAllAsync} is preferred.
-     * @see #setFindListener
-     */
-    @Deprecated
-    public int findAll(String find) {
-        checkThread();
-        StrictMode.noteSlowCall("findAll blocks UI: prefer findAllAsync");
-        return mProvider.findAll(find);
-    }
-
-    /**
-     * Finds all instances of find on the page and highlights them,
-     * asynchronously. Notifies any registered {@link FindListener}.
-     * Successive calls to this will cancel any pending searches.
-     *
-     * @param find the string to find.
-     * @see #setFindListener
-     */
-    public void findAllAsync(String find) {
-        checkThread();
-        mProvider.findAllAsync(find);
-    }
-
-    /**
-     * Starts an ActionMode for finding text in this WebView.  Only works if this
-     * WebView is attached to the view system.
-     *
-     * @param text if non-null, will be the initial text to search for.
-     *             Otherwise, the last String searched for in this WebView will
-     *             be used to start.
-     * @param showIme if {@code true}, show the IME, assuming the user will begin typing.
-     *                If {@code false} and text is non-null, perform a find all.
-     * @return {@code true} if the find dialog is shown, {@code false} otherwise
-     * @deprecated This method does not work reliably on all Android versions;
-     *             implementing a custom find dialog using WebView.findAllAsync()
-     *             provides a more robust solution.
-     */
-    @Deprecated
-    public boolean showFindDialog(@Nullable String text, boolean showIme) {
-        checkThread();
-        return mProvider.showFindDialog(text, showIme);
-    }
-
-    /**
-     * Gets the first substring consisting of the address of a physical
-     * location. Currently, only addresses in the United States are detected,
-     * and consist of:
-     * <ul>
-     *   <li>a house number</li>
-     *   <li>a street name</li>
-     *   <li>a street type (Road, Circle, etc), either spelled out or
-     *       abbreviated</li>
-     *   <li>a city name</li>
-     *   <li>a state or territory, either spelled out or two-letter abbr</li>
-     *   <li>an optional 5 digit or 9 digit zip code</li>
-     * </ul>
-     * All names must be correctly capitalized, and the zip code, if present,
-     * must be valid for the state. The street type must be a standard USPS
-     * spelling or abbreviation. The state or territory must also be spelled
-     * or abbreviated using USPS standards. The house number may not exceed
-     * five digits.
-     *
-     * @param addr the string to search for addresses
-     * @return the address, or if no address is found, {@code null}
-     */
-    @Nullable
     public static String findAddress(String addr) {
-        // TODO: Rewrite this in Java so it is not needed to start up chromium
-        // Could also be deprecated
-        return getFactory().getStatics().findAddress(addr);
+        return null;
     }
 
-    /**
-     * For apps targeting the L release, WebView has a new default behavior that reduces
-     * memory footprint and increases performance by intelligently choosing
-     * the portion of the HTML document that needs to be drawn. These
-     * optimizations are transparent to the developers. However, under certain
-     * circumstances, an App developer may want to disable them:
-     * <ol>
-     *   <li>When an app uses {@link #onDraw} to do own drawing and accesses portions
-     *       of the page that is way outside the visible portion of the page.</li>
-     *   <li>When an app uses {@link #capturePicture} to capture a very large HTML document.
-     *       Note that capturePicture is a deprecated API.</li>
-     * </ol>
-     * Enabling drawing the entire HTML document has a significant performance
-     * cost. This method should be called before any WebViews are created.
-     */
-    public static void enableSlowWholeDocumentDraw() {
-        getFactory().getStatics().enableSlowWholeDocumentDraw();
-    }
-
-    /**
-     * Clears the highlighting surrounding text matches created by
-     * {@link #findAllAsync}.
-     */
-    public void clearMatches() {
-        checkThread();
-        mProvider.clearMatches();
-    }
-
-    /**
-     * Queries the document to see if it contains any image references. The
-     * message object will be dispatched with arg1 being set to 1 if images
-     * were found and 0 if the document does not reference any images.
-     *
-     * @param response the message that will be dispatched with the result
-     */
     public void documentHasImages(Message response) {
-        checkThread();
-        mProvider.documentHasImages(response);
     }
 
-    /**
-     * Sets the WebViewClient that will receive various notifications and
-     * requests. This will replace the current handler.
-     *
-     * @param client an implementation of WebViewClient
-     * @see #getWebViewClient
-     */
     public void setWebViewClient(WebViewClient client) {
-        checkThread();
-        mProvider.setWebViewClient(client);
     }
 
-    /**
-     * Gets the WebViewClient.
-     *
-     * @return the WebViewClient, or a default client if not yet set
-     * @see #setWebViewClient
-     */
-    public WebViewClient getWebViewClient() {
-        checkThread();
-        return mProvider.getWebViewClient();
-    }
-
-    /**
-     * Registers the interface to be used when content can not be handled by
-     * the rendering engine, and should be downloaded instead. This will replace
-     * the current handler.
-     *
-     * @param listener an implementation of DownloadListener
-     */
     public void setDownloadListener(DownloadListener listener) {
-        checkThread();
-        mProvider.setDownloadListener(listener);
     }
 
-    /**
-     * Sets the chrome handler. This is an implementation of WebChromeClient for
-     * use in handling JavaScript dialogs, favicons, titles, and the progress.
-     * This will replace the current handler.
-     *
-     * @param client an implementation of WebChromeClient
-     * @see #getWebChromeClient
-     */
     public void setWebChromeClient(WebChromeClient client) {
-        checkThread();
-        mProvider.setWebChromeClient(client);
     }
 
-    /**
-     * Gets the chrome handler.
-     *
-     * @return the WebChromeClient, or {@code null} if not yet set
-     * @see #setWebChromeClient
-     */
-    @Nullable
-    public WebChromeClient getWebChromeClient() {
-        checkThread();
-        return mProvider.getWebChromeClient();
+    public void addJavascriptInterface(Object obj, String interfaceName) {
     }
 
-    /**
-     * Sets the Picture listener. This is an interface used to receive
-     * notifications of a new Picture.
-     *
-     * @param listener an implementation of WebView.PictureListener
-     * @deprecated This method is now obsolete.
-     */
-    @Deprecated
-    public void setPictureListener(PictureListener listener) {
-        checkThread();
-        mProvider.setPictureListener(listener);
-    }
-
-    /**
-     * Injects the supplied Java object into this WebView. The object is
-     * injected into the JavaScript context of the main frame, using the
-     * supplied name. This allows the Java object's methods to be
-     * accessed from JavaScript. For applications targeted to API
-     * level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
-     * and above, only public methods that are annotated with
-     * {@link android.webkit.JavascriptInterface} can be accessed from JavaScript.
-     * For applications targeted to API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN} or below,
-     * all public methods (including the inherited ones) can be accessed, see the
-     * important security note below for implications.
-     * <p> Note that injected objects will not appear in JavaScript until the page is next
-     * (re)loaded. JavaScript should be enabled before injecting the object. For example:
-     * <pre>
-     * class JsObject {
-     *    {@literal @}JavascriptInterface
-     *    public String toString() { return "injectedObject"; }
-     * }
-     * webview.getSettings().setJavaScriptEnabled(true);
-     * webView.addJavascriptInterface(new JsObject(), "injectedObject");
-     * webView.loadData("<!DOCTYPE html><title></title>", "text/html", null);
-     * webView.loadUrl("javascript:alert(injectedObject.toString())");</pre>
-     * <p>
-     * <strong>IMPORTANT:</strong>
-     * <ul>
-     * <li> This method can be used to allow JavaScript to control the host
-     * application. This is a powerful feature, but also presents a security
-     * risk for apps targeting {@link android.os.Build.VERSION_CODES#JELLY_BEAN} or earlier.
-     * Apps that target a version later than {@link android.os.Build.VERSION_CODES#JELLY_BEAN}
-     * are still vulnerable if the app runs on a device running Android earlier than 4.2.
-     * The most secure way to use this method is to target {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
-     * and to ensure the method is called only when running on Android 4.2 or later.
-     * With these older versions, JavaScript could use reflection to access an
-     * injected object's public fields. Use of this method in a WebView
-     * containing untrusted content could allow an attacker to manipulate the
-     * host application in unintended ways, executing Java code with the
-     * permissions of the host application. Use extreme care when using this
-     * method in a WebView which could contain untrusted content.</li>
-     * <li> JavaScript interacts with Java object on a private, background
-     * thread of this WebView. Care is therefore required to maintain thread
-     * safety.
-     * </li>
-     * <li> The Java object's fields are not accessible.</li>
-     * <li> For applications targeted to API level {@link android.os.Build.VERSION_CODES#LOLLIPOP}
-     * and above, methods of injected Java objects are enumerable from
-     * JavaScript.</li>
-     * </ul>
-     *
-     * @param object the Java object to inject into this WebView's JavaScript
-     *               context. {@code null} values are ignored.
-     * @param name the name used to expose the object in JavaScript
-     */
-    public void addJavascriptInterface(Object object, String name) {
-        checkThread();
-        mProvider.addJavascriptInterface(object, name);
-    }
-
-    /**
-     * Removes a previously injected Java object from this WebView. Note that
-     * the removal will not be reflected in JavaScript until the page is next
-     * (re)loaded. See {@link #addJavascriptInterface}.
-     *
-     * @param name the name used to expose the object in JavaScript
-     */
-    public void removeJavascriptInterface(@NonNull String name) {
-        checkThread();
-        mProvider.removeJavascriptInterface(name);
-    }
-
-    /**
-     * Creates a message channel to communicate with JS and returns the message
-     * ports that represent the endpoints of this message channel. The HTML5 message
-     * channel functionality is described
-     * <a href="https://html.spec.whatwg.org/multipage/comms.html#messagechannel">here
-     * </a>
-     *
-     * <p>The returned message channels are entangled and already in started state.
-     *
-     * @return the two message ports that form the message channel.
-     */
-    public WebMessagePort[] createWebMessageChannel() {
-        checkThread();
-        return mProvider.createWebMessageChannel();
-    }
-
-    /**
-     * Post a message to main frame. The embedded application can restrict the
-     * messages to a certain target origin. See
-     * <a href="https://html.spec.whatwg.org/multipage/comms.html#posting-messages">
-     * HTML5 spec</a> for how target origin can be used.
-     * <p>
-     * A target origin can be set as a wildcard ("*"). However this is not recommended.
-     * See the page above for security issues.
-     *
-     * @param message the WebMessage
-     * @param targetOrigin the target origin.
-     */
-    public void postWebMessage(WebMessage message, Uri targetOrigin) {
-        checkThread();
-        mProvider.postMessageToMainFrame(message, targetOrigin);
-    }
-
-    /**
-     * Gets the WebSettings object used to control the settings for this
-     * WebView.
-     *
-     * @return a WebSettings object that can be used to control this WebView's
-     *         settings
-     */
-    public WebSettings getSettings() {
-        checkThread();
-        return mProvider.getSettings();
-    }
-
-    /**
-     * Enables debugging of web contents (HTML / CSS / JavaScript)
-     * loaded into any WebViews of this application. This flag can be enabled
-     * in order to facilitate debugging of web layouts and JavaScript
-     * code running inside WebViews. Please refer to WebView documentation
-     * for the debugging guide.
-     *
-     * The default is {@code false}.
-     *
-     * @param enabled whether to enable web contents debugging
-     */
-    public static void setWebContentsDebuggingEnabled(boolean enabled) {
-        getFactory().getStatics().setWebContentsDebuggingEnabled(enabled);
-    }
-
-    /**
-     * Gets the list of currently loaded plugins.
-     *
-     * @return the list of currently loaded plugins
-     * @deprecated This was used for Gears, which has been deprecated.
-     * @hide
-     */
-    @Deprecated
-    public static synchronized PluginList getPluginList() {
-        return new PluginList();
-    }
-
-    /**
-     * @deprecated This was used for Gears, which has been deprecated.
-     * @hide
-     */
-    @Deprecated
-    public void refreshPlugins(boolean reloadOpenPages) {
-        checkThread();
-    }
-
-    /**
-     * Puts this WebView into text selection mode. Do not rely on this
-     * functionality; it will be deprecated in the future.
-     *
-     * @deprecated This method is now obsolete.
-     * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
-     */
-    @Deprecated
-    public void emulateShiftHeld() {
-        checkThread();
-    }
-
-    /**
-     * @deprecated WebView no longer needs to implement
-     * ViewGroup.OnHierarchyChangeListener.  This method does nothing now.
-     */
-    @Override
-    // Cannot add @hide as this can always be accessed via the interface.
-    @Deprecated
-    public void onChildViewAdded(View parent, View child) {}
-
-    /**
-     * @deprecated WebView no longer needs to implement
-     * ViewGroup.OnHierarchyChangeListener.  This method does nothing now.
-     */
-    @Override
-    // Cannot add @hide as this can always be accessed via the interface.
-    @Deprecated
-    public void onChildViewRemoved(View p, View child) {}
-
-    /**
-     * @deprecated WebView should not have implemented
-     * ViewTreeObserver.OnGlobalFocusChangeListener. This method does nothing now.
-     */
-    @Override
-    // Cannot add @hide as this can always be accessed via the interface.
-    @Deprecated
-    public void onGlobalFocusChanged(View oldFocus, View newFocus) {
-    }
-
-    /**
-     * @deprecated Only the default case, {@code true}, will be supported in a future version.
-     */
-    @Deprecated
-    public void setMapTrackballToArrowKeys(boolean setMap) {
-        checkThread();
-        mProvider.setMapTrackballToArrowKeys(setMap);
-    }
-
-
-    public void flingScroll(int vx, int vy) {
-        checkThread();
-        mProvider.flingScroll(vx, vy);
-    }
-
-    /**
-     * Gets the zoom controls for this WebView, as a separate View. The caller
-     * is responsible for inserting this View into the layout hierarchy.
-     * <p/>
-     * API level {@link android.os.Build.VERSION_CODES#CUPCAKE} introduced
-     * built-in zoom mechanisms for the WebView, as opposed to these separate
-     * zoom controls. The built-in mechanisms are preferred and can be enabled
-     * using {@link WebSettings#setBuiltInZoomControls}.
-     *
-     * @deprecated the built-in zoom mechanisms are preferred
-     * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN}
-     */
-    @Deprecated
     public View getZoomControls() {
-        checkThread();
-        return mProvider.getZoomControls();
+        return null;
     }
 
-    /**
-     * Gets whether this WebView can be zoomed in.
-     *
-     * @return {@code true} if this WebView can be zoomed in
-     *
-     * @deprecated This method is prone to inaccuracy due to race conditions
-     * between the web rendering and UI threads; prefer
-     * {@link WebViewClient#onScaleChanged}.
-     */
-    @Deprecated
-    public boolean canZoomIn() {
-        checkThread();
-        return mProvider.canZoomIn();
-    }
-
-    /**
-     * Gets whether this WebView can be zoomed out.
-     *
-     * @return {@code true} if this WebView can be zoomed out
-     *
-     * @deprecated This method is prone to inaccuracy due to race conditions
-     * between the web rendering and UI threads; prefer
-     * {@link WebViewClient#onScaleChanged}.
-     */
-    @Deprecated
-    public boolean canZoomOut() {
-        checkThread();
-        return mProvider.canZoomOut();
-    }
-
-    /**
-     * Performs a zoom operation in this WebView.
-     *
-     * @param zoomFactor the zoom factor to apply. The zoom factor will be clamped to the WebView's
-     * zoom limits. This value must be in the range 0.01 to 100.0 inclusive.
-     */
-    public void zoomBy(float zoomFactor) {
-        checkThread();
-        if (zoomFactor < 0.01)
-            throw new IllegalArgumentException("zoomFactor must be greater than 0.01.");
-        if (zoomFactor > 100.0)
-            throw new IllegalArgumentException("zoomFactor must be less than 100.");
-        mProvider.zoomBy(zoomFactor);
-    }
-
-    /**
-     * Performs zoom in in this WebView.
-     *
-     * @return {@code true} if zoom in succeeds, {@code false} if no zoom changes
-     */
     public boolean zoomIn() {
-        checkThread();
-        return mProvider.zoomIn();
+        return false;
     }
 
-    /**
-     * Performs zoom out in this WebView.
-     *
-     * @return {@code true} if zoom out succeeds, {@code false} if no zoom changes
-     */
     public boolean zoomOut() {
-        checkThread();
-        return mProvider.zoomOut();
-    }
-
-    /**
-     * @deprecated This method is now obsolete.
-     * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
-     */
-    @Deprecated
-    public void debugDump() {
-        checkThread();
-    }
-
-    /**
-     * See {@link ViewDebug.HierarchyHandler#dumpViewHierarchyWithProperties(BufferedWriter, int)}
-     * @hide
-     */
-    @Override
-    public void dumpViewHierarchyWithProperties(BufferedWriter out, int level) {
-        mProvider.dumpViewHierarchyWithProperties(out, level);
-    }
-
-    /**
-     * See {@link ViewDebug.HierarchyHandler#findHierarchyView(String, int)}
-     * @hide
-     */
-    @Override
-    public View findHierarchyView(String className, int hashCode) {
-        return mProvider.findHierarchyView(className, hashCode);
-    }
-
-    /** @hide */
-    @IntDef({
-        RENDERER_PRIORITY_WAIVED,
-        RENDERER_PRIORITY_BOUND,
-        RENDERER_PRIORITY_IMPORTANT
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface RendererPriority {}
-
-    /**
-     * The renderer associated with this WebView is bound with
-     * {@link Context#BIND_WAIVE_PRIORITY}. At this priority level
-     * {@link WebView} renderers will be strong targets for out of memory
-     * killing.
-     *
-     * Use with {@link #setRendererPriorityPolicy}.
-     */
-    public static final int RENDERER_PRIORITY_WAIVED = 0;
-    /**
-     * The renderer associated with this WebView is bound with
-     * the default priority for services.
-     *
-     * Use with {@link #setRendererPriorityPolicy}.
-     */
-    public static final int RENDERER_PRIORITY_BOUND = 1;
-    /**
-     * The renderer associated with this WebView is bound with
-     * {@link Context#BIND_IMPORTANT}.
-     *
-     * Use with {@link #setRendererPriorityPolicy}.
-     */
-    public static final int RENDERER_PRIORITY_IMPORTANT = 2;
-
-    /**
-     * Set the renderer priority policy for this {@link WebView}. The
-     * priority policy will be used to determine whether an out of
-     * process renderer should be considered to be a target for OOM
-     * killing.
-     *
-     * Because a renderer can be associated with more than one
-     * WebView, the final priority it is computed as the maximum of
-     * any attached WebViews. When a WebView is destroyed it will
-     * cease to be considerered when calculating the renderer
-     * priority. Once no WebViews remain associated with the renderer,
-     * the priority of the renderer will be reduced to
-     * {@link #RENDERER_PRIORITY_WAIVED}.
-     *
-     * The default policy is to set the priority to
-     * {@link #RENDERER_PRIORITY_IMPORTANT} regardless of visibility,
-     * and this should not be changed unless the caller also handles
-     * renderer crashes with
-     * {@link WebViewClient#onRenderProcessGone}. Any other setting
-     * will result in WebView renderers being killed by the system
-     * more aggressively than the application.
-     *
-     * @param rendererRequestedPriority the minimum priority at which
-     *        this WebView desires the renderer process to be bound.
-     * @param waivedWhenNotVisible if {@code true}, this flag specifies that
-     *        when this WebView is not visible, it will be treated as
-     *        if it had requested a priority of
-     *        {@link #RENDERER_PRIORITY_WAIVED}.
-     */
-    public void setRendererPriorityPolicy(
-            @RendererPriority int rendererRequestedPriority,
-            boolean waivedWhenNotVisible) {
-        mProvider.setRendererPriorityPolicy(rendererRequestedPriority, waivedWhenNotVisible);
-    }
-
-    /**
-     * Get the requested renderer priority for this WebView.
-     *
-     * @return the requested renderer priority policy.
-     */
-    @RendererPriority
-    public int getRendererRequestedPriority() {
-        return mProvider.getRendererRequestedPriority();
-    }
-
-    /**
-     * Return whether this WebView requests a priority of
-     * {@link #RENDERER_PRIORITY_WAIVED} when not visible.
-     *
-     * @return whether this WebView requests a priority of
-     * {@link #RENDERER_PRIORITY_WAIVED} when not visible.
-     */
-    public boolean getRendererPriorityWaivedWhenNotVisible() {
-        return mProvider.getRendererPriorityWaivedWhenNotVisible();
-    }
-
-    /**
-     * Sets the {@link TextClassifier} for this WebView.
-     */
-    public void setTextClassifier(@Nullable TextClassifier textClassifier) {
-        mProvider.setTextClassifier(textClassifier);
-    }
-
-    /**
-     * Returns the {@link TextClassifier} used by this WebView.
-     * If no TextClassifier has been set, this WebView uses the default set by the system.
-     */
-    @NonNull
-    public TextClassifier getTextClassifier() {
-        return mProvider.getTextClassifier();
-    }
-
-    //-------------------------------------------------------------------------
-    // Interface for WebView providers
-    //-------------------------------------------------------------------------
-
-    /**
-     * Gets the WebViewProvider. Used by providers to obtain the underlying
-     * implementation, e.g. when the application responds to
-     * WebViewClient.onCreateWindow() request.
-     *
-     * @hide WebViewProvider is not public API.
-     */
-    @SystemApi
-    public WebViewProvider getWebViewProvider() {
-        return mProvider;
-    }
-
-    /**
-     * Callback interface, allows the provider implementation to access non-public methods
-     * and fields, and make super-class calls in this WebView instance.
-     * @hide Only for use by WebViewProvider implementations
-     */
-    @SystemApi
-    public class PrivateAccess {
-        // ---- Access to super-class methods ----
-        public int super_getScrollBarStyle() {
-            return WebView.super.getScrollBarStyle();
-        }
-
-        public void super_scrollTo(int scrollX, int scrollY) {
-            WebView.super.scrollTo(scrollX, scrollY);
-        }
-
-        public void super_computeScroll() {
-            WebView.super.computeScroll();
-        }
-
-        public boolean super_onHoverEvent(MotionEvent event) {
-            return WebView.super.onHoverEvent(event);
-        }
-
-        public boolean super_performAccessibilityAction(int action, Bundle arguments) {
-            return WebView.super.performAccessibilityActionInternal(action, arguments);
-        }
-
-        public boolean super_performLongClick() {
-            return WebView.super.performLongClick();
-        }
-
-        public boolean super_setFrame(int left, int top, int right, int bottom) {
-            return WebView.super.setFrame(left, top, right, bottom);
-        }
-
-        public boolean super_dispatchKeyEvent(KeyEvent event) {
-            return WebView.super.dispatchKeyEvent(event);
-        }
-
-        public boolean super_onGenericMotionEvent(MotionEvent event) {
-            return WebView.super.onGenericMotionEvent(event);
-        }
-
-        public boolean super_requestFocus(int direction, Rect previouslyFocusedRect) {
-            return WebView.super.requestFocus(direction, previouslyFocusedRect);
-        }
-
-        public void super_setLayoutParams(ViewGroup.LayoutParams params) {
-            WebView.super.setLayoutParams(params);
-        }
-
-        public void super_startActivityForResult(Intent intent, int requestCode) {
-            WebView.super.startActivityForResult(intent, requestCode);
-        }
-
-        // ---- Access to non-public methods ----
-        public void overScrollBy(int deltaX, int deltaY,
-                int scrollX, int scrollY,
-                int scrollRangeX, int scrollRangeY,
-                int maxOverScrollX, int maxOverScrollY,
-                boolean isTouchEvent) {
-            WebView.this.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX, scrollRangeY,
-                    maxOverScrollX, maxOverScrollY, isTouchEvent);
-        }
-
-        public void awakenScrollBars(int duration) {
-            WebView.this.awakenScrollBars(duration);
-        }
-
-        public void awakenScrollBars(int duration, boolean invalidate) {
-            WebView.this.awakenScrollBars(duration, invalidate);
-        }
-
-        public float getVerticalScrollFactor() {
-            return WebView.this.getVerticalScrollFactor();
-        }
-
-        public float getHorizontalScrollFactor() {
-            return WebView.this.getHorizontalScrollFactor();
-        }
-
-        public void setMeasuredDimension(int measuredWidth, int measuredHeight) {
-            WebView.this.setMeasuredDimension(measuredWidth, measuredHeight);
-        }
-
-        public void onScrollChanged(int l, int t, int oldl, int oldt) {
-            WebView.this.onScrollChanged(l, t, oldl, oldt);
-        }
-
-        public int getHorizontalScrollbarHeight() {
-            return WebView.this.getHorizontalScrollbarHeight();
-        }
-
-        public void super_onDrawVerticalScrollBar(Canvas canvas, Drawable scrollBar,
-                int l, int t, int r, int b) {
-            WebView.super.onDrawVerticalScrollBar(canvas, scrollBar, l, t, r, b);
-        }
-
-        // ---- Access to (non-public) fields ----
-        /** Raw setter for the scroll X value, without invoking onScrollChanged handlers etc. */
-        public void setScrollXRaw(int scrollX) {
-            WebView.this.mScrollX = scrollX;
-        }
-
-        /** Raw setter for the scroll Y value, without invoking onScrollChanged handlers etc. */
-        public void setScrollYRaw(int scrollY) {
-            WebView.this.mScrollY = scrollY;
-        }
-
-    }
-
-    //-------------------------------------------------------------------------
-    // Package-private internal stuff
-    //-------------------------------------------------------------------------
-
-    // Only used by android.webkit.FindActionModeCallback.
-    void setFindDialogFindListener(FindListener listener) {
-        checkThread();
-        setupFindListenerIfNeeded();
-        mFindListener.mFindDialogFindListener = listener;
-    }
-
-    // Only used by android.webkit.FindActionModeCallback.
-    void notifyFindDialogDismissed() {
-        checkThread();
-        mProvider.notifyFindDialogDismissed();
-    }
-
-    //-------------------------------------------------------------------------
-    // Private internal stuff
-    //-------------------------------------------------------------------------
-
-    private WebViewProvider mProvider;
-
-    /**
-     * In addition to the FindListener that the user may set via the WebView.setFindListener
-     * API, FindActionModeCallback will register it's own FindListener. We keep them separate
-     * via this class so that the two FindListeners can potentially exist at once.
-     */
-    private class FindListenerDistributor implements FindListener {
-        private FindListener mFindDialogFindListener;
-        private FindListener mUserFindListener;
-
-        @Override
-        public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches,
-                boolean isDoneCounting) {
-            if (mFindDialogFindListener != null) {
-                mFindDialogFindListener.onFindResultReceived(activeMatchOrdinal, numberOfMatches,
-                        isDoneCounting);
-            }
-
-            if (mUserFindListener != null) {
-                mUserFindListener.onFindResultReceived(activeMatchOrdinal, numberOfMatches,
-                        isDoneCounting);
-            }
-        }
-    }
-    private FindListenerDistributor mFindListener;
-
-    private void setupFindListenerIfNeeded() {
-        if (mFindListener == null) {
-            mFindListener = new FindListenerDistributor();
-            mProvider.setFindListener(mFindListener);
-        }
-    }
-
-    private void ensureProviderCreated() {
-        checkThread();
-        if (mProvider == null) {
-            // As this can get called during the base class constructor chain, pass the minimum
-            // number of dependencies here; the rest are deferred to init().
-            mProvider = getFactory().createWebView(this, new PrivateAccess());
-        }
-    }
-
-    private static WebViewFactoryProvider getFactory() {
-        return WebViewFactory.getProvider();
-    }
-
-    private final Looper mWebViewThread = Looper.myLooper();
-
-    private void checkThread() {
-        // Ignore mWebViewThread == null because this can be called during in the super class
-        // constructor, before this class's own constructor has even started.
-        if (mWebViewThread != null && Looper.myLooper() != mWebViewThread) {
-            Throwable throwable = new Throwable(
-                    "A WebView method was called on thread '" +
-                    Thread.currentThread().getName() + "'. " +
-                    "All WebView methods must be called on the same thread. " +
-                    "(Expected Looper " + mWebViewThread + " called on " + Looper.myLooper() +
-                    ", FYI main Looper is " + Looper.getMainLooper() + ")");
-            Log.w(LOGTAG, Log.getStackTraceString(throwable));
-            StrictMode.onWebViewMethodCalledOnWrongThread(throwable);
-
-            if (sEnforceThreadChecking) {
-                throw new RuntimeException(throwable);
-            }
-        }
-    }
-
-    //-------------------------------------------------------------------------
-    // Override View methods
-    //-------------------------------------------------------------------------
-
-    // TODO: Add a test that enumerates all methods in ViewDelegte & ScrollDelegate, and ensures
-    // there's a corresponding override (or better, caller) for each of them in here.
-
-    @Override
-    protected void onAttachedToWindow() {
-        super.onAttachedToWindow();
-        mProvider.getViewDelegate().onAttachedToWindow();
-    }
-
-    /** @hide */
-    @Override
-    protected void onDetachedFromWindowInternal() {
-        mProvider.getViewDelegate().onDetachedFromWindow();
-        super.onDetachedFromWindowInternal();
-    }
-
-    /** @hide */
-    @Override
-    public void onMovedToDisplay(int displayId, Configuration config) {
-        mProvider.getViewDelegate().onMovedToDisplay(displayId, config);
-    }
-
-    @Override
-    public void setLayoutParams(ViewGroup.LayoutParams params) {
-        mProvider.getViewDelegate().setLayoutParams(params);
-    }
-
-    @Override
-    public void setOverScrollMode(int mode) {
-        super.setOverScrollMode(mode);
-        // This method may be called in the constructor chain, before the WebView provider is
-        // created.
-        ensureProviderCreated();
-        mProvider.getViewDelegate().setOverScrollMode(mode);
-    }
-
-    @Override
-    public void setScrollBarStyle(int style) {
-        mProvider.getViewDelegate().setScrollBarStyle(style);
-        super.setScrollBarStyle(style);
-    }
-
-    @Override
-    protected int computeHorizontalScrollRange() {
-        return mProvider.getScrollDelegate().computeHorizontalScrollRange();
-    }
-
-    @Override
-    protected int computeHorizontalScrollOffset() {
-        return mProvider.getScrollDelegate().computeHorizontalScrollOffset();
-    }
-
-    @Override
-    protected int computeVerticalScrollRange() {
-        return mProvider.getScrollDelegate().computeVerticalScrollRange();
-    }
-
-    @Override
-    protected int computeVerticalScrollOffset() {
-        return mProvider.getScrollDelegate().computeVerticalScrollOffset();
-    }
-
-    @Override
-    protected int computeVerticalScrollExtent() {
-        return mProvider.getScrollDelegate().computeVerticalScrollExtent();
-    }
-
-    @Override
-    public void computeScroll() {
-        mProvider.getScrollDelegate().computeScroll();
-    }
-
-    @Override
-    public boolean onHoverEvent(MotionEvent event) {
-        return mProvider.getViewDelegate().onHoverEvent(event);
-    }
-
-    @Override
-    public boolean onTouchEvent(MotionEvent event) {
-        return mProvider.getViewDelegate().onTouchEvent(event);
-    }
-
-    @Override
-    public boolean onGenericMotionEvent(MotionEvent event) {
-        return mProvider.getViewDelegate().onGenericMotionEvent(event);
-    }
-
-    @Override
-    public boolean onTrackballEvent(MotionEvent event) {
-        return mProvider.getViewDelegate().onTrackballEvent(event);
-    }
-
-    @Override
-    public boolean onKeyDown(int keyCode, KeyEvent event) {
-        return mProvider.getViewDelegate().onKeyDown(keyCode, event);
-    }
-
-    @Override
-    public boolean onKeyUp(int keyCode, KeyEvent event) {
-        return mProvider.getViewDelegate().onKeyUp(keyCode, event);
-    }
-
-    @Override
-    public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
-        return mProvider.getViewDelegate().onKeyMultiple(keyCode, repeatCount, event);
-    }
-
-    /*
-    TODO: These are not currently implemented in WebViewClassic, but it seems inconsistent not
-    to be delegating them too.
-
-    @Override
-    public boolean onKeyPreIme(int keyCode, KeyEvent event) {
-        return mProvider.getViewDelegate().onKeyPreIme(keyCode, event);
-    }
-    @Override
-    public boolean onKeyLongPress(int keyCode, KeyEvent event) {
-        return mProvider.getViewDelegate().onKeyLongPress(keyCode, event);
-    }
-    @Override
-    public boolean onKeyShortcut(int keyCode, KeyEvent event) {
-        return mProvider.getViewDelegate().onKeyShortcut(keyCode, event);
-    }
-    */
-
-    @Override
-    public AccessibilityNodeProvider getAccessibilityNodeProvider() {
-        AccessibilityNodeProvider provider =
-                mProvider.getViewDelegate().getAccessibilityNodeProvider();
-        return provider == null ? super.getAccessibilityNodeProvider() : provider;
-    }
-
-    @Deprecated
-    @Override
-    public boolean shouldDelayChildPressedState() {
-        return mProvider.getViewDelegate().shouldDelayChildPressedState();
-    }
-
-    @Override
-    public CharSequence getAccessibilityClassName() {
-        return WebView.class.getName();
-    }
-
-    @Override
-    public void onProvideVirtualStructure(ViewStructure structure) {
-        mProvider.getViewDelegate().onProvideVirtualStructure(structure);
-    }
-
-    /**
-     * {@inheritDoc}
-     *
-     * <p>The {@link ViewStructure} traditionally represents a {@link View}, while for web pages
-     * it represent HTML nodes. Hence, it's necessary to "map" the HTML properties in a way that is
-     * understood by the {@link android.service.autofill.AutofillService} implementations:
-     *
-     * <ol>
-     *   <li>Only the HTML nodes inside a {@code FORM} are generated.
-     *   <li>The source of the HTML is set using {@link ViewStructure#setWebDomain(String)} in the
-     *   node representing the WebView.
-     *   <li>If a web page has multiple {@code FORM}s, only the data for the current form is
-     *   represented&mdash;if the user taps a field from another form, then the current autofill
-     *   context is canceled (by calling {@link android.view.autofill.AutofillManager#cancel()} and
-     *   a new context is created for that {@code FORM}.
-     *   <li>Similarly, if the page has {@code IFRAME} nodes, they are not initially represented in
-     *   the view structure until the user taps a field from a {@code FORM} inside the
-     *   {@code IFRAME}, in which case it would be treated the same way as multiple forms described
-     *   above, except that the {@link ViewStructure#setWebDomain(String) web domain} of the
-     *   {@code FORM} contains the {@code src} attribute from the {@code IFRAME} node.
-     *   <li>The W3C autofill field ({@code autocomplete} tag attribute) maps to
-     *   {@link ViewStructure#setAutofillHints(String[])}.
-     *   <li>If the view is editable, the {@link ViewStructure#setAutofillType(int)} and
-     *   {@link ViewStructure#setAutofillValue(AutofillValue)} must be set.
-     *   <li>The {@code placeholder} attribute maps to {@link ViewStructure#setHint(CharSequence)}.
-     *   <li>Other HTML attributes can be represented through
-     *   {@link ViewStructure#setHtmlInfo(android.view.ViewStructure.HtmlInfo)}.
-     * </ol>
-     *
-     * <p>If the WebView implementation can determine that the value of a field was set statically
-     * (for example, not through Javascript), it should also call
-     * {@code structure.setDataIsSensitive(false)}.
-     *
-     * <p>For example, an HTML form with 2 fields for username and password:
-     *
-     * <pre class="prettyprint">
-     *    &lt;input type="text" name="username" id="user" value="Type your username" autocomplete="username" placeholder="Email or username"&gt;
-     *    &lt;input type="password" name="password" id="pass" autocomplete="current-password" placeholder="Password"&gt;
-     * </pre>
-     *
-     * <p>Would map to:
-     *
-     * <pre class="prettyprint">
-     *     int index = structure.addChildCount(2);
-     *     ViewStructure username = structure.newChild(index);
-     *     username.setAutofillId(structure.getAutofillId(), 1); // id 1 - first child
-     *     username.setAutofillHints("username");
-     *     username.setHtmlInfo(username.newHtmlInfoBuilder("input")
-     *         .addAttribute("type", "text")
-     *         .addAttribute("name", "username")
-     *         .build());
-     *     username.setHint("Email or username");
-     *     username.setAutofillType(View.AUTOFILL_TYPE_TEXT);
-     *     username.setAutofillValue(AutofillValue.forText("Type your username"));
-     *     // Value of the field is not sensitive because it was created statically and not changed.
-     *     username.setDataIsSensitive(false);
-     *
-     *     ViewStructure password = structure.newChild(index + 1);
-     *     username.setAutofillId(structure, 2); // id 2 - second child
-     *     password.setAutofillHints("current-password");
-     *     password.setHtmlInfo(password.newHtmlInfoBuilder("input")
-     *         .addAttribute("type", "password")
-     *         .addAttribute("name", "password")
-     *         .build());
-     *     password.setHint("Password");
-     *     password.setAutofillType(View.AUTOFILL_TYPE_TEXT);
-     * </pre>
-     */
-    @Override
-    public void onProvideAutofillVirtualStructure(ViewStructure structure, int flags) {
-        mProvider.getViewDelegate().onProvideAutofillVirtualStructure(structure, flags);
-    }
-
-    @Override
-    public void autofill(SparseArray<AutofillValue>values) {
-        mProvider.getViewDelegate().autofill(values);
-    }
-
-    /** @hide */
-    @Override
-    public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
-        super.onInitializeAccessibilityNodeInfoInternal(info);
-        mProvider.getViewDelegate().onInitializeAccessibilityNodeInfo(info);
-    }
-
-    /** @hide */
-    @Override
-    public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
-        super.onInitializeAccessibilityEventInternal(event);
-        mProvider.getViewDelegate().onInitializeAccessibilityEvent(event);
-    }
-
-    /** @hide */
-    @Override
-    public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
-        return mProvider.getViewDelegate().performAccessibilityAction(action, arguments);
-    }
-
-    /** @hide */
-    @Override
-    protected void onDrawVerticalScrollBar(Canvas canvas, Drawable scrollBar,
-            int l, int t, int r, int b) {
-        mProvider.getViewDelegate().onDrawVerticalScrollBar(canvas, scrollBar, l, t, r, b);
-    }
-
-    @Override
-    protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
-        mProvider.getViewDelegate().onOverScrolled(scrollX, scrollY, clampedX, clampedY);
-    }
-
-    @Override
-    protected void onWindowVisibilityChanged(int visibility) {
-        super.onWindowVisibilityChanged(visibility);
-        mProvider.getViewDelegate().onWindowVisibilityChanged(visibility);
-    }
-
-    @Override
-    protected void onDraw(Canvas canvas) {
-        mProvider.getViewDelegate().onDraw(canvas);
-    }
-
-    @Override
-    public boolean performLongClick() {
-        return mProvider.getViewDelegate().performLongClick();
-    }
-
-    @Override
-    protected void onConfigurationChanged(Configuration newConfig) {
-        mProvider.getViewDelegate().onConfigurationChanged(newConfig);
-    }
-
-    /**
-     * Creates a new InputConnection for an InputMethod to interact with the WebView.
-     * This is similar to {@link View#onCreateInputConnection} but note that WebView
-     * calls InputConnection methods on a thread other than the UI thread.
-     * If these methods are overridden, then the overriding methods should respect
-     * thread restrictions when calling View methods or accessing data.
-     */
-    @Override
-    public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
-        return mProvider.getViewDelegate().onCreateInputConnection(outAttrs);
-    }
-
-    @Override
-    public boolean onDragEvent(DragEvent event) {
-        return mProvider.getViewDelegate().onDragEvent(event);
-    }
-
-    @Override
-    protected void onVisibilityChanged(View changedView, int visibility) {
-        super.onVisibilityChanged(changedView, visibility);
-        // This method may be called in the constructor chain, before the WebView provider is
-        // created.
-        ensureProviderCreated();
-        mProvider.getViewDelegate().onVisibilityChanged(changedView, visibility);
-    }
-
-    @Override
-    public void onWindowFocusChanged(boolean hasWindowFocus) {
-        mProvider.getViewDelegate().onWindowFocusChanged(hasWindowFocus);
-        super.onWindowFocusChanged(hasWindowFocus);
-    }
-
-    @Override
-    protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
-        mProvider.getViewDelegate().onFocusChanged(focused, direction, previouslyFocusedRect);
-        super.onFocusChanged(focused, direction, previouslyFocusedRect);
-    }
-
-    /** @hide */
-    @Override
-    protected boolean setFrame(int left, int top, int right, int bottom) {
-        return mProvider.getViewDelegate().setFrame(left, top, right, bottom);
-    }
-
-    @Override
-    protected void onSizeChanged(int w, int h, int ow, int oh) {
-        super.onSizeChanged(w, h, ow, oh);
-        mProvider.getViewDelegate().onSizeChanged(w, h, ow, oh);
-    }
-
-    @Override
-    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
-        super.onScrollChanged(l, t, oldl, oldt);
-        mProvider.getViewDelegate().onScrollChanged(l, t, oldl, oldt);
-    }
-
-    @Override
-    public boolean dispatchKeyEvent(KeyEvent event) {
-        return mProvider.getViewDelegate().dispatchKeyEvent(event);
-    }
-
-    @Override
-    public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
-        return mProvider.getViewDelegate().requestFocus(direction, previouslyFocusedRect);
-    }
-
-    @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-        mProvider.getViewDelegate().onMeasure(widthMeasureSpec, heightMeasureSpec);
-    }
-
-    @Override
-    public boolean requestChildRectangleOnScreen(View child, Rect rect, boolean immediate) {
-        return mProvider.getViewDelegate().requestChildRectangleOnScreen(child, rect, immediate);
-    }
-
-    @Override
-    public void setBackgroundColor(int color) {
-        mProvider.getViewDelegate().setBackgroundColor(color);
-    }
-
-    @Override
-    public void setLayerType(int layerType, Paint paint) {
-        super.setLayerType(layerType, paint);
-        mProvider.getViewDelegate().setLayerType(layerType, paint);
-    }
-
-    @Override
-    protected void dispatchDraw(Canvas canvas) {
-        mProvider.getViewDelegate().preDispatchDraw(canvas);
-        super.dispatchDraw(canvas);
-    }
-
-    @Override
-    public void onStartTemporaryDetach() {
-        super.onStartTemporaryDetach();
-        mProvider.getViewDelegate().onStartTemporaryDetach();
-    }
-
-    @Override
-    public void onFinishTemporaryDetach() {
-        super.onFinishTemporaryDetach();
-        mProvider.getViewDelegate().onFinishTemporaryDetach();
-    }
-
-    @Override
-    public Handler getHandler() {
-        return mProvider.getViewDelegate().getHandler(super.getHandler());
-    }
-
-    @Override
-    public View findFocus() {
-        return mProvider.getViewDelegate().findFocus(super.findFocus());
-    }
-
-    /**
-     * If WebView has already been loaded into the current process this method will return the
-     * package that was used to load it. Otherwise, the package that would be used if the WebView
-     * was loaded right now will be returned; this does not cause WebView to be loaded, so this
-     * information may become outdated at any time.
-     * The WebView package changes either when the current WebView package is updated, disabled, or
-     * uninstalled. It can also be changed through a Developer Setting.
-     * If the WebView package changes, any app process that has loaded WebView will be killed. The
-     * next time the app starts and loads WebView it will use the new WebView package instead.
-     * @return the current WebView package, or {@code null} if there is none.
-     */
-    @Nullable
-    public static PackageInfo getCurrentWebViewPackage() {
-        PackageInfo webviewPackage = WebViewFactory.getLoadedPackageInfo();
-        if (webviewPackage != null) {
-            return webviewPackage;
-        }
-
-        IWebViewUpdateService service = WebViewFactory.getUpdateService();
-        if (service == null) {
-            return null;
-        }
-        try {
-            return service.getCurrentWebViewPackage();
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-     * Receive the result from a previous call to {@link #startActivityForResult(Intent, int)}.
-     *
-     * @param requestCode The integer request code originally supplied to
-     *                    startActivityForResult(), allowing you to identify who this
-     *                    result came from.
-     * @param resultCode The integer result code returned by the child activity
-     *                   through its setResult().
-     * @param data An Intent, which can return result data to the caller
-     *               (various data can be attached to Intent "extras").
-     * @hide
-     */
-    @Override
-    public void onActivityResult(int requestCode, int resultCode, Intent data) {
-        mProvider.getViewDelegate().onActivityResult(requestCode, resultCode, data);
-    }
-
-    /** @hide */
-    @Override
-    protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
-        super.encodeProperties(encoder);
-
-        checkThread();
-        encoder.addProperty("webview:contentHeight", mProvider.getContentHeight());
-        encoder.addProperty("webview:contentWidth", mProvider.getContentWidth());
-        encoder.addProperty("webview:scale", mProvider.getScale());
-        encoder.addProperty("webview:title", mProvider.getTitle());
-        encoder.addProperty("webview:url", mProvider.getUrl());
-        encoder.addProperty("webview:originalUrl", mProvider.getOriginalUrl());
+        return false;
     }
 }
diff --git a/android/widget/Editor.java b/android/widget/Editor.java
index afd1188..384f4f8 100644
--- a/android/widget/Editor.java
+++ b/android/widget/Editor.java
@@ -165,7 +165,7 @@
     private static final int MENU_ITEM_ORDER_PASTE_AS_PLAIN_TEXT = 11;
     private static final int MENU_ITEM_ORDER_PROCESS_TEXT_INTENT_ACTIONS_START = 100;
 
-    private static final float MAGNIFIER_ZOOM = 1.5f;
+    private static final float MAGNIFIER_ZOOM = 1.25f;
     @IntDef({MagnifierHandleTrigger.SELECTION_START,
             MagnifierHandleTrigger.SELECTION_END,
             MagnifierHandleTrigger.INSERTION})
@@ -3888,7 +3888,7 @@
                 if (selected == null || selected.isEmpty()) {
                     menu.add(Menu.NONE, TextView.ID_AUTOFILL, MENU_ITEM_ORDER_AUTOFILL,
                             com.android.internal.R.string.autofill)
-                            .setShowAsAction(MenuItem.SHOW_AS_OVERFLOW_ALWAYS);
+                            .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
                 }
             }
 
@@ -4539,23 +4539,21 @@
             final Layout layout = mTextView.getLayout();
             final int lineNumber = layout.getLineForOffset(offset);
             // Horizontally snap to character offset.
-            final float xPosInView = getHorizontal(mTextView.getLayout(), offset);
+            final float xPosInView = getHorizontal(mTextView.getLayout(), offset)
+                    + mTextView.getTotalPaddingLeft() - mTextView.getScrollX();
             // Vertically snap to middle of current line.
             final float yPosInView = (mTextView.getLayout().getLineTop(lineNumber)
-                    + mTextView.getLayout().getLineBottom(lineNumber)) / 2.0f;
-            final int[] coordinatesOnScreen = new int[2];
-            mTextView.getLocationOnScreen(coordinatesOnScreen);
-            final float centerXOnScreen = xPosInView + mTextView.getTotalPaddingLeft()
-                    - mTextView.getScrollX() + coordinatesOnScreen[0];
-            final float centerYOnScreen = yPosInView + mTextView.getTotalPaddingTop()
-                    - mTextView.getScrollY() + coordinatesOnScreen[1];
+                    + mTextView.getLayout().getLineBottom(lineNumber)) / 2.0f
+                    + mTextView.getTotalPaddingTop() - mTextView.getScrollY();
 
-            mMagnifier.show(centerXOnScreen, centerYOnScreen, MAGNIFIER_ZOOM);
+            suspendBlink();
+            mMagnifier.show(xPosInView, yPosInView, MAGNIFIER_ZOOM);
         }
 
         protected final void dismissMagnifier() {
             if (mMagnifier != null) {
                 mMagnifier.dismiss();
+                resumeBlink();
             }
         }
 
diff --git a/android/widget/RemoteViews.java b/android/widget/RemoteViews.java
index 1b26f8e..631f388 100644
--- a/android/widget/RemoteViews.java
+++ b/android/widget/RemoteViews.java
@@ -131,7 +131,7 @@
      *
      * @hide
      */
-    private ApplicationInfo mApplication;
+    public ApplicationInfo mApplication;
 
     /**
      * The resource ID of the layout file. (Added to the parcel)
@@ -1519,8 +1519,7 @@
 
         @Override
         public boolean hasSameAppInfo(ApplicationInfo parentInfo) {
-            return mNestedViews.mApplication.packageName.equals(parentInfo.packageName)
-                    && mNestedViews.mApplication.uid == parentInfo.uid;
+            return mNestedViews.hasSameAppInfo(parentInfo);
         }
 
         @Override
@@ -2138,8 +2137,7 @@
         if (landscape == null || portrait == null) {
             throw new RuntimeException("Both RemoteViews must be non-null");
         }
-        if (landscape.mApplication.uid != portrait.mApplication.uid
-                || !landscape.mApplication.packageName.equals(portrait.mApplication.packageName)) {
+        if (!landscape.hasSameAppInfo(portrait.mApplication)) {
             throw new RuntimeException("Both RemoteViews must share the same package and user");
         }
         mApplication = portrait.mApplication;
@@ -2653,7 +2651,11 @@
     /**
      * Equivalent to calling
      * {@link android.view.View#setOnClickListener(android.view.View.OnClickListener)}
-     * to launch the provided {@link PendingIntent}.
+     * to launch the provided {@link PendingIntent}. The source bounds
+     * ({@link Intent#getSourceBounds()}) of the intent will be set to the bounds of the clicked
+     * view in screen space.
+     * Note that any activity options associated with the pendingIntent may get overridden
+     * before starting the intent.
      *
      * When setting the on-click action of items within collections (eg. {@link ListView},
      * {@link StackView} etc.), this method will not work. Instead, use {@link
@@ -3551,6 +3553,15 @@
     }
 
     /**
+     * Returns true if the {@link #mApplication} is same as the provided info.
+     *
+     * @hide
+     */
+    public boolean hasSameAppInfo(ApplicationInfo info) {
+        return mApplication.packageName.equals(info.packageName) && mApplication.uid == info.uid;
+    }
+
+    /**
      * Parcelable.Creator that instantiates RemoteViews objects
      */
     public static final Parcelable.Creator<RemoteViews> CREATOR = new Parcelable.Creator<RemoteViews>() {
diff --git a/android/widget/RemoteViewsAdapter.java b/android/widget/RemoteViewsAdapter.java
index 0968652..e5ae0ca 100644
--- a/android/widget/RemoteViewsAdapter.java
+++ b/android/widget/RemoteViewsAdapter.java
@@ -16,11 +16,15 @@
 
 package android.widget;
 
-import android.Manifest;
+import android.annotation.WorkerThread;
+import android.app.IServiceConnection;
 import android.appwidget.AppWidgetHostView;
 import android.appwidget.AppWidgetManager;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.ApplicationInfo;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
@@ -28,7 +32,6 @@
 import android.os.Message;
 import android.os.RemoteException;
 import android.util.Log;
-import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
 import android.util.SparseIntArray;
@@ -38,7 +41,6 @@
 import android.view.ViewGroup;
 import android.widget.RemoteViews.OnClickHandler;
 
-import com.android.internal.widget.IRemoteViewsAdapterConnection;
 import com.android.internal.widget.IRemoteViewsFactory;
 
 import java.lang.ref.WeakReference;
@@ -48,52 +50,33 @@
 import java.util.concurrent.Executor;
 
 /**
- * An adapter to a RemoteViewsService which fetches and caches RemoteViews
- * to be later inflated as child views.
+ * An adapter to a RemoteViewsService which fetches and caches RemoteViews to be later inflated as
+ * child views.
+ *
+ * The adapter runs in the host process, typically a Launcher app.
+ *
+ * It makes a service connection to the {@link RemoteViewsService} running in the
+ * AppWidgetsProvider's process. This connection is made on a background thread (and proxied via
+ * the platform to get the bind permissions) and all interaction with the service is done on the
+ * background thread.
+ *
+ * On first bind, the adapter will load can cache the RemoteViews locally. Afterwards the
+ * connection is only made when new RemoteViews are required.
+ * @hide
  */
-/** @hide */
 public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback {
-    private static final String MULTI_USER_PERM = Manifest.permission.INTERACT_ACROSS_USERS_FULL;
 
     private static final String TAG = "RemoteViewsAdapter";
 
     // The max number of items in the cache
-    private static final int sDefaultCacheSize = 40;
+    private static final int DEFAULT_CACHE_SIZE = 40;
     // The delay (in millis) to wait until attempting to unbind from a service after a request.
     // This ensures that we don't stay continually bound to the service and that it can be destroyed
     // if we need the memory elsewhere in the system.
-    private static final int sUnbindServiceDelay = 5000;
+    private static final int UNBIND_SERVICE_DELAY = 5000;
 
     // Default height for the default loading view, in case we cannot get inflate the first view
-    private static final int sDefaultLoadingViewHeight = 50;
-
-    // Type defs for controlling different messages across the main and worker message queues
-    private static final int sDefaultMessageType = 0;
-    private static final int sUnbindServiceMessageType = 1;
-
-    private final Context mContext;
-    private final Intent mIntent;
-    private final int mAppWidgetId;
-    private final Executor mAsyncViewLoadExecutor;
-
-    private RemoteViewsAdapterServiceConnection mServiceConnection;
-    private WeakReference<RemoteAdapterConnectionCallback> mCallback;
-    private OnClickHandler mRemoteViewsOnClickHandler;
-    private final FixedSizeRemoteViewsCache mCache;
-    private int mVisibleWindowLowerBound;
-    private int mVisibleWindowUpperBound;
-
-    // A flag to determine whether we should notify data set changed after we connect
-    private boolean mNotifyDataSetChangedAfterOnServiceConnected = false;
-
-    // The set of requested views that are to be notified when the associated RemoteViews are
-    // loaded.
-    private RemoteViewsFrameLayoutRefSet mRequestedViews;
-
-    private HandlerThread mWorkerThread;
-    // items may be interrupted within the normally processed queues
-    private Handler mWorkerQueue;
-    private Handler mMainQueue;
+    private static final int DEFAULT_LOADING_VIEW_HEIGHT = 50;
 
     // We cache the FixedSizeRemoteViewsCaches across orientation. These are the related data
     // structures;
@@ -110,11 +93,37 @@
     // duration, the cache is dropped.
     private static final int REMOTE_VIEWS_CACHE_DURATION = 5000;
 
+    private final Context mContext;
+    private final Intent mIntent;
+    private final int mAppWidgetId;
+    private final Executor mAsyncViewLoadExecutor;
+
+    private OnClickHandler mRemoteViewsOnClickHandler;
+    private final FixedSizeRemoteViewsCache mCache;
+    private int mVisibleWindowLowerBound;
+    private int mVisibleWindowUpperBound;
+
+    // The set of requested views that are to be notified when the associated RemoteViews are
+    // loaded.
+    private RemoteViewsFrameLayoutRefSet mRequestedViews;
+
+    private final HandlerThread mWorkerThread;
+    // items may be interrupted within the normally processed queues
+    private final Handler mMainHandler;
+    private final RemoteServiceHandler mServiceHandler;
+    private final RemoteAdapterConnectionCallback mCallback;
+
     // Used to indicate to the AdapterView that it can use this Adapter immediately after
     // construction (happens when we have a cached FixedSizeRemoteViewsCache).
     private boolean mDataReady = false;
 
     /**
+     * USed to dedupe {@link RemoteViews#mApplication} so that we do not hold on to
+     * multiple copies of the same ApplicationInfo object.
+     */
+    private ApplicationInfo mLastRemoteViewAppInfo;
+
+    /**
      * An interface for the RemoteAdapter to notify other classes when adapters
      * are actually connected to/disconnected from their actual services.
      */
@@ -151,154 +160,192 @@
         }
     }
 
+    static final int MSG_REQUEST_BIND = 1;
+    static final int MSG_NOTIFY_DATA_SET_CHANGED = 2;
+    static final int MSG_LOAD_NEXT_ITEM = 3;
+    static final int MSG_UNBIND_SERVICE = 4;
+
+    private static final int MSG_MAIN_HANDLER_COMMIT_METADATA = 1;
+    private static final int MSG_MAIN_HANDLER_SUPER_NOTIFY_DATA_SET_CHANGED = 2;
+    private static final int MSG_MAIN_HANDLER_REMOTE_ADAPTER_CONNECTED = 3;
+    private static final int MSG_MAIN_HANDLER_REMOTE_ADAPTER_DISCONNECTED = 4;
+    private static final int MSG_MAIN_HANDLER_REMOTE_VIEWS_LOADED = 5;
+
     /**
-     * The service connection that gets populated when the RemoteViewsService is
-     * bound.  This must be a static inner class to ensure that no references to the outer
-     * RemoteViewsAdapter instance is retained (this would prevent the RemoteViewsAdapter from being
-     * garbage collected, and would cause us to leak activities due to the caching mechanism for
-     * FrameLayouts in the adapter).
+     * Handler for various interactions with the {@link RemoteViewsService}.
      */
-    private static class RemoteViewsAdapterServiceConnection extends
-            IRemoteViewsAdapterConnection.Stub {
-        private boolean mIsConnected;
-        private boolean mIsConnecting;
-        private WeakReference<RemoteViewsAdapter> mAdapter;
+    private static class RemoteServiceHandler extends Handler implements ServiceConnection {
+
+        private final WeakReference<RemoteViewsAdapter> mAdapter;
+        private final Context mContext;
+
         private IRemoteViewsFactory mRemoteViewsFactory;
 
-        public RemoteViewsAdapterServiceConnection(RemoteViewsAdapter adapter) {
-            mAdapter = new WeakReference<RemoteViewsAdapter>(adapter);
+        // The last call to notifyDataSetChanged didn't succeed, try again on next service bind.
+        private boolean mNotifyDataSetChangedPending = false;
+        private boolean mBindRequested = false;
+
+        RemoteServiceHandler(Looper workerLooper, RemoteViewsAdapter adapter, Context context) {
+            super(workerLooper);
+            mAdapter = new WeakReference<>(adapter);
+            mContext = context;
         }
 
-        public synchronized void bind(Context context, int appWidgetId, Intent intent) {
-            if (!mIsConnecting) {
-                try {
-                    RemoteViewsAdapter adapter;
-                    final AppWidgetManager mgr = AppWidgetManager.getInstance(context);
-                    if ((adapter = mAdapter.get()) != null) {
-                        mgr.bindRemoteViewsService(context.getOpPackageName(), appWidgetId,
-                                intent, asBinder());
-                    } else {
-                        Slog.w(TAG, "bind: adapter was null");
-                    }
-                    mIsConnecting = true;
-                } catch (Exception e) {
-                    Log.e("RVAServiceConnection", "bind(): " + e.getMessage());
-                    mIsConnecting = false;
-                    mIsConnected = false;
-                }
-            }
-        }
-
-        public synchronized void unbind(Context context, int appWidgetId, Intent intent) {
-            try {
-                RemoteViewsAdapter adapter;
-                final AppWidgetManager mgr = AppWidgetManager.getInstance(context);
-                if ((adapter = mAdapter.get()) != null) {
-                    mgr.unbindRemoteViewsService(context.getOpPackageName(), appWidgetId, intent);
-                } else {
-                    Slog.w(TAG, "unbind: adapter was null");
-                }
-                mIsConnecting = false;
-            } catch (Exception e) {
-                Log.e("RVAServiceConnection", "unbind(): " + e.getMessage());
-                mIsConnecting = false;
-                mIsConnected = false;
-            }
-        }
-
-        public synchronized void onServiceConnected(IBinder service) {
+        @Override
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            // This is called on the same thread.
             mRemoteViewsFactory = IRemoteViewsFactory.Stub.asInterface(service);
+            enqueueDeferredUnbindServiceMessage();
 
-            // Remove any deferred unbind messages
-            final RemoteViewsAdapter adapter = mAdapter.get();
-            if (adapter == null) return;
+            RemoteViewsAdapter adapter = mAdapter.get();
+            if (adapter == null) {
+                return;
+            }
 
-            // Queue up work that we need to do for the callback to run
-            adapter.mWorkerQueue.post(new Runnable() {
-                @Override
-                public void run() {
-                    if (adapter.mNotifyDataSetChangedAfterOnServiceConnected) {
-                        // Handle queued notifyDataSetChanged() if necessary
-                        adapter.onNotifyDataSetChanged();
-                    } else {
-                        IRemoteViewsFactory factory =
-                            adapter.mServiceConnection.getRemoteViewsFactory();
-                        try {
-                            if (!factory.isCreated()) {
-                                // We only call onDataSetChanged() if this is the factory was just
-                                // create in response to this bind
-                                factory.onDataSetChanged();
-                            }
-                        } catch (RemoteException e) {
-                            Log.e(TAG, "Error notifying factory of data set changed in " +
-                                        "onServiceConnected(): " + e.getMessage());
-
-                            // Return early to prevent anything further from being notified
-                            // (effectively nothing has changed)
-                            return;
-                        } catch (RuntimeException e) {
-                            Log.e(TAG, "Error notifying factory of data set changed in " +
-                                    "onServiceConnected(): " + e.getMessage());
-                        }
-
-                        // Request meta data so that we have up to date data when calling back to
-                        // the remote adapter callback
-                        adapter.updateTemporaryMetaData();
-
-                        // Notify the host that we've connected
-                        adapter.mMainQueue.post(new Runnable() {
-                            @Override
-                            public void run() {
-                                synchronized (adapter.mCache) {
-                                    adapter.mCache.commitTemporaryMetaData();
-                                }
-
-                                final RemoteAdapterConnectionCallback callback =
-                                    adapter.mCallback.get();
-                                if (callback != null) {
-                                    callback.onRemoteAdapterConnected();
-                                }
-                            }
-                        });
-                    }
-
-                    // Enqueue unbind message
-                    adapter.enqueueDeferredUnbindServiceMessage();
-                    mIsConnected = true;
-                    mIsConnecting = false;
+            if (mNotifyDataSetChangedPending) {
+                mNotifyDataSetChangedPending = false;
+                Message msg = Message.obtain(this, MSG_NOTIFY_DATA_SET_CHANGED);
+                handleMessage(msg);
+                msg.recycle();
+            } else {
+                if (!sendNotifyDataSetChange(false)) {
+                    return;
                 }
-            });
+
+                // Request meta data so that we have up to date data when calling back to
+                // the remote adapter callback
+                adapter.updateTemporaryMetaData(mRemoteViewsFactory);
+                adapter.mMainHandler.sendEmptyMessage(MSG_MAIN_HANDLER_COMMIT_METADATA);
+                adapter.mMainHandler.sendEmptyMessage(MSG_MAIN_HANDLER_REMOTE_ADAPTER_CONNECTED);
+            }
         }
 
-        public synchronized void onServiceDisconnected() {
-            mIsConnected = false;
-            mIsConnecting = false;
+        @Override
+        public void onServiceDisconnected(ComponentName name) {
             mRemoteViewsFactory = null;
+            RemoteViewsAdapter adapter = mAdapter.get();
+            if (adapter != null) {
+                adapter.mMainHandler.sendEmptyMessage(MSG_MAIN_HANDLER_REMOTE_ADAPTER_DISCONNECTED);
+            }
+        }
 
-            // Clear the main/worker queues
-            final RemoteViewsAdapter adapter = mAdapter.get();
-            if (adapter == null) return;
+        @Override
+        public void handleMessage(Message msg) {
+            RemoteViewsAdapter adapter = mAdapter.get();
 
-            adapter.mMainQueue.post(new Runnable() {
-                @Override
-                public void run() {
-                    // Dequeue any unbind messages
-                    adapter.mMainQueue.removeMessages(sUnbindServiceMessageType);
-
-                    final RemoteAdapterConnectionCallback callback = adapter.mCallback.get();
-                    if (callback != null) {
-                        callback.onRemoteAdapterDisconnected();
+            switch (msg.what) {
+                case MSG_REQUEST_BIND: {
+                    if (adapter == null || mRemoteViewsFactory != null) {
+                        enqueueDeferredUnbindServiceMessage();
                     }
+                    if (mBindRequested) {
+                        return;
+                    }
+                    int flags = Context.BIND_AUTO_CREATE
+                            | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE;
+                    final IServiceConnection sd = mContext.getServiceDispatcher(this, this, flags);
+                    Intent intent = (Intent) msg.obj;
+                    int appWidgetId = msg.arg1;
+                    mBindRequested = AppWidgetManager.getInstance(mContext)
+                            .bindRemoteViewsService(mContext, appWidgetId, intent, sd, flags);
+                    return;
                 }
-            });
+                case MSG_NOTIFY_DATA_SET_CHANGED: {
+                    enqueueDeferredUnbindServiceMessage();
+                    if (adapter == null) {
+                        return;
+                    }
+                    if (mRemoteViewsFactory == null) {
+                        mNotifyDataSetChangedPending = true;
+                        adapter.requestBindService();
+                        return;
+                    }
+                    if (!sendNotifyDataSetChange(true)) {
+                        return;
+                    }
+
+                    // Flush the cache so that we can reload new items from the service
+                    synchronized (adapter.mCache) {
+                        adapter.mCache.reset();
+                    }
+
+                    // Re-request the new metadata (only after the notification to the factory)
+                    adapter.updateTemporaryMetaData(mRemoteViewsFactory);
+                    int newCount;
+                    int[] visibleWindow;
+                    synchronized (adapter.mCache.getTemporaryMetaData()) {
+                        newCount = adapter.mCache.getTemporaryMetaData().count;
+                        visibleWindow = adapter.getVisibleWindow(newCount);
+                    }
+
+                    // Pre-load (our best guess of) the views which are currently visible in the
+                    // AdapterView. This mitigates flashing and flickering of loading views when a
+                    // widget notifies that its data has changed.
+                    for (int position : visibleWindow) {
+                        // Because temporary meta data is only ever modified from this thread
+                        // (ie. mWorkerThread), it is safe to assume that count is a valid
+                        // representation.
+                        if (position < newCount) {
+                            adapter.updateRemoteViews(mRemoteViewsFactory, position, false);
+                        }
+                    }
+
+                    // Propagate the notification back to the base adapter
+                    adapter.mMainHandler.sendEmptyMessage(MSG_MAIN_HANDLER_COMMIT_METADATA);
+                    adapter.mMainHandler.sendEmptyMessage(
+                            MSG_MAIN_HANDLER_SUPER_NOTIFY_DATA_SET_CHANGED);
+                    return;
+                }
+
+                case MSG_LOAD_NEXT_ITEM: {
+                    if (adapter == null || mRemoteViewsFactory == null) {
+                        return;
+                    }
+                    removeMessages(MSG_UNBIND_SERVICE);
+                    // Get the next index to load
+                    final int position = adapter.mCache.getNextIndexToLoad();
+                    if (position > -1) {
+                        // Load the item, and notify any existing RemoteViewsFrameLayouts
+                        adapter.updateRemoteViews(mRemoteViewsFactory, position, true);
+
+                        // Queue up for the next one to load
+                        sendEmptyMessage(MSG_LOAD_NEXT_ITEM);
+                    } else {
+                        // No more items to load, so queue unbind
+                        enqueueDeferredUnbindServiceMessage();
+                    }
+                    return;
+                }
+                case MSG_UNBIND_SERVICE: {
+                    unbindNow();
+                    return;
+                }
+            }
         }
 
-        public synchronized IRemoteViewsFactory getRemoteViewsFactory() {
-            return mRemoteViewsFactory;
+        protected void unbindNow() {
+            if (mBindRequested) {
+                mBindRequested = false;
+                mContext.unbindService(this);
+            }
+            mRemoteViewsFactory = null;
         }
 
-        public synchronized boolean isConnected() {
-            return mIsConnected;
+        private boolean sendNotifyDataSetChange(boolean always) {
+            try {
+                if (always || !mRemoteViewsFactory.isCreated()) {
+                    mRemoteViewsFactory.onDataSetChanged();
+                }
+                return true;
+            } catch (RemoteException | RuntimeException e) {
+                Log.e(TAG, "Error in updateNotifyDataSetChanged(): " + e.getMessage());
+                return false;
+            }
+        }
+
+        private void enqueueDeferredUnbindServiceMessage() {
+            removeMessages(MSG_UNBIND_SERVICE);
+            sendEmptyMessageDelayed(MSG_UNBIND_SERVICE, UNBIND_SERVICE_DELAY);
         }
     }
 
@@ -309,6 +356,8 @@
     static class RemoteViewsFrameLayout extends AppWidgetHostView {
         private final FixedSizeRemoteViewsCache mCache;
 
+        public int cacheIndex = -1;
+
         public RemoteViewsFrameLayout(Context context, FixedSizeRemoteViewsCache cache) {
             super(context);
             mCache = cache;
@@ -359,26 +408,23 @@
      * Stores the references of all the RemoteViewsFrameLayouts that have been returned by the
      * adapter that have not yet had their RemoteViews loaded.
      */
-    private class RemoteViewsFrameLayoutRefSet {
-        private final SparseArray<LinkedList<RemoteViewsFrameLayout>> mReferences =
-                new SparseArray<>();
-        private final HashMap<RemoteViewsFrameLayout, LinkedList<RemoteViewsFrameLayout>>
-                mViewToLinkedList = new HashMap<>();
+    private class RemoteViewsFrameLayoutRefSet
+            extends SparseArray<LinkedList<RemoteViewsFrameLayout>> {
 
         /**
          * Adds a new reference to a RemoteViewsFrameLayout returned by the adapter.
          */
         public void add(int position, RemoteViewsFrameLayout layout) {
-            LinkedList<RemoteViewsFrameLayout> refs = mReferences.get(position);
+            LinkedList<RemoteViewsFrameLayout> refs = get(position);
 
             // Create the list if necessary
             if (refs == null) {
-                refs = new LinkedList<RemoteViewsFrameLayout>();
-                mReferences.put(position, refs);
+                refs = new LinkedList<>();
+                put(position, refs);
             }
-            mViewToLinkedList.put(layout, refs);
 
             // Add the references to the list
+            layout.cacheIndex = position;
             refs.add(layout);
         }
 
@@ -389,18 +435,13 @@
         public void notifyOnRemoteViewsLoaded(int position, RemoteViews view) {
             if (view == null) return;
 
-            final LinkedList<RemoteViewsFrameLayout> refs = mReferences.get(position);
+            // Remove this set from the original mapping
+            final LinkedList<RemoteViewsFrameLayout> refs = removeReturnOld(position);
             if (refs != null) {
                 // Notify all the references for that position of the newly loaded RemoteViews
                 for (final RemoteViewsFrameLayout ref : refs) {
                     ref.onRemoteViewsLoaded(view, mRemoteViewsOnClickHandler, true);
-                    if (mViewToLinkedList.containsKey(ref)) {
-                        mViewToLinkedList.remove(ref);
-                    }
                 }
-                refs.clear();
-                // Remove this set from the original mapping
-                mReferences.remove(position);
             }
         }
 
@@ -408,20 +449,14 @@
          * We need to remove views from this set if they have been recycled by the AdapterView.
          */
         public void removeView(RemoteViewsFrameLayout rvfl) {
-            if (mViewToLinkedList.containsKey(rvfl)) {
-                mViewToLinkedList.get(rvfl).remove(rvfl);
-                mViewToLinkedList.remove(rvfl);
+            if (rvfl.cacheIndex < 0) {
+                return;
             }
-        }
-
-        /**
-         * Removes all references to all RemoteViewsFrameLayouts returned by the adapter.
-         */
-        public void clear() {
-            // We currently just clear the references, and leave all the previous layouts returned
-            // in their default state of the loading view.
-            mReferences.clear();
-            mViewToLinkedList.clear();
+            final LinkedList<RemoteViewsFrameLayout> refs = get(rvfl.cacheIndex);
+            if (refs != null) {
+                refs.remove(rvfl);
+            }
+            rvfl.cacheIndex = -1;
         }
     }
 
@@ -512,7 +547,6 @@
      *
      */
     private static class FixedSizeRemoteViewsCache {
-        private static final String TAG = "FixedSizeRemoteViewsCache";
 
         // The meta data related to all the RemoteViews, ie. count, is stable, etc.
         // The meta data objects are made final so that they can be locked on independently
@@ -534,7 +568,7 @@
         // too much memory.
         private final SparseArray<RemoteViews> mIndexRemoteViews = new SparseArray<>();
 
-        // An array of indices to load, Indices which are explicitely requested are set to true,
+        // An array of indices to load, Indices which are explicitly requested are set to true,
         // and those determined by the preloading algorithm to prefetch are set to false.
         private final SparseBooleanArray mIndicesToLoad = new SparseBooleanArray();
 
@@ -676,7 +710,7 @@
                 }
             }
 
-            int count = 0;
+            int count;
             synchronized (mMetaData) {
                 count = mMetaData.count;
             }
@@ -791,9 +825,11 @@
         // Initialize the worker thread
         mWorkerThread = new HandlerThread("RemoteViewsCache-loader");
         mWorkerThread.start();
-        mWorkerQueue = new Handler(mWorkerThread.getLooper());
-        mMainQueue = new Handler(Looper.myLooper(), this);
+        mMainHandler = new Handler(Looper.myLooper(), this);
+        mServiceHandler = new RemoteServiceHandler(mWorkerThread.getLooper(), this,
+                context.getApplicationContext());
         mAsyncViewLoadExecutor = useAsyncLoader ? new HandlerThreadExecutor(mWorkerThread) : null;
+        mCallback = callback;
 
         if (sCacheRemovalThread == null) {
             sCacheRemovalThread = new HandlerThread("RemoteViewsAdapter-cachePruner");
@@ -801,10 +837,6 @@
             sCacheRemovalQueue = new Handler(sCacheRemovalThread.getLooper());
         }
 
-        // Initialize the cache and the service connection on startup
-        mCallback = new WeakReference<RemoteAdapterConnectionCallback>(callback);
-        mServiceConnection = new RemoteViewsAdapterServiceConnection(this);
-
         RemoteViewsCacheKey key = new RemoteViewsCacheKey(new Intent.FilterComparison(mIntent),
                 mAppWidgetId);
 
@@ -819,7 +851,7 @@
                     }
                 }
             } else {
-                mCache = new FixedSizeRemoteViewsCache(sDefaultCacheSize);
+                mCache = new FixedSizeRemoteViewsCache(DEFAULT_CACHE_SIZE);
             }
             if (!mDataReady) {
                 requestBindService();
@@ -830,9 +862,8 @@
     @Override
     protected void finalize() throws Throwable {
         try {
-            if (mWorkerThread != null) {
-                mWorkerThread.quit();
-            }
+            mServiceHandler.unbindNow();
+            mWorkerThread.quit();
         } finally {
             super.finalize();
         }
@@ -869,16 +900,13 @@
                 sCachedRemoteViewsCaches.put(key, mCache);
             }
 
-            Runnable r = new Runnable() {
-                @Override
-                public void run() {
-                    synchronized (sCachedRemoteViewsCaches) {
-                        if (sCachedRemoteViewsCaches.containsKey(key)) {
-                            sCachedRemoteViewsCaches.remove(key);
-                        }
-                        if (sRemoteViewsCacheRemoveRunnables.containsKey(key)) {
-                            sRemoteViewsCacheRemoveRunnables.remove(key);
-                        }
+            Runnable r = () -> {
+                synchronized (sCachedRemoteViewsCaches) {
+                    if (sCachedRemoteViewsCaches.containsKey(key)) {
+                        sCachedRemoteViewsCaches.remove(key);
+                    }
+                    if (sRemoteViewsCacheRemoveRunnables.containsKey(key)) {
+                        sRemoteViewsCacheRemoveRunnables.remove(key);
                     }
                 }
             };
@@ -887,54 +915,8 @@
         }
     }
 
-    private void loadNextIndexInBackground() {
-        mWorkerQueue.post(new Runnable() {
-            @Override
-            public void run() {
-                if (mServiceConnection.isConnected()) {
-                    // Get the next index to load
-                    int position = -1;
-                    synchronized (mCache) {
-                        position = mCache.getNextIndexToLoad();
-                    }
-                    if (position > -1) {
-                        // Load the item, and notify any existing RemoteViewsFrameLayouts
-                        updateRemoteViews(position, true);
-
-                        // Queue up for the next one to load
-                        loadNextIndexInBackground();
-                    } else {
-                        // No more items to load, so queue unbind
-                        enqueueDeferredUnbindServiceMessage();
-                    }
-                }
-            }
-        });
-    }
-
-    private void processException(String method, Exception e) {
-        Log.e("RemoteViewsAdapter", "Error in " + method + ": " + e.getMessage());
-
-        // If we encounter a crash when updating, we should reset the metadata & cache and trigger
-        // a notifyDataSetChanged to update the widget accordingly
-        final RemoteViewsMetaData metaData = mCache.getMetaData();
-        synchronized (metaData) {
-            metaData.reset();
-        }
-        synchronized (mCache) {
-            mCache.reset();
-        }
-        mMainQueue.post(new Runnable() {
-            @Override
-            public void run() {
-                superNotifyDataSetChanged();
-            }
-        });
-    }
-
-    private void updateTemporaryMetaData() {
-        IRemoteViewsFactory factory = mServiceConnection.getRemoteViewsFactory();
-
+    @WorkerThread
+    private void updateTemporaryMetaData(IRemoteViewsFactory factory) {
         try {
             // get the properties/first view (so that we can use it to
             // measure our dummy views)
@@ -958,40 +940,54 @@
                 tmpMetaData.count = count;
                 tmpMetaData.loadingTemplate = loadingTemplate;
             }
-        } catch(RemoteException e) {
-            processException("updateMetaData", e);
-        } catch(RuntimeException e) {
-            processException("updateMetaData", e);
+        } catch (RemoteException | RuntimeException e) {
+            Log.e("RemoteViewsAdapter", "Error in updateMetaData: " + e.getMessage());
+
+            // If we encounter a crash when updating, we should reset the metadata & cache
+            // and trigger a notifyDataSetChanged to update the widget accordingly
+            synchronized (mCache.getMetaData()) {
+                mCache.getMetaData().reset();
+            }
+            synchronized (mCache) {
+                mCache.reset();
+            }
+            mMainHandler.sendEmptyMessage(MSG_MAIN_HANDLER_SUPER_NOTIFY_DATA_SET_CHANGED);
         }
     }
 
-    private void updateRemoteViews(final int position, boolean notifyWhenLoaded) {
-        IRemoteViewsFactory factory = mServiceConnection.getRemoteViewsFactory();
-
+    @WorkerThread
+    private void updateRemoteViews(IRemoteViewsFactory factory, int position,
+            boolean notifyWhenLoaded) {
         // Load the item information from the remote service
-        RemoteViews remoteViews = null;
-        long itemId = 0;
+        final RemoteViews remoteViews;
+        final long itemId;
         try {
             remoteViews = factory.getViewAt(position);
             itemId = factory.getItemId(position);
-        } catch (RemoteException e) {
+
+            if (remoteViews == null) {
+                throw new RuntimeException("Null remoteViews");
+            }
+        } catch (RemoteException | RuntimeException e) {
             Log.e(TAG, "Error in updateRemoteViews(" + position + "): " + e.getMessage());
 
             // Return early to prevent additional work in re-centering the view cache, and
             // swapping from the loading view
             return;
-        } catch (RuntimeException e) {
-            Log.e(TAG, "Error in updateRemoteViews(" + position + "): " + e.getMessage());
-            return;
         }
 
-        if (remoteViews == null) {
-            // If a null view was returned, we break early to prevent it from getting
-            // into our cache and causing problems later. The effect is that the child  at this
-            // position will remain as a loading view until it is updated.
-            Log.e(TAG, "Error in updateRemoteViews(" + position + "): " + " null RemoteViews " +
-                    "returned from RemoteViewsFactory.");
-            return;
+        if (remoteViews.mApplication != null) {
+            // We keep track of last application info. This helps when all the remoteViews have
+            // same applicationInfo, which should be the case for a typical adapter. But if every
+            // view has different application info, there will not be any optimization.
+            if (mLastRemoteViewAppInfo != null
+                    && remoteViews.hasSameAppInfo(mLastRemoteViewAppInfo)) {
+                // We should probably also update the remoteViews for nested ViewActions.
+                // Hopefully, RemoteViews in an adapter would be less complicated.
+                remoteViews.mApplication = mLastRemoteViewAppInfo;
+            } else {
+                mLastRemoteViewAppInfo = remoteViews.mApplication;
+            }
         }
 
         int layoutId = remoteViews.getLayoutId();
@@ -1004,21 +1000,15 @@
         }
         synchronized (mCache) {
             if (viewTypeInRange) {
-                int[] visibleWindow = getVisibleWindow(mVisibleWindowLowerBound,
-                        mVisibleWindowUpperBound, cacheCount);
+                int[] visibleWindow = getVisibleWindow(cacheCount);
                 // Cache the RemoteViews we loaded
                 mCache.insert(position, remoteViews, itemId, visibleWindow);
 
-                // Notify all the views that we have previously returned for this index that
-                // there is new data for it.
-                final RemoteViews rv = remoteViews;
                 if (notifyWhenLoaded) {
-                    mMainQueue.post(new Runnable() {
-                        @Override
-                        public void run() {
-                            mRequestedViews.notifyOnRemoteViewsLoaded(position, rv);
-                        }
-                    });
+                    // Notify all the views that we have previously returned for this index that
+                    // there is new data for it.
+                    Message.obtain(mMainHandler, MSG_MAIN_HANDLER_REMOTE_VIEWS_LOADED, position, 0,
+                            remoteViews).sendToTarget();
                 }
             } else {
                 // We need to log an error here, as the the view type count specified by the
@@ -1057,7 +1047,7 @@
     }
 
     public int getItemViewType(int position) {
-        int typeId = 0;
+        final int typeId;
         synchronized (mCache) {
             if (mCache.containsMetaDataAt(position)) {
                 typeId = mCache.getMetaDataAt(position).typeId;
@@ -1088,14 +1078,13 @@
         synchronized (mCache) {
             RemoteViews rv = mCache.getRemoteViewsAt(position);
             boolean isInCache = (rv != null);
-            boolean isConnected = mServiceConnection.isConnected();
             boolean hasNewItems = false;
 
             if (convertView != null && convertView instanceof RemoteViewsFrameLayout) {
                 mRequestedViews.removeView((RemoteViewsFrameLayout) convertView);
             }
 
-            if (!isInCache && !isConnected) {
+            if (!isInCache) {
                 // Requesting bind service will trigger a super.notifyDataSetChanged(), which will
                 // in turn trigger another request to getView()
                 requestBindService();
@@ -1115,7 +1104,9 @@
             if (isInCache) {
                 // Apply the view synchronously if possible, to avoid flickering
                 layout.onRemoteViewsLoaded(rv, mRemoteViewsOnClickHandler, false);
-                if (hasNewItems) loadNextIndexInBackground();
+                if (hasNewItems) {
+                    mServiceHandler.sendEmptyMessage(MSG_LOAD_NEXT_ITEM);
+                }
             } else {
                 // If the views is not loaded, apply the loading view. If the loading view doesn't
                 // exist, the layout will create a default view based on the firstView height.
@@ -1125,7 +1116,7 @@
                         false);
                 mRequestedViews.add(position, layout);
                 mCache.queueRequestedPositionToLoad(position);
-                loadNextIndexInBackground();
+                mServiceHandler.sendEmptyMessage(MSG_LOAD_NEXT_ITEM);
             }
             return layout;
         }
@@ -1149,69 +1140,12 @@
         return getCount() <= 0;
     }
 
-    private void onNotifyDataSetChanged() {
-        // Complete the actual notifyDataSetChanged() call initiated earlier
-        IRemoteViewsFactory factory = mServiceConnection.getRemoteViewsFactory();
-        try {
-            factory.onDataSetChanged();
-        } catch (RemoteException e) {
-            Log.e(TAG, "Error in updateNotifyDataSetChanged(): " + e.getMessage());
-
-            // Return early to prevent from further being notified (since nothing has
-            // changed)
-            return;
-        } catch (RuntimeException e) {
-            Log.e(TAG, "Error in updateNotifyDataSetChanged(): " + e.getMessage());
-            return;
-        }
-
-        // Flush the cache so that we can reload new items from the service
-        synchronized (mCache) {
-            mCache.reset();
-        }
-
-        // Re-request the new metadata (only after the notification to the factory)
-        updateTemporaryMetaData();
-        int newCount;
-        int[] visibleWindow;
-        synchronized(mCache.getTemporaryMetaData()) {
-            newCount = mCache.getTemporaryMetaData().count;
-            visibleWindow = getVisibleWindow(mVisibleWindowLowerBound,
-                    mVisibleWindowUpperBound, newCount);
-        }
-
-        // Pre-load (our best guess of) the views which are currently visible in the AdapterView.
-        // This mitigates flashing and flickering of loading views when a widget notifies that
-        // its data has changed.
-        for (int i: visibleWindow) {
-            // Because temporary meta data is only ever modified from this thread (ie.
-            // mWorkerThread), it is safe to assume that count is a valid representation.
-            if (i < newCount) {
-                updateRemoteViews(i, false);
-            }
-        }
-
-        // Propagate the notification back to the base adapter
-        mMainQueue.post(new Runnable() {
-            @Override
-            public void run() {
-                synchronized (mCache) {
-                    mCache.commitTemporaryMetaData();
-                }
-
-                superNotifyDataSetChanged();
-                enqueueDeferredUnbindServiceMessage();
-            }
-        });
-
-        // Reset the notify flagflag
-        mNotifyDataSetChangedAfterOnServiceConnected = false;
-    }
-
     /**
      * Returns a sorted array of all integers between lower and upper.
      */
-    private int[] getVisibleWindow(int lower, int upper, int count) {
+    private int[] getVisibleWindow(int count) {
+        int lower = mVisibleWindowLowerBound;
+        int upper = mVisibleWindowUpperBound;
         // In the case that the window is invalid or uninitialized, return an empty window.
         if ((lower == 0 && upper == 0) || lower < 0 || upper < 0) {
             return new int[0];
@@ -1241,23 +1175,8 @@
     }
 
     public void notifyDataSetChanged() {
-        // Dequeue any unbind messages
-        mMainQueue.removeMessages(sUnbindServiceMessageType);
-
-        // If we are not connected, queue up the notifyDataSetChanged to be handled when we do
-        // connect
-        if (!mServiceConnection.isConnected()) {
-            mNotifyDataSetChangedAfterOnServiceConnected = true;
-            requestBindService();
-            return;
-        }
-
-        mWorkerQueue.post(new Runnable() {
-            @Override
-            public void run() {
-                onNotifyDataSetChanged();
-            }
-        });
+        mServiceHandler.removeMessages(MSG_UNBIND_SERVICE);
+        mServiceHandler.sendEmptyMessage(MSG_NOTIFY_DATA_SET_CHANGED);
     }
 
     void superNotifyDataSetChanged() {
@@ -1266,35 +1185,38 @@
 
     @Override
     public boolean handleMessage(Message msg) {
-        boolean result = false;
         switch (msg.what) {
-        case sUnbindServiceMessageType:
-            if (mServiceConnection.isConnected()) {
-                mServiceConnection.unbind(mContext, mAppWidgetId, mIntent);
+            case MSG_MAIN_HANDLER_COMMIT_METADATA: {
+                mCache.commitTemporaryMetaData();
+                return true;
             }
-            result = true;
-            break;
-        default:
-            break;
+            case MSG_MAIN_HANDLER_SUPER_NOTIFY_DATA_SET_CHANGED: {
+                superNotifyDataSetChanged();
+                return true;
+            }
+            case MSG_MAIN_HANDLER_REMOTE_ADAPTER_CONNECTED: {
+                if (mCallback != null) {
+                    mCallback.onRemoteAdapterConnected();
+                }
+                return true;
+            }
+            case MSG_MAIN_HANDLER_REMOTE_ADAPTER_DISCONNECTED: {
+                if (mCallback != null) {
+                    mCallback.onRemoteAdapterDisconnected();
+                }
+                return true;
+            }
+            case MSG_MAIN_HANDLER_REMOTE_VIEWS_LOADED: {
+                mRequestedViews.notifyOnRemoteViewsLoaded(msg.arg1, (RemoteViews) msg.obj);
+                return true;
+            }
         }
-        return result;
+        return false;
     }
 
-    private void enqueueDeferredUnbindServiceMessage() {
-        // Remove any existing deferred-unbind messages
-        mMainQueue.removeMessages(sUnbindServiceMessageType);
-        mMainQueue.sendEmptyMessageDelayed(sUnbindServiceMessageType, sUnbindServiceDelay);
-    }
-
-    private boolean requestBindService() {
-        // Try binding the service (which will start it if it's not already running)
-        if (!mServiceConnection.isConnected()) {
-            mServiceConnection.bind(mContext, mAppWidgetId, mIntent);
-        }
-
-        // Remove any existing deferred-unbind messages
-        mMainQueue.removeMessages(sUnbindServiceMessageType);
-        return mServiceConnection.isConnected();
+    private void requestBindService() {
+        mServiceHandler.removeMessages(MSG_UNBIND_SERVICE);
+        Message.obtain(mServiceHandler, MSG_REQUEST_BIND, mAppWidgetId, 0, mIntent).sendToTarget();
     }
 
     private static class HandlerThreadExecutor implements Executor {
@@ -1322,7 +1244,7 @@
             remoteViews = views;
 
             float density = context.getResources().getDisplayMetrics().density;
-            defaultHeight = Math.round(sDefaultLoadingViewHeight * density);
+            defaultHeight = Math.round(DEFAULT_LOADING_VIEW_HEIGHT * density);
         }
 
         public void loadFirstViewHeight(
diff --git a/android/widget/SelectionActionModeHelper.java b/android/widget/SelectionActionModeHelper.java
index 3be42a5..5e22650 100644
--- a/android/widget/SelectionActionModeHelper.java
+++ b/android/widget/SelectionActionModeHelper.java
@@ -95,11 +95,15 @@
     }
 
     public void startActionModeAsync(boolean adjustSelection) {
+        // Check if the smart selection should run for editable text.
+        adjustSelection &= !mTextView.isTextEditable()
+                || mTextView.getTextClassifier().getSettings()
+                        .isSuggestSelectionEnabledForEditableText();
+
         mSelectionTracker.onOriginalSelection(
                 getText(mTextView),
                 mTextView.getSelectionStart(),
-                mTextView.getSelectionEnd(),
-                mTextView.isTextEditable());
+                mTextView.getSelectionEnd());
         cancelAsyncTask();
         if (skipTextClassification()) {
             startActionMode(null);
@@ -196,7 +200,10 @@
     private void startActionMode(@Nullable SelectionResult result) {
         final CharSequence text = getText(mTextView);
         if (result != null && text instanceof Spannable) {
-            Selection.setSelection((Spannable) text, result.mStart, result.mEnd);
+            // Do not change the selection if TextClassifier should be dark launched.
+            if (!mTextView.getTextClassifier().getSettings().isDarkLaunch()) {
+                Selection.setSelection((Spannable) text, result.mStart, result.mEnd);
+            }
             mTextClassification = result.mClassification;
         } else {
             mTextClassification = null;
@@ -377,7 +384,7 @@
     }
 
     private void resetTextClassificationHelper() {
-        mTextClassificationHelper.reset(
+        mTextClassificationHelper.init(
                 mTextView.getTextClassifier(),
                 getText(mTextView),
                 mTextView.getSelectionStart(), mTextView.getSelectionEnd(),
@@ -415,8 +422,7 @@
         /**
          * Called when the original selection happens, before smart selection is triggered.
          */
-        public void onOriginalSelection(
-                CharSequence text, int selectionStart, int selectionEnd, boolean editableText) {
+        public void onOriginalSelection(CharSequence text, int selectionStart, int selectionEnd) {
             // If we abandoned a selection and created a new one very shortly after, we may still
             // have a pending request to log ABANDON, which we flush here.
             mDelayedLogAbandon.flush();
@@ -812,11 +818,11 @@
 
         TextClassificationHelper(TextClassifier textClassifier,
                 CharSequence text, int selectionStart, int selectionEnd, LocaleList locales) {
-            reset(textClassifier, text, selectionStart, selectionEnd, locales);
+            init(textClassifier, text, selectionStart, selectionEnd, locales);
         }
 
         @UiThread
-        public void reset(TextClassifier textClassifier,
+        public void init(TextClassifier textClassifier,
                 CharSequence text, int selectionStart, int selectionEnd, LocaleList locales) {
             mTextClassifier = Preconditions.checkNotNull(textClassifier);
             mText = Preconditions.checkNotNull(text).toString();
@@ -839,8 +845,12 @@
             trimText();
             final TextSelection selection = mTextClassifier.suggestSelection(
                     mTrimmedText, mRelativeStart, mRelativeEnd, mLocales);
-            mSelectionStart = Math.max(0, selection.getSelectionStartIndex() + mTrimStart);
-            mSelectionEnd = Math.min(mText.length(), selection.getSelectionEndIndex() + mTrimStart);
+            // Do not classify new selection boundaries if TextClassifier should be dark launched.
+            if (!mTextClassifier.getSettings().isDarkLaunch()) {
+                mSelectionStart = Math.max(0, selection.getSelectionStartIndex() + mTrimStart);
+                mSelectionEnd = Math.min(
+                        mText.length(), selection.getSelectionEndIndex() + mTrimStart);
+            }
             return performClassification(selection);
         }
 
diff --git a/android/widget/TextView.java b/android/widget/TextView.java
index 24ae03c..d9bc51f 100644
--- a/android/widget/TextView.java
+++ b/android/widget/TextView.java
@@ -10338,6 +10338,17 @@
                 // of the View (and can be any drawable) or a BackgroundColorSpan inside the text.
                 structure.setTextStyle(getTextSize(), getCurrentTextColor(),
                         AssistStructure.ViewNode.TEXT_COLOR_UNDEFINED /* bgColor */, style);
+            } else {
+                structure.setMinTextEms(getMinEms());
+                structure.setMaxTextEms(getMaxEms());
+                int maxLength = -1;
+                for (InputFilter filter: getFilters()) {
+                    if (filter instanceof InputFilter.LengthFilter) {
+                        maxLength = ((InputFilter.LengthFilter) filter).getMax();
+                        break;
+                    }
+                }
+                structure.setMaxTextLength(maxLength);
             }
         }
         structure.setHint(getHint());
diff --git a/com/android/commands/pm/Pm.java b/com/android/commands/pm/Pm.java
index c5c38f5..60ec8a9 100644
--- a/com/android/commands/pm/Pm.java
+++ b/com/android/commands/pm/Pm.java
@@ -1570,11 +1570,19 @@
     private static int showUsage() {
         System.err.println("usage: pm path [--user USER_ID] PACKAGE");
         System.err.println("       pm dump PACKAGE");
-        System.err.println("       pm install [-lrtsfd] [-i PACKAGE] [--user USER_ID] [PATH]");
-        System.err.println("       pm install-create [-lrtsfdp] [-i PACKAGE] [-S BYTES]");
-        System.err.println("               [--install-location 0/1/2]");
-        System.err.println("               [--force-uuid internal|UUID]");
-        System.err.println("       pm install-write [-S BYTES] SESSION_ID SPLIT_NAME [PATH]");
+        System.err.println("       pm install [-lrtsfdg] [-i PACKAGE] [--user USER_ID]");
+        System.err.println("               [-p INHERIT_PACKAGE] [--install-location 0/1/2]");
+        System.err.println("               [--originating-uri URI] [---referrer URI]");
+        System.err.println("               [--abi ABI_NAME] [--force-sdk]");
+        System.err.println("               [--preload] [--instantapp] [--full] [--dont-kill]");
+        System.err.println("               [--force-uuid internal|UUID] [--pkg PACKAGE] [-S BYTES] [PATH|-]");
+        System.err.println("       pm install-create [-lrtsfdg] [-i PACKAGE] [--user USER_ID]");
+        System.err.println("               [-p INHERIT_PACKAGE] [--install-location 0/1/2]");
+        System.err.println("               [--originating-uri URI] [---referrer URI]");
+        System.err.println("               [--abi ABI_NAME] [--force-sdk]");
+        System.err.println("               [--preload] [--instantapp] [--full] [--dont-kill]");
+        System.err.println("               [--force-uuid internal|UUID] [--pkg PACKAGE] [-S BYTES]");
+        System.err.println("       pm install-write [-S BYTES] SESSION_ID SPLIT_NAME [PATH|-]");
         System.err.println("       pm install-commit SESSION_ID");
         System.err.println("       pm install-abandon SESSION_ID");
         System.err.println("       pm uninstall [-k] [--user USER_ID] [--versionCode VERSION_CODE] PACKAGE");
@@ -1613,15 +1621,27 @@
         System.err.println("pm install: install a single legacy package");
         System.err.println("pm install-create: create an install session");
         System.err.println("    -l: forward lock application");
-        System.err.println("    -r: replace existing application");
+        System.err.println("    -r: allow replacement of existing application");
         System.err.println("    -t: allow test packages");
-        System.err.println("    -i: specify the installer package name");
+        System.err.println("    -i: specify package name of installer owning the app");
         System.err.println("    -s: install application on sdcard");
         System.err.println("    -f: install application on internal flash");
         System.err.println("    -d: allow version code downgrade (debuggable packages only)");
-        System.err.println("    -p: partial application install");
+        System.err.println("    -p: partial application install (new split on top of existing pkg)");
         System.err.println("    -g: grant all runtime permissions");
         System.err.println("    -S: size in bytes of entire session");
+        System.err.println("    --dont-kill: installing a new feature split, don't kill running app");
+        System.err.println("    --originating-uri: set URI where app was downloaded from");
+        System.err.println("    --referrer: set URI that instigated the install of the app");
+        System.err.println("    --pkg: specify expected package name of app being installed");
+        System.err.println("    --abi: override the default ABI of the platform");
+        System.err.println("    --instantapp: cause the app to be installed as an ephemeral install app");
+        System.err.println("    --full: cause the app to be installed as a non-ephemeral full app");
+        System.err.println("    --install-location: force the install location:");
+        System.err.println("        0=auto, 1=internal only, 2=prefer external");
+        System.err.println("    --force-uuid: force install on to disk volume with given UUID");
+        System.err.println("    --force-sdk: allow install even when existing app targets platform");
+        System.err.println("        codename but new one targets a final API level");
         System.err.println("");
         System.err.println("pm install-write: write a package into existing session; path may");
         System.err.println("  be '-' to read from stdin");
diff --git a/com/android/egg/octo/Ocquarium.java b/com/android/egg/octo/Ocquarium.java
index bbbdf80..8a06cc6 100644
--- a/com/android/egg/octo/Ocquarium.java
+++ b/com/android/egg/octo/Ocquarium.java
@@ -26,6 +26,7 @@
 
 public class Ocquarium extends Activity {
     ImageView mImageView;
+    private OctopusDrawable mOcto;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
@@ -43,10 +44,9 @@
         bg.addView(mImageView, new FrameLayout.LayoutParams(
                 ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
 
-        final OctopusDrawable octo = new OctopusDrawable(getApplicationContext());
-        octo.setSizePx((int) (OctopusDrawable.randfrange(40f,180f) * dp));
-        mImageView.setImageDrawable(octo);
-        octo.startDrift();
+        mOcto = new OctopusDrawable(getApplicationContext());
+        mOcto.setSizePx((int) (OctopusDrawable.randfrange(40f,180f) * dp));
+        mImageView.setImageDrawable(mOcto);
 
         mImageView.setOnTouchListener(new View.OnTouchListener() {
             boolean touching;
@@ -54,24 +54,36 @@
             public boolean onTouch(View view, MotionEvent motionEvent) {
                 switch (motionEvent.getActionMasked()) {
                     case MotionEvent.ACTION_DOWN:
-                        if (octo.hitTest(motionEvent.getX(), motionEvent.getY())) {
+                        if (mOcto.hitTest(motionEvent.getX(), motionEvent.getY())) {
                             touching = true;
-                            octo.stopDrift();
+                            mOcto.stopDrift();
                         }
                         break;
                     case MotionEvent.ACTION_MOVE:
                         if (touching) {
-                            octo.moveTo(motionEvent.getX(), motionEvent.getY());
+                            mOcto.moveTo(motionEvent.getX(), motionEvent.getY());
                         }
                         break;
                     case MotionEvent.ACTION_UP:
                     case MotionEvent.ACTION_CANCEL:
                         touching = false;
-                        octo.startDrift();
+                        mOcto.startDrift();
                         break;
                 }
                 return true;
             }
         });
     }
+
+    @Override
+    protected void onPause() {
+        mOcto.stopDrift();
+        super.onPause();
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        mOcto.startDrift();
+    }
 }
diff --git a/com/android/internal/alsa/AlsaCardsParser.java b/com/android/internal/alsa/AlsaCardsParser.java
index 5b92a17..bb75bf6 100644
--- a/com/android/internal/alsa/AlsaCardsParser.java
+++ b/com/android/internal/alsa/AlsaCardsParser.java
@@ -37,6 +37,12 @@
 
     private ArrayList<AlsaCardRecord> mCardRecords = new ArrayList<AlsaCardRecord>();
 
+    public static final int SCANSTATUS_NOTSCANNED = -1;
+    public static final int SCANSTATUS_SUCCESS = 0;
+    public static final int SCANSTATUS_FAIL = 1;
+    public static final int SCANSTATUS_EMPTY = 2;
+    private int mScanStatus = SCANSTATUS_NOTSCANNED;
+
     public class AlsaCardRecord {
         private static final String TAG = "AlsaCardRecord";
         private static final String kUsbCardKeyStr = "at usb-";
@@ -104,10 +110,11 @@
 
     public AlsaCardsParser() {}
 
-    public void scan() {
+    public int scan() {
         if (DEBUG) {
-            Slog.i(TAG, "AlsaCardsParser.scan()");
+            Slog.i(TAG, "AlsaCardsParser.scan()....");
         }
+
         mCardRecords = new ArrayList<AlsaCardRecord>();
 
         File cardsFile = new File(kCardsFilePath);
@@ -134,11 +141,26 @@
                 mCardRecords.add(cardRecord);
             }
             reader.close();
+            if (mCardRecords.size() > 0) {
+                mScanStatus = SCANSTATUS_SUCCESS;
+            } else {
+                mScanStatus = SCANSTATUS_EMPTY;
+            }
         } catch (FileNotFoundException e) {
             e.printStackTrace();
+            mScanStatus = SCANSTATUS_FAIL;
         } catch (IOException e) {
             e.printStackTrace();
+            mScanStatus = SCANSTATUS_FAIL;
         }
+        if (DEBUG) {
+            Slog.i(TAG, "  status:" + mScanStatus);
+        }
+        return mScanStatus;
+    }
+
+    public int getScanStatus() {
+        return mScanStatus;
     }
 
     public ArrayList<AlsaCardRecord> getScanRecords() {
@@ -182,7 +204,11 @@
         }
 
         // get the new list of devices
-        scan();
+        if (scan() != SCANSTATUS_SUCCESS) {
+            Slog.e(TAG, "Error scanning Alsa cards file.");
+            return -1;
+        }
+
         if (DEBUG) {
             LogDevices("Current Devices:", mCardRecords);
         }
diff --git a/com/android/internal/alsa/AlsaDevicesParser.java b/com/android/internal/alsa/AlsaDevicesParser.java
index 6e3d596..15261ba 100644
--- a/com/android/internal/alsa/AlsaDevicesParser.java
+++ b/com/android/internal/alsa/AlsaDevicesParser.java
@@ -46,6 +46,12 @@
     private boolean mHasPlaybackDevices = false;
     private boolean mHasMIDIDevices = false;
 
+    public static final int SCANSTATUS_NOTSCANNED = -1;
+    public static final int SCANSTATUS_SUCCESS = 0;
+    public static final int SCANSTATUS_FAIL = 1;
+    public static final int SCANSTATUS_EMPTY = 2;
+    private int mScanStatus = SCANSTATUS_NOTSCANNED;
+
     public class AlsaDeviceRecord {
         public static final int kDeviceType_Unknown = -1;
         public static final int kDeviceType_Audio = 0;
@@ -258,7 +264,11 @@
         return line.charAt(kIndex_CardDeviceField) == '[';
     }
 
-    public boolean scan() {
+    public int scan() {
+        if (DEBUG) {
+            Slog.i(TAG, "AlsaDevicesParser.scan()....");
+        }
+
         mDeviceRecords.clear();
 
         File devicesFile = new File(kDevicesFilePath);
@@ -274,13 +284,27 @@
                 }
             }
             reader.close();
-            return true;
+            // success if we add at least 1 record
+            if (mDeviceRecords.size() > 0) {
+                mScanStatus = SCANSTATUS_SUCCESS;
+            } else {
+                mScanStatus = SCANSTATUS_EMPTY;
+            }
         } catch (FileNotFoundException e) {
             e.printStackTrace();
+            mScanStatus = SCANSTATUS_FAIL;
         } catch (IOException e) {
             e.printStackTrace();
+            mScanStatus = SCANSTATUS_FAIL;
         }
-        return false;
+        if (DEBUG) {
+            Slog.i(TAG, "  status:" + mScanStatus);
+        }
+        return mScanStatus;
+    }
+
+    public int getScanStatus() {
+        return mScanStatus;
     }
 
     //
diff --git a/com/android/internal/app/LocalePickerWithRegion.java b/com/android/internal/app/LocalePickerWithRegion.java
index 3d5cd0f..d0719ee 100644
--- a/com/android/internal/app/LocalePickerWithRegion.java
+++ b/com/android/internal/app/LocalePickerWithRegion.java
@@ -238,7 +238,7 @@
             mSearchView.setOnQueryTextListener(this);
 
             // Restore previous search status
-            if (mPreviousSearch != null) {
+            if (!TextUtils.isEmpty(mPreviousSearch)) {
                 searchMenuItem.expandActionView();
                 mSearchView.setIconified(false);
                 mSearchView.setActivated(true);
diff --git a/com/android/internal/notification/SystemNotificationChannels.java b/com/android/internal/notification/SystemNotificationChannels.java
index d64c9a1..4a181b2 100644
--- a/com/android/internal/notification/SystemNotificationChannels.java
+++ b/com/android/internal/notification/SystemNotificationChannels.java
@@ -20,6 +20,7 @@
 import android.app.NotificationManager;
 import android.content.Context;
 import android.content.pm.ParceledListSlice;
+import android.media.AudioAttributes;
 import android.os.RemoteException;
 import android.provider.Settings;
 
@@ -47,6 +48,7 @@
     public static String RETAIL_MODE = "RETAIL_MODE";
     public static String USB = "USB";
     public static String FOREGROUND_SERVICE = "FOREGROUND_SERVICE";
+    public static String HEAVY_WEIGHT_APP = "HEAVY_WEIGHT_APP";
 
     public static void createAll(Context context) {
         final NotificationManager nm = context.getSystemService(NotificationManager.class);
@@ -139,6 +141,17 @@
         foregroundChannel.setBlockableSystem(true);
         channelsList.add(foregroundChannel);
 
+        NotificationChannel heavyWeightChannel = new NotificationChannel(
+                HEAVY_WEIGHT_APP,
+                context.getString(R.string.notification_channel_heavy_weight_app),
+                NotificationManager.IMPORTANCE_DEFAULT);
+        heavyWeightChannel.setShowBadge(false);
+        heavyWeightChannel.setSound(null, new AudioAttributes.Builder()
+                .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
+                .setUsage(AudioAttributes.USAGE_NOTIFICATION_EVENT)
+                .build());
+        channelsList.add(heavyWeightChannel);
+
         nm.createNotificationChannels(channelsList);
     }
 
diff --git a/com/android/internal/os/BatteryStatsImpl.java b/com/android/internal/os/BatteryStatsImpl.java
index 36fd991..9cc1959 100644
--- a/com/android/internal/os/BatteryStatsImpl.java
+++ b/com/android/internal/os/BatteryStatsImpl.java
@@ -63,6 +63,7 @@
 import android.util.SparseArray;
 import android.util.SparseIntArray;
 import android.util.SparseLongArray;
+import android.util.StatsLog;
 import android.util.TimeUtils;
 import android.util.Xml;
 import android.view.Display;
@@ -119,7 +120,7 @@
     private static final int MAGIC = 0xBA757475; // 'BATSTATS'
 
     // Current on-disk Parcel version
-    private static final int VERSION = 167 + (USE_OLD_HISTORY ? 1000 : 0);
+    private static final int VERSION = 168 + (USE_OLD_HISTORY ? 1000 : 0);
 
     // Maximum number of items we will record in the history.
     private static final int MAX_HISTORY_ITEMS;
@@ -681,17 +682,17 @@
     }
 
     @Override
-    public long getMahDischarge(int which) {
+    public long getUahDischarge(int which) {
         return mDischargeCounter.getCountLocked(which);
     }
 
     @Override
-    public long getMahDischargeScreenOff(int which) {
+    public long getUahDischargeScreenOff(int which) {
         return mDischargeScreenOffCounter.getCountLocked(which);
     }
 
     @Override
-    public long getMahDischargeScreenDoze(int which) {
+    public long getUahDischargeScreenDoze(int which) {
         return mDischargeScreenDozeCounter.getCountLocked(which);
     }
 
@@ -3588,7 +3589,7 @@
 
     public void updateTimeBasesLocked(boolean unplugged, int screenState, long uptime,
             long realtime) {
-        final boolean screenOff = isScreenOff(screenState) || isScreenDoze(screenState);
+        final boolean screenOff = !isScreenOn(screenState);
         final boolean updateOnBatteryTimeBase = unplugged != mOnBatteryTimeBase.isRunning();
         final boolean updateOnBatteryScreenOffTimeBase =
                 (unplugged && screenOff) != mOnBatteryScreenOffTimeBase.isRunning();
@@ -4030,6 +4031,7 @@
         }
         addHistoryEventLocked(elapsedRealtime, uptime, HistoryItem.EVENT_LONG_WAKE_LOCK_START,
                 historyName, uid);
+        StatsLog.write(StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED, uid, name, historyName, 1);
     }
 
     public void noteLongPartialWakelockFinish(String name, String historyName, int uid) {
@@ -4045,6 +4047,7 @@
         }
         addHistoryEventLocked(elapsedRealtime, uptime, HistoryItem.EVENT_LONG_WAKE_LOCK_FINISH,
                 historyName, uid);
+        StatsLog.write(StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED, uid, name, historyName, 0);
     }
 
     void aggregateLastWakeupUptimeLocked(long uptimeMs) {
@@ -4403,6 +4406,7 @@
                 mPowerSaveModeEnabledTimer.stopRunningLocked(elapsedRealtime);
             }
             addHistoryRecordLocked(elapsedRealtime, uptime);
+            StatsLog.write(StatsLog.BATTERY_SAVER_MODE_STATE_CHANGED, enabled ? 1 : 0);
         }
     }
 
@@ -5427,6 +5431,10 @@
                 elapsedRealtimeUs, which);
     }
 
+    @Override public Timer getScreenBrightnessTimer(int brightnessBin) {
+        return mScreenBrightnessTimer[brightnessBin];
+    }
+
     @Override public long getInteractiveTime(long elapsedRealtimeUs, int which) {
         return mInteractiveTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
     }
@@ -5520,10 +5528,18 @@
                 elapsedRealtimeUs, which);
     }
 
+    @Override public Timer getPhoneSignalScanningTimer() {
+        return mPhoneSignalScanningTimer;
+    }
+
     @Override public int getPhoneSignalStrengthCount(int strengthBin, int which) {
         return mPhoneSignalStrengthsTimer[strengthBin].getCountLocked(which);
     }
 
+    @Override public Timer getPhoneSignalStrengthTimer(int strengthBin) {
+        return mPhoneSignalStrengthsTimer[strengthBin];
+    }
+
     @Override public long getPhoneDataConnectionTime(int dataType,
             long elapsedRealtimeUs, int which) {
         return mPhoneDataConnectionsTimer[dataType].getTotalTimeLocked(
@@ -5534,6 +5550,10 @@
         return mPhoneDataConnectionsTimer[dataType].getCountLocked(which);
     }
 
+    @Override public Timer getPhoneDataConnectionTimer(int dataType) {
+        return mPhoneDataConnectionsTimer[dataType];
+    }
+
     @Override public long getMobileRadioActiveTime(long elapsedRealtimeUs, int which) {
         return mMobileRadioActiveTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
     }
@@ -5572,6 +5592,10 @@
         return mWifiStateTimer[wifiState].getCountLocked(which);
     }
 
+    @Override public Timer getWifiStateTimer(int wifiState) {
+        return mWifiStateTimer[wifiState];
+    }
+
     @Override public long getWifiSupplStateTime(int state,
             long elapsedRealtimeUs, int which) {
         return mWifiSupplStateTimer[state].getTotalTimeLocked(
@@ -5582,6 +5606,10 @@
         return mWifiSupplStateTimer[state].getCountLocked(which);
     }
 
+    @Override public Timer getWifiSupplStateTimer(int state) {
+        return mWifiSupplStateTimer[state];
+    }
+
     @Override public long getWifiSignalStrengthTime(int strengthBin,
             long elapsedRealtimeUs, int which) {
         return mWifiSignalStrengthsTimer[strengthBin].getTotalTimeLocked(
@@ -5592,6 +5620,10 @@
         return mWifiSignalStrengthsTimer[strengthBin].getCountLocked(which);
     }
 
+    @Override public Timer getWifiSignalStrengthTimer(int strengthBin) {
+        return mWifiSignalStrengthsTimer[strengthBin];
+    }
+
     @Override
     public ControllerActivityCounter getBluetoothControllerActivity() {
         return mBluetoothActivity;
@@ -6152,17 +6184,25 @@
 
         public void noteAudioTurnedOnLocked(long elapsedRealtimeMs) {
             createAudioTurnedOnTimerLocked().startRunningLocked(elapsedRealtimeMs);
+            // TODO(statsd): Possibly use a worksource instead of a uid.
+            StatsLog.write(StatsLog.AUDIO_STATE_CHANGED, getUid(), 1);
         }
 
         public void noteAudioTurnedOffLocked(long elapsedRealtimeMs) {
             if (mAudioTurnedOnTimer != null) {
                 mAudioTurnedOnTimer.stopRunningLocked(elapsedRealtimeMs);
+                if (!mAudioTurnedOnTimer.isRunningLocked()) { // only tell statsd if truly stopped
+                    // TODO(statsd): Possibly use a worksource instead of a uid.
+                    StatsLog.write(StatsLog.AUDIO_STATE_CHANGED, getUid(), 0);
+                }
             }
         }
 
         public void noteResetAudioLocked(long elapsedRealtimeMs) {
             if (mAudioTurnedOnTimer != null) {
                 mAudioTurnedOnTimer.stopAllRunningLocked(elapsedRealtimeMs);
+                // TODO(statsd): Possibly use a worksource instead of a uid.
+                StatsLog.write(StatsLog.AUDIO_STATE_CHANGED, getUid(), 0);
             }
         }
 
@@ -6176,17 +6216,25 @@
 
         public void noteVideoTurnedOnLocked(long elapsedRealtimeMs) {
             createVideoTurnedOnTimerLocked().startRunningLocked(elapsedRealtimeMs);
+            // TODO(statsd): Possibly use a worksource instead of a uid.
+            StatsLog.write(StatsLog.MEDIA_CODEC_ACTIVITY_CHANGED, getUid(), 1);
         }
 
         public void noteVideoTurnedOffLocked(long elapsedRealtimeMs) {
             if (mVideoTurnedOnTimer != null) {
                 mVideoTurnedOnTimer.stopRunningLocked(elapsedRealtimeMs);
+                if (!mVideoTurnedOnTimer.isRunningLocked()) { // only tell statsd if truly stopped
+                    // TODO(statsd): Possibly use a worksource instead of a uid.
+                    StatsLog.write(StatsLog.MEDIA_CODEC_ACTIVITY_CHANGED, getUid(), 0);
+                }
             }
         }
 
         public void noteResetVideoLocked(long elapsedRealtimeMs) {
             if (mVideoTurnedOnTimer != null) {
                 mVideoTurnedOnTimer.stopAllRunningLocked(elapsedRealtimeMs);
+                // TODO(statsd): Possibly use a worksource instead of a uid.
+                StatsLog.write(StatsLog.MEDIA_CODEC_ACTIVITY_CHANGED, getUid(), 0);
             }
         }
 
@@ -6200,17 +6248,25 @@
 
         public void noteFlashlightTurnedOnLocked(long elapsedRealtimeMs) {
             createFlashlightTurnedOnTimerLocked().startRunningLocked(elapsedRealtimeMs);
+            // TODO(statsd): Possibly use a worksource instead of a uid.
+            StatsLog.write(StatsLog.FLASHLIGHT_STATE_CHANGED, getUid(), 1);
         }
 
         public void noteFlashlightTurnedOffLocked(long elapsedRealtimeMs) {
             if (mFlashlightTurnedOnTimer != null) {
                 mFlashlightTurnedOnTimer.stopRunningLocked(elapsedRealtimeMs);
+                if (!mFlashlightTurnedOnTimer.isRunningLocked()) {
+                    // TODO(statsd): Possibly use a worksource instead of a uid.
+                    StatsLog.write(StatsLog.FLASHLIGHT_STATE_CHANGED, getUid(), 0);
+                }
             }
         }
 
         public void noteResetFlashlightLocked(long elapsedRealtimeMs) {
             if (mFlashlightTurnedOnTimer != null) {
                 mFlashlightTurnedOnTimer.stopAllRunningLocked(elapsedRealtimeMs);
+                // TODO(statsd): Possibly use a worksource instead of a uid.
+                StatsLog.write(StatsLog.FLASHLIGHT_STATE_CHANGED, getUid(), 0);
             }
         }
 
@@ -6224,17 +6280,25 @@
 
         public void noteCameraTurnedOnLocked(long elapsedRealtimeMs) {
             createCameraTurnedOnTimerLocked().startRunningLocked(elapsedRealtimeMs);
+            // TODO(statsd): Possibly use a worksource instead of a uid.
+            StatsLog.write(StatsLog.CAMERA_STATE_CHANGED, getUid(), 1);
         }
 
         public void noteCameraTurnedOffLocked(long elapsedRealtimeMs) {
             if (mCameraTurnedOnTimer != null) {
                 mCameraTurnedOnTimer.stopRunningLocked(elapsedRealtimeMs);
+                if (!mCameraTurnedOnTimer.isRunningLocked()) { // only tell statsd if truly stopped
+                    // TODO(statsd): Possibly use a worksource instead of a uid.
+                    StatsLog.write(StatsLog.CAMERA_STATE_CHANGED, getUid(), 0);
+                }
             }
         }
 
         public void noteResetCameraLocked(long elapsedRealtimeMs) {
             if (mCameraTurnedOnTimer != null) {
                 mCameraTurnedOnTimer.stopAllRunningLocked(elapsedRealtimeMs);
+                // TODO(statsd): Possibly use a worksource instead of a uid.
+                StatsLog.write(StatsLog.CAMERA_STATE_CHANGED, getUid(), 0);
             }
         }
 
@@ -6283,26 +6347,42 @@
 
         public void noteBluetoothScanStartedLocked(long elapsedRealtimeMs, boolean isUnoptimized) {
             createBluetoothScanTimerLocked().startRunningLocked(elapsedRealtimeMs);
+            // TODO(statsd): Possibly use a worksource instead of a uid.
+            StatsLog.write(StatsLog.BLE_SCAN_STATE_CHANGED, getUid(), 1);
             if (isUnoptimized) {
                 createBluetoothUnoptimizedScanTimerLocked().startRunningLocked(elapsedRealtimeMs);
+                // TODO(statsd): Possibly use a worksource instead of a uid.
+                StatsLog.write(StatsLog.BLE_UNOPTIMIZED_SCAN_STATE_CHANGED, getUid(), 1);
             }
         }
 
         public void noteBluetoothScanStoppedLocked(long elapsedRealtimeMs, boolean isUnoptimized) {
             if (mBluetoothScanTimer != null) {
                 mBluetoothScanTimer.stopRunningLocked(elapsedRealtimeMs);
+                if (!mBluetoothScanTimer.isRunningLocked()) { // only tell statsd if truly stopped
+                    // TODO(statsd): Possibly use a worksource instead of a uid.
+                    StatsLog.write(StatsLog.BLE_SCAN_STATE_CHANGED, getUid(), 0);
+                }
             }
             if (isUnoptimized && mBluetoothUnoptimizedScanTimer != null) {
                 mBluetoothUnoptimizedScanTimer.stopRunningLocked(elapsedRealtimeMs);
+                if (!mBluetoothUnoptimizedScanTimer.isRunningLocked()) {
+                    // TODO(statsd): Possibly use a worksource instead of a uid.
+                    StatsLog.write(StatsLog.BLE_UNOPTIMIZED_SCAN_STATE_CHANGED, getUid(), 0);
+                }
             }
         }
 
         public void noteResetBluetoothScanLocked(long elapsedRealtimeMs) {
             if (mBluetoothScanTimer != null) {
                 mBluetoothScanTimer.stopAllRunningLocked(elapsedRealtimeMs);
+                // TODO(statsd): Possibly use a worksource instead of a uid.
+                StatsLog.write(StatsLog.BLE_SCAN_STATE_CHANGED, getUid(), 0);
             }
             if (mBluetoothUnoptimizedScanTimer != null) {
                 mBluetoothUnoptimizedScanTimer.stopAllRunningLocked(elapsedRealtimeMs);
+                // TODO(statsd): Possibly use a worksource instead of a uid.
+                StatsLog.write(StatsLog.BLE_UNOPTIMIZED_SCAN_STATE_CHANGED, getUid(), 0);
             }
         }
 
@@ -6324,6 +6404,9 @@
             createBluetoothScanResultCounterLocked().addAtomic(numNewResults);
             // Uses background timebase, so the count will only be incremented if uid in background.
             createBluetoothScanResultBgCounterLocked().addAtomic(numNewResults);
+            // TODO(statsd): Possibly use a worksource instead of a uid.
+            // TODO(statsd): This could be in AppScanStats instead, if desired.
+            StatsLog.write(StatsLog.BLE_SCAN_RESULT_RECEIVED, getUid(), numNewResults);
         }
 
         @Override
@@ -6400,6 +6483,11 @@
         }
 
         @Override
+        public Timer getWifiScanTimer() {
+            return mWifiScanTimer;
+        }
+
+        @Override
         public int getWifiScanBackgroundCount(int which) {
             if (mWifiScanTimer == null || mWifiScanTimer.getSubTimer() == null) {
                 return 0;
@@ -6426,6 +6514,14 @@
         }
 
         @Override
+        public Timer getWifiScanBackgroundTimer() {
+            if (mWifiScanTimer == null) {
+                return null;
+            }
+            return mWifiScanTimer.getSubTimer();
+        }
+
+        @Override
         public long getWifiBatchedScanTime(int csphBin, long elapsedRealtimeUs, int which) {
             if (csphBin < 0 || csphBin >= NUM_WIFI_BATCHED_SCAN_BINS) return 0;
             if (mWifiBatchedScanTimer[csphBin] == null) {
@@ -8684,6 +8780,8 @@
             DualTimer t = mSyncStats.startObject(name);
             if (t != null) {
                 t.startRunningLocked(elapsedRealtimeMs);
+                // TODO(statsd): Possibly use a worksource instead of a uid.
+                StatsLog.write(StatsLog.SYNC_STATE_CHANGED, getUid(), name, 1);
             }
         }
 
@@ -8691,6 +8789,10 @@
             DualTimer t = mSyncStats.stopObject(name);
             if (t != null) {
                 t.stopRunningLocked(elapsedRealtimeMs);
+                if (!t.isRunningLocked()) { // only tell statsd if truly stopped
+                    // TODO(statsd): Possibly use a worksource instead of a uid.
+                    StatsLog.write(StatsLog.SYNC_STATE_CHANGED, getUid(), name, 0);
+                }
             }
         }
 
@@ -8698,6 +8800,8 @@
             DualTimer t = mJobStats.startObject(name);
             if (t != null) {
                 t.startRunningLocked(elapsedRealtimeMs);
+                // TODO(statsd): Possibly use a worksource instead of a uid.
+                StatsLog.write(StatsLog.SCHEDULED_JOB_STATE_CHANGED, getUid(), name, 1);
             }
         }
 
@@ -8705,6 +8809,10 @@
             DualTimer t = mJobStats.stopObject(name);
             if (t != null) {
                 t.stopRunningLocked(elapsedRealtimeMs);
+                if (!t.isRunningLocked()) { // only tell statsd if truly stopped
+                    // TODO(statsd): Possibly use a worksource instead of a uid.
+                    StatsLog.write(StatsLog.SCHEDULED_JOB_STATE_CHANGED, getUid(), name, 0);
+                }
             }
             if (mBsi.mOnBatteryTimeBase.isRunning()) {
                 SparseIntArray types = mJobCompletions.get(name);
@@ -8771,6 +8879,8 @@
             }
             if (type == WAKE_TYPE_PARTIAL) {
                 createAggregatedPartialWakelockTimerLocked().startRunningLocked(elapsedRealtimeMs);
+                // TODO(statsd): Possibly use a worksource instead of a uid.
+                StatsLog.write(StatsLog.UID_WAKELOCK_STATE_CHANGED, getUid(), type, 1);
                 if (pid >= 0) {
                     Pid p = getPidStatsLocked(pid);
                     if (p.mWakeNesting++ == 0) {
@@ -8788,6 +8898,11 @@
             if (type == WAKE_TYPE_PARTIAL) {
                 if (mAggregatedPartialWakelockTimer != null) {
                     mAggregatedPartialWakelockTimer.stopRunningLocked(elapsedRealtimeMs);
+                    if (!mAggregatedPartialWakelockTimer.isRunningLocked()) {
+                        // TODO(statsd): Possibly use a worksource instead of a uid.
+                        StatsLog.write(StatsLog.UID_WAKELOCK_STATE_CHANGED, getUid(), type,
+                                0);
+                    }
                 }
                 if (pid >= 0) {
                     Pid p = mPids.get(pid);
@@ -8811,6 +8926,12 @@
         public void noteStartSensor(int sensor, long elapsedRealtimeMs) {
             DualTimer t = getSensorTimerLocked(sensor, /* create= */ true);
             t.startRunningLocked(elapsedRealtimeMs);
+            // TODO(statsd): Possibly use a worksource instead of a uid.
+            if (sensor == Sensor.GPS) {
+                StatsLog.write(StatsLog.GPS_SCAN_STATE_CHANGED, getUid(), 1);
+            } else {
+                StatsLog.write(StatsLog.SENSOR_STATE_CHANGED, getUid(), sensor, 1);
+            }
         }
 
         public void noteStopSensor(int sensor, long elapsedRealtimeMs) {
@@ -8818,6 +8939,14 @@
             DualTimer t = getSensorTimerLocked(sensor, false);
             if (t != null) {
                 t.stopRunningLocked(elapsedRealtimeMs);
+                if (!t.isRunningLocked()) { // only tell statsd if truly stopped
+                    // TODO(statsd): Possibly use a worksource instead of a uid.
+                    if (sensor == Sensor.GPS) {
+                        StatsLog.write(StatsLog.GPS_SCAN_STATE_CHANGED, getUid(), 0);
+                    } else {
+                        StatsLog.write(StatsLog.SENSOR_STATE_CHANGED, getUid(), sensor, 0);
+                    }
+                }
             }
         }
 
@@ -9463,7 +9592,7 @@
     }
 
     public boolean isScreenOn(int state) {
-        return state == Display.STATE_ON;
+        return state == Display.STATE_ON || state == Display.STATE_VR;
     }
 
     public boolean isScreenOff(int state) {
@@ -12791,7 +12920,7 @@
         mMobileRadioPowerState = DataConnectionRealTimeInfo.DC_POWER_STATE_LOW;
         mMobileRadioActiveTimer = new StopwatchTimer(mClocks, null, -400, null,
                 mOnBatteryTimeBase, in);
-        mMobileRadioActivePerAppTimer = new StopwatchTimer(mClocks, null, -401, null, 
+        mMobileRadioActivePerAppTimer = new StopwatchTimer(mClocks, null, -401, null,
                 mOnBatteryTimeBase, in);
         mMobileRadioActiveAdjustedTime = new LongSamplingCounter(mOnBatteryTimeBase, in);
         mMobileRadioActiveUnknownTime = new LongSamplingCounter(mOnBatteryTimeBase, in);
@@ -13090,7 +13219,7 @@
                 }
             }
         } else {
-            // TODO: There should be two 0's printed here, not just one.
+            out.writeInt(0);
             out.writeInt(0);
         }
 
diff --git a/com/android/internal/os/KernelCpuSpeedReader.java b/com/android/internal/os/KernelCpuSpeedReader.java
index 757a112..4c0370c 100644
--- a/com/android/internal/os/KernelCpuSpeedReader.java
+++ b/com/android/internal/os/KernelCpuSpeedReader.java
@@ -15,13 +15,12 @@
  */
 package com.android.internal.os;
 
+import android.system.Os;
 import android.text.TextUtils;
 import android.os.StrictMode;
 import android.system.OsConstants;
 import android.util.Slog;
 
-import libcore.io.Libcore;
-
 import java.io.BufferedReader;
 import java.io.FileReader;
 import java.io.IOException;
@@ -53,7 +52,7 @@
                 cpuNumber);
         mLastSpeedTimesMs = new long[numSpeedSteps];
         mDeltaSpeedTimesMs = new long[numSpeedSteps];
-        long jiffyHz = Libcore.os.sysconf(OsConstants._SC_CLK_TCK);
+        long jiffyHz = Os.sysconf(OsConstants._SC_CLK_TCK);
         mJiffyMillis = 1000/jiffyHz;
     }
 
diff --git a/com/android/internal/os/LoggingPrintStream.java b/com/android/internal/os/LoggingPrintStream.java
index f14394a..d27874c 100644
--- a/com/android/internal/os/LoggingPrintStream.java
+++ b/com/android/internal/os/LoggingPrintStream.java
@@ -28,12 +28,15 @@
 import java.util.Formatter;
 import java.util.Locale;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 /**
  * A print stream which logs output line by line.
  *
  * {@hide}
  */
-abstract class LoggingPrintStream extends PrintStream {
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+public abstract class LoggingPrintStream extends PrintStream {
 
     private final StringBuilder builder = new StringBuilder();
 
diff --git a/com/android/internal/os/ProcessCpuTracker.java b/com/android/internal/os/ProcessCpuTracker.java
index e46dfc4..bf31c7d 100644
--- a/com/android/internal/os/ProcessCpuTracker.java
+++ b/com/android/internal/os/ProcessCpuTracker.java
@@ -22,6 +22,7 @@
 import android.os.Process;
 import android.os.StrictMode;
 import android.os.SystemClock;
+import android.system.Os;
 import android.system.OsConstants;
 import android.util.Slog;
 
@@ -294,7 +295,7 @@
 
     public ProcessCpuTracker(boolean includeThreads) {
         mIncludeThreads = includeThreads;
-        long jiffyHz = Libcore.os.sysconf(OsConstants._SC_CLK_TCK);
+        long jiffyHz = Os.sysconf(OsConstants._SC_CLK_TCK);
         mJiffyMillis = 1000/jiffyHz;
     }
 
diff --git a/com/android/internal/os/ZygoteInit.java b/com/android/internal/os/ZygoteInit.java
index 4abab28..2be6212 100644
--- a/com/android/internal/os/ZygoteInit.java
+++ b/com/android/internal/os/ZygoteInit.java
@@ -549,7 +549,7 @@
             try {
                 dexoptNeeded = DexFile.getDexOptNeeded(
                     classPathElement, instructionSet, systemServerFilter,
-                    false /* newProfile */, false /* downgrade */);
+                    null /* classLoaderContext */, false /* newProfile */, false /* downgrade */);
             } catch (FileNotFoundException ignored) {
                 // Do not add to the classpath.
                 Log.w(TAG, "Missing classpath element for system server: " + classPathElement);
diff --git a/com/android/internal/telephony/CarrierKeyDownloadManager.java b/com/android/internal/telephony/CarrierKeyDownloadManager.java
index 606f7ff..66bc529 100644
--- a/com/android/internal/telephony/CarrierKeyDownloadManager.java
+++ b/com/android/internal/telephony/CarrierKeyDownloadManager.java
@@ -16,6 +16,10 @@
 
 package com.android.internal.telephony;
 
+import static android.preference.PreferenceManager.getDefaultSharedPreferences;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
 import android.app.AlarmManager;
 import android.app.DownloadManager;
 import android.app.PendingIntent;
@@ -53,8 +57,7 @@
 import java.security.cert.CertificateFactory;
 import java.security.cert.X509Certificate;
 import java.util.Date;
-
-import static android.preference.PreferenceManager.getDefaultSharedPreferences;
+import java.util.zip.GZIPInputStream;
 
 /**
  * This class contains logic to get Certificates and keep them current.
@@ -82,7 +85,7 @@
     private static final String SEPARATOR = ":";
 
     private static final String JSON_CERTIFICATE = "certificate";
-    // This is a hack to accomodate Verizon. Verizon insists on using the public-key
+    // This is a hack to accommodate certain Carriers who insists on using the public-key
     // field to store the certificate. We'll just use which-ever is not null.
     private static final String JSON_CERTIFICATE_ALTERNATE = "public-key";
     private static final String JSON_TYPE = "key-type";
@@ -296,6 +299,7 @@
         DownloadManager.Query query = new DownloadManager.Query();
         query.setFilterById(carrierKeyDownloadIdentifier);
         Cursor cursor = mDownloadManager.query(query);
+        InputStream source = null;
 
         if (cursor == null) {
             return;
@@ -304,7 +308,7 @@
             int columnIndex = cursor.getColumnIndex(DownloadManager.COLUMN_STATUS);
             if (DownloadManager.STATUS_SUCCESSFUL == cursor.getInt(columnIndex)) {
                 try {
-                    final InputStream source = new FileInputStream(
+                    source = new FileInputStream(
                             mDownloadManager.openDownloadedFile(carrierKeyDownloadIdentifier)
                                     .getFileDescriptor());
                     jsonStr = convertToString(source);
@@ -314,6 +318,11 @@
                             + ". " + e);
                 } finally {
                     mDownloadManager.remove(carrierKeyDownloadIdentifier);
+                    try {
+                        source.close();
+                    } catch (IOException e) {
+                        e.printStackTrace();
+                    }
                 }
             }
             Log.d(LOG_TAG, "Completed downloading keys");
@@ -353,24 +362,23 @@
     }
 
     private static String convertToString(InputStream is) {
-        BufferedReader reader = new BufferedReader(new InputStreamReader(is));
-        StringBuilder sb = new StringBuilder();
-
-        String line;
         try {
+            // The current implementation at certain Carriers has the data gzipped, which requires
+            // us to unzip the contents. Longer term, we want to add a flag in carrier config which
+            // determines if the data needs to be zipped or not.
+            GZIPInputStream gunzip = new GZIPInputStream(is);
+            BufferedReader reader = new BufferedReader(new InputStreamReader(gunzip, UTF_8));
+            StringBuilder sb = new StringBuilder();
+
+            String line;
             while ((line = reader.readLine()) != null) {
                 sb.append(line).append('\n');
             }
+            return sb.toString();
         } catch (IOException e) {
             e.printStackTrace();
-        } finally {
-            try {
-                is.close();
-            } catch (IOException e) {
-                e.printStackTrace();
-            }
         }
-        return sb.toString();
+        return null;
     }
 
     /**
@@ -401,7 +409,7 @@
             JSONArray keys = jsonObj.getJSONArray(JSON_CARRIER_KEYS);
             for (int i = 0; i < keys.length(); i++) {
                 JSONObject key = keys.getJSONObject(i);
-                // This is a hack to accomodate Verizon. Verizon insists on using the public-key
+                // This is a hack to accommodate certain carriers who insist on using the public-key
                 // field to store the certificate. We'll just use which-ever is not null.
                 String cert = null;
                 if (key.has(JSON_CERTIFICATE)) {
diff --git a/com/android/internal/telephony/NetworkScanRequestTracker.java b/com/android/internal/telephony/NetworkScanRequestTracker.java
index 14c6810..46b1eef 100644
--- a/com/android/internal/telephony/NetworkScanRequestTracker.java
+++ b/com/android/internal/telephony/NetworkScanRequestTracker.java
@@ -125,6 +125,34 @@
                 return false;
             }
         }
+
+        if ((nsri.mRequest.searchPeriodicity < NetworkScanRequest.MIN_SEARCH_PERIODICITY_SEC)
+                || (nsri.mRequest.searchPeriodicity
+                > NetworkScanRequest.MAX_SEARCH_PERIODICITY_SEC)) {
+            return false;
+        }
+
+        if ((nsri.mRequest.maxSearchTime < NetworkScanRequest.MIN_SEARCH_MAX_SEC)
+                || (nsri.mRequest.maxSearchTime > NetworkScanRequest.MAX_SEARCH_MAX_SEC)) {
+            return false;
+        }
+
+        if ((nsri.mRequest.incrementalResultsPeriodicity
+                < NetworkScanRequest.MIN_INCREMENTAL_PERIODICITY_SEC)
+                || (nsri.mRequest.incrementalResultsPeriodicity
+                > NetworkScanRequest.MAX_INCREMENTAL_PERIODICITY_SEC)) {
+            return false;
+        }
+
+        if ((nsri.mRequest.searchPeriodicity > nsri.mRequest.maxSearchTime)
+                || (nsri.mRequest.incrementalResultsPeriodicity > nsri.mRequest.maxSearchTime)) {
+            return false;
+        }
+
+        if ((nsri.mRequest.mccMncs != null)
+                && (nsri.mRequest.mccMncs.size() > NetworkScanRequest.MAX_MCC_MNC_LIST_SIZE)) {
+            return false;
+        }
         return true;
     }
 
diff --git a/com/android/internal/telephony/Phone.java b/com/android/internal/telephony/Phone.java
index 6acc874..16f816f 100644
--- a/com/android/internal/telephony/Phone.java
+++ b/com/android/internal/telephony/Phone.java
@@ -3319,7 +3319,9 @@
         mRadioCapability.set(rc);
 
         if (SubscriptionManager.isValidSubscriptionId(getSubId())) {
-            sendSubscriptionSettings(true);
+            boolean restoreSelection = !mContext.getResources().getBoolean(
+                    com.android.internal.R.bool.skip_restoring_network_selection);
+            sendSubscriptionSettings(restoreSelection);
         }
     }
 
diff --git a/com/android/internal/telephony/RIL.java b/com/android/internal/telephony/RIL.java
index 84c2b65..9007f14 100644
--- a/com/android/internal/telephony/RIL.java
+++ b/com/android/internal/telephony/RIL.java
@@ -1655,56 +1655,69 @@
         }
     }
 
+    private android.hardware.radio.V1_1.RadioAccessSpecifier convertRadioAccessSpecifierToRadioHAL(
+            RadioAccessSpecifier ras) {
+        android.hardware.radio.V1_1.RadioAccessSpecifier rasInHalFormat =
+                new android.hardware.radio.V1_1.RadioAccessSpecifier();
+        rasInHalFormat.radioAccessNetwork = ras.radioAccessNetwork;
+        List<Integer> bands = null;
+        switch (ras.radioAccessNetwork) {
+            case RadioAccessNetworks.GERAN:
+                bands = rasInHalFormat.geranBands;
+                break;
+            case RadioAccessNetworks.UTRAN:
+                bands = rasInHalFormat.utranBands;
+                break;
+            case RadioAccessNetworks.EUTRAN:
+                bands = rasInHalFormat.eutranBands;
+                break;
+            default:
+                Log.wtf(RILJ_LOG_TAG, "radioAccessNetwork " + ras.radioAccessNetwork
+                        + " not supported!");
+                return null;
+        }
+
+        if (ras.bands != null) {
+            for (int band : ras.bands) {
+                bands.add(band);
+            }
+        }
+        if (ras.channels != null) {
+            for (int channel : ras.channels) {
+                rasInHalFormat.channels.add(channel);
+            }
+        }
+
+        return rasInHalFormat;
+    }
+
     @Override
     public void startNetworkScan(NetworkScanRequest nsr, Message result) {
         IRadio radioProxy = getRadioProxy(result);
         if (radioProxy != null) {
-            android.hardware.radio.V1_1.IRadio radioProxy11 =
-                    android.hardware.radio.V1_1.IRadio.castFrom(radioProxy);
-            if (radioProxy11 == null) {
-                if (result != null) {
-                    AsyncResult.forMessage(result, null,
-                            CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
-                    result.sendToTarget();
-                }
-            } else {
-                android.hardware.radio.V1_1.NetworkScanRequest request =
-                        new android.hardware.radio.V1_1.NetworkScanRequest();
+            android.hardware.radio.V1_2.IRadio radioProxy12 =
+                    android.hardware.radio.V1_2.IRadio.castFrom(radioProxy);
+            if (radioProxy12 != null) {
+                android.hardware.radio.V1_2.NetworkScanRequest request =
+                        new android.hardware.radio.V1_2.NetworkScanRequest();
                 request.type = nsr.scanType;
-                request.interval = 60;
+                request.interval = nsr.searchPeriodicity;
+                request.maxSearchTime = nsr.maxSearchTime;
+                request.incrementalResultsPeriodicity = nsr.incrementalResultsPeriodicity;
+                request.incrementalResults = nsr.incrementalResults;
+
                 for (RadioAccessSpecifier ras : nsr.specifiers) {
-                    android.hardware.radio.V1_1.RadioAccessSpecifier s =
-                            new android.hardware.radio.V1_1.RadioAccessSpecifier();
-                    s.radioAccessNetwork = ras.radioAccessNetwork;
-                    List<Integer> bands = null;
-                    switch (ras.radioAccessNetwork) {
-                        case RadioAccessNetworks.GERAN:
-                            bands = s.geranBands;
-                            break;
-                        case RadioAccessNetworks.UTRAN:
-                            bands = s.utranBands;
-                            break;
-                        case RadioAccessNetworks.EUTRAN:
-                            bands = s.eutranBands;
-                            break;
-                        default:
-                            Log.wtf(RILJ_LOG_TAG, "radioAccessNetwork " + ras.radioAccessNetwork
-                                    + " not supported!");
-                            return;
+
+                    android.hardware.radio.V1_1.RadioAccessSpecifier rasInHalFormat =
+                            convertRadioAccessSpecifierToRadioHAL(ras);
+                    if (rasInHalFormat == null) {
+                        return;
                     }
-                    if (ras.bands != null) {
-                        for (int band : ras.bands) {
-                            bands.add(band);
-                        }
-                    }
-                    if (ras.channels != null) {
-                        for (int channel : ras.channels) {
-                            s.channels.add(channel);
-                        }
-                    }
-                    request.specifiers.add(s);
+
+                    request.specifiers.add(rasInHalFormat);
                 }
 
+                request.mccMncs.addAll(nsr.mccMncs);
                 RILRequest rr = obtainRequest(RIL_REQUEST_START_NETWORK_SCAN, result,
                         mRILDefaultWorkSource);
 
@@ -1713,10 +1726,47 @@
                 }
 
                 try {
-                    radioProxy11.startNetworkScan(rr.mSerial, request);
+                    radioProxy12.startNetworkScan_1_2(rr.mSerial, request);
                 } catch (RemoteException | RuntimeException e) {
                     handleRadioProxyExceptionForRR(rr, "startNetworkScan", e);
                 }
+            } else {
+                android.hardware.radio.V1_1.IRadio radioProxy11 =
+                        android.hardware.radio.V1_1.IRadio.castFrom(radioProxy);
+                if (radioProxy11 == null) {
+                    if (result != null) {
+                        AsyncResult.forMessage(result, null,
+                                CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
+                        result.sendToTarget();
+                    }
+                } else {
+                    android.hardware.radio.V1_1.NetworkScanRequest request =
+                            new android.hardware.radio.V1_1.NetworkScanRequest();
+                    request.type = nsr.scanType;
+                    request.interval = nsr.searchPeriodicity;
+                    for (RadioAccessSpecifier ras : nsr.specifiers) {
+                        android.hardware.radio.V1_1.RadioAccessSpecifier rasInHalFormat =
+                                convertRadioAccessSpecifierToRadioHAL(ras);
+                        if (rasInHalFormat == null) {
+                            return;
+                        }
+
+                        request.specifiers.add(rasInHalFormat);
+                    }
+
+                    RILRequest rr = obtainRequest(RIL_REQUEST_START_NETWORK_SCAN, result,
+                            mRILDefaultWorkSource);
+
+                    if (RILJ_LOGD) {
+                        riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+                    }
+
+                    try {
+                        radioProxy11.startNetworkScan(rr.mSerial, request);
+                    } catch (RemoteException | RuntimeException e) {
+                        handleRadioProxyExceptionForRR(rr, "startNetworkScan", e);
+                    }
+                }
             }
         }
     }
diff --git a/com/android/internal/telephony/ServiceStateTracker.java b/com/android/internal/telephony/ServiceStateTracker.java
index c34cbb2..3aafdfb 100644
--- a/com/android/internal/telephony/ServiceStateTracker.java
+++ b/com/android/internal/telephony/ServiceStateTracker.java
@@ -211,6 +211,7 @@
     protected static final int EVENT_PHONE_TYPE_SWITCHED               = 50;
     protected static final int EVENT_RADIO_POWER_FROM_CARRIER          = 51;
     protected static final int EVENT_SIM_NOT_INSERTED                  = 52;
+    protected static final int EVENT_IMS_SERVICE_STATE_CHANGED         = 53;
 
     protected static final String TIMEZONE_PROPERTY = "persist.sys.timezone";
 
@@ -1349,6 +1350,15 @@
                 updateSpnDisplay();
                 break;
 
+            case EVENT_IMS_SERVICE_STATE_CHANGED:
+                if (DBG) log("EVENT_IMS_SERVICE_STATE_CHANGED");
+                // IMS state will only affect the merged service state if the service state of
+                // GsmCdma phone is not STATE_IN_SERVICE.
+                if (mSS.getState() != ServiceState.STATE_IN_SERVICE) {
+                    mPhone.notifyServiceStateChanged(mPhone.getServiceState());
+                }
+                break;
+
             //CDMA
             case EVENT_CDMA_SUBSCRIPTION_SOURCE_CHANGED:
                 handleCdmaSubscriptionSource(mCdmaSSM.getCdmaSubscriptionSource());
@@ -2551,6 +2561,11 @@
         }
     }
 
+    /** Called when the service state of ImsPhone is changed. */
+    public void onImsServiceStateChanged() {
+        sendMessage(obtainMessage(EVENT_IMS_SERVICE_STATE_CHANGED));
+    }
+
     public void setImsRegistrationState(boolean registered) {
         log("ImsRegistrationState - registered : " + registered);
 
@@ -2813,6 +2828,8 @@
             mRejectCode = mNewRejectCode;
         }
 
+        ServiceState oldMergedSS = mPhone.getServiceState();
+
         // swap mSS and mNewSS to put new state in mSS
         ServiceState tss = mSS;
         mSS = mNewSS;
@@ -2924,7 +2941,10 @@
             setRoamingType(mSS);
             log("Broadcasting ServiceState : " + mSS);
             // notify using PhoneStateListener and the legacy intent ACTION_SERVICE_STATE_CHANGED
-            mPhone.notifyServiceStateChanged(mSS);
+            // notify service state changed only if the merged service state is changed.
+            if (!oldMergedSS.equals(mPhone.getServiceState())) {
+                mPhone.notifyServiceStateChanged(mPhone.getServiceState());
+            }
 
             // insert into ServiceStateProvider. This will trigger apps to wake through JobScheduler
             mPhone.getContext().getContentResolver()
@@ -4412,7 +4432,12 @@
         if ((ar.exception == null) && (ar.result != null)) {
             mSignalStrength = (SignalStrength) ar.result;
             mSignalStrength.validateInput();
-            mSignalStrength.setGsm(isGsm);
+            if (dataRat == ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN
+                    && voiceRat == ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN) {
+                mSignalStrength.fixType();
+            } else {
+                mSignalStrength.setGsm(isGsm);
+            }
             mSignalStrength.setLteRsrpBoost(mSS.getLteEarfcnRsrpBoost());
         } else {
             log("onSignalStrengthResult() Exception from RIL : " + ar.exception);
diff --git a/com/android/internal/telephony/SubscriptionInfoUpdater.java b/com/android/internal/telephony/SubscriptionInfoUpdater.java
index 7e8f51e..2c819c3 100644
--- a/com/android/internal/telephony/SubscriptionInfoUpdater.java
+++ b/com/android/internal/telephony/SubscriptionInfoUpdater.java
@@ -393,6 +393,7 @@
         // The SIM should be loaded at this state, but it is possible in cases such as SIM being
         // removed or a refresh RESET that the IccRecords could be null. The right behavior is to
         // not broadcast the SIM loaded.
+        int loadedSlotId = slotId;
         IccRecords records = mPhone[slotId].getIccCard().getIccRecords();
         if (records == null) {  // Possibly a race condition.
             logd("handleSimLoaded: IccRecords null");
@@ -483,16 +484,16 @@
                     editor.putInt(CURR_SUBID + slotId, subId);
                     editor.apply();
                 }
-
-                // Update set of enabled carrier apps now that the privilege rules may have changed.
-                CarrierAppUtils.disableCarrierAppsUntilPrivileged(mContext.getOpPackageName(),
-                        mPackageManager, TelephonyManager.getDefault(),
-                        mContext.getContentResolver(), mCurrentlyActiveUserId);
-
-                broadcastSimStateChanged(slotId, IccCardConstants.INTENT_VALUE_ICC_LOADED, null);
-                updateCarrierServices(slotId, IccCardConstants.INTENT_VALUE_ICC_LOADED);
             }
         }
+
+        // Update set of enabled carrier apps now that the privilege rules may have changed.
+        CarrierAppUtils.disableCarrierAppsUntilPrivileged(mContext.getOpPackageName(),
+                mPackageManager, TelephonyManager.getDefault(),
+                mContext.getContentResolver(), mCurrentlyActiveUserId);
+
+        broadcastSimStateChanged(loadedSlotId, IccCardConstants.INTENT_VALUE_ICC_LOADED, null);
+        updateCarrierServices(loadedSlotId, IccCardConstants.INTENT_VALUE_ICC_LOADED);
     }
 
     private void updateCarrierServices(int slotId, String simState) {
diff --git a/com/android/internal/telephony/cat/CatService.java b/com/android/internal/telephony/cat/CatService.java
index cd7a756..802944d 100644
--- a/com/android/internal/telephony/cat/CatService.java
+++ b/com/android/internal/telephony/cat/CatService.java
@@ -1071,6 +1071,13 @@
             }
             break;
         case NO_RESPONSE_FROM_USER:
+            // No need to send terminal response for SET UP CALL on user timeout,
+            // instead use dedicated API
+            if (type == CommandType.SET_UP_CALL) {
+                mCmdIf.handleCallSetupRequestFromSim(false, null);
+                mCurrntCmd = null;
+                return;
+            }
         case UICC_SESSION_TERM_BY_USER:
             resp = null;
             break;
diff --git a/com/android/internal/telephony/imsphone/ImsPhone.java b/com/android/internal/telephony/imsphone/ImsPhone.java
index 03d83df..51849ff 100644
--- a/com/android/internal/telephony/imsphone/ImsPhone.java
+++ b/com/android/internal/telephony/imsphone/ImsPhone.java
@@ -260,16 +260,17 @@
     @VisibleForTesting
     public void setServiceState(int state) {
         boolean isVoiceRegStateChanged = false;
+
         synchronized (this) {
             isVoiceRegStateChanged = mSS.getVoiceRegState() != state;
             mSS.setVoiceRegState(state);
         }
         updateDataServiceState();
 
-        // Notifies the service state to the listeners. The service state combined from ImsPhone
-        // and GsmCdmaPhone, it may be changed when the service state in ImsPhone is changed.
         if (isVoiceRegStateChanged) {
-            mNotifier.notifyServiceState(mDefaultPhone);
+            if (mDefaultPhone.getServiceStateTracker() != null) {
+                mDefaultPhone.getServiceStateTracker().onImsServiceStateChanged();
+            }
         }
     }
 
diff --git a/com/android/internal/telephony/uicc/UiccCardApplication.java b/com/android/internal/telephony/uicc/UiccCardApplication.java
index e2904df..fa6bc3a 100644
--- a/com/android/internal/telephony/uicc/UiccCardApplication.java
+++ b/com/android/internal/telephony/uicc/UiccCardApplication.java
@@ -382,11 +382,8 @@
                 case EVENT_CHANGE_PIN2_DONE:
                     // a PIN/PUK/PIN2/PUK2 complete
                     // request has completed. ar.userObj is the response Message
-                    int attemptsRemaining = -1;
                     ar = (AsyncResult)msg.obj;
-                    if ((ar.exception != null) && (ar.result != null)) {
-                        attemptsRemaining = parsePinPukErrorResult(ar);
-                    }
+                    int attemptsRemaining = parsePinPukErrorResult(ar);
                     Message response = (Message)ar.userObj;
                     AsyncResult.forMessage(response).exception = ar.exception;
                     response.arg1 = attemptsRemaining;
diff --git a/com/android/internal/util/MemInfoReader.java b/com/android/internal/util/MemInfoReader.java
index b71fa06..8d71666 100644
--- a/com/android/internal/util/MemInfoReader.java
+++ b/com/android/internal/util/MemInfoReader.java
@@ -82,7 +82,7 @@
      * that are mapped in to processes.
      */
     public long getCachedSizeKb() {
-        return mInfos[Debug.MEMINFO_BUFFERS]
+        return mInfos[Debug.MEMINFO_BUFFERS] + mInfos[Debug.MEMINFO_SLAB_RECLAIMABLE]
                 + mInfos[Debug.MEMINFO_CACHED] - mInfos[Debug.MEMINFO_MAPPED];
     }
 
@@ -90,7 +90,7 @@
      * Amount of RAM that is in use by the kernel for actual allocations.
      */
     public long getKernelUsedSizeKb() {
-        return mInfos[Debug.MEMINFO_SHMEM] + mInfos[Debug.MEMINFO_SLAB]
+        return mInfos[Debug.MEMINFO_SHMEM] + mInfos[Debug.MEMINFO_SLAB_UNRECLAIMABLE]
                 + mInfos[Debug.MEMINFO_VM_ALLOC_USED] + mInfos[Debug.MEMINFO_PAGE_TABLES]
                 + mInfos[Debug.MEMINFO_KERNEL_STACK];
     }
diff --git a/com/android/internal/util/WakeupMessage.java b/com/android/internal/util/WakeupMessage.java
index 46098c5..70b6f96 100644
--- a/com/android/internal/util/WakeupMessage.java
+++ b/com/android/internal/util/WakeupMessage.java
@@ -47,17 +47,19 @@
     protected final int mCmd, mArg1, mArg2;
     @VisibleForTesting
     protected final Object mObj;
+    private final Runnable mRunnable;
     private boolean mScheduled;
 
     public WakeupMessage(Context context, Handler handler,
             String cmdName, int cmd, int arg1, int arg2, Object obj) {
-        mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+        mAlarmManager = getAlarmManager(context);
         mHandler = handler;
         mCmdName = cmdName;
         mCmd = cmd;
         mArg1 = arg1;
         mArg2 = arg2;
         mObj = obj;
+        mRunnable = null;
     }
 
     public WakeupMessage(Context context, Handler handler, String cmdName, int cmd, int arg1) {
@@ -73,6 +75,21 @@
         this(context, handler, cmdName, cmd, 0, 0, null);
     }
 
+    public WakeupMessage(Context context, Handler handler, String cmdName, Runnable runnable) {
+        mAlarmManager = getAlarmManager(context);
+        mHandler = handler;
+        mCmdName = cmdName;
+        mCmd = 0;
+        mArg1 = 0;
+        mArg2 = 0;
+        mObj = null;
+        mRunnable = runnable;
+    }
+
+    private static AlarmManager getAlarmManager(Context context) {
+        return (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+    }
+
     /**
      * Schedule the message to be delivered at the time in milliseconds of the
      * {@link android.os.SystemClock#elapsedRealtime SystemClock.elapsedRealtime()} clock and wakeup
@@ -107,7 +124,12 @@
             mScheduled = false;
         }
         if (stillScheduled) {
-            Message msg = mHandler.obtainMessage(mCmd, mArg1, mArg2, mObj);
+            Message msg;
+            if (mRunnable == null) {
+                msg = mHandler.obtainMessage(mCmd, mArg1, mArg2, mObj);
+            } else {
+                msg = Message.obtain(mHandler, mRunnable);
+            }
             mHandler.dispatchMessage(msg);
             msg.recycle();
         }
diff --git a/com/android/internal/view/InputConnectionWrapper.java b/com/android/internal/view/InputConnectionWrapper.java
index cc0ef75..5b65bbe 100644
--- a/com/android/internal/view/InputConnectionWrapper.java
+++ b/com/android/internal/view/InputConnectionWrapper.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.view;
 
+import android.annotation.AnyThread;
+import android.annotation.BinderThread;
 import android.annotation.NonNull;
 import android.inputmethodservice.AbstractInputMethodService;
 import android.os.Bundle;
@@ -34,6 +36,7 @@
 import android.view.inputmethod.InputContentInfo;
 
 import java.lang.ref.WeakReference;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 public class InputConnectionWrapper implements InputConnection {
     private static final int MAX_WAIT_TIME_MILLIS = 2000;
@@ -44,6 +47,14 @@
     @MissingMethodFlags
     private final int mMissingMethods;
 
+    /**
+     * {@code true} if the system already decided to take away IME focus from the target app. This
+     * can be signaled even when the corresponding signal is in the task queue and
+     * {@link InputMethodService#onUnbindInput()} is not yet called back on the UI thread.
+     */
+    @NonNull
+    private final AtomicBoolean mIsUnbindIssued;
+
     static class InputContextCallback extends IInputContextCallback.Stub {
         private static final String TAG = "InputConnectionWrapper.ICC";
         public int mSeq;
@@ -67,6 +78,7 @@
          * sequence number is set to a new integer.  We use a sequence number so that replies that
          * occur after a timeout has expired are not interpreted as replies to a later request.
          */
+        @AnyThread
         private static InputContextCallback getInstance() {
             synchronized (InputContextCallback.class) {
                 // Return sInstance if it's non-null, otherwise construct a new callback
@@ -90,6 +102,7 @@
         /**
          * Makes the given InputContextCallback available for use in the future.
          */
+        @AnyThread
         private void dispose() {
             synchronized (InputContextCallback.class) {
                 // If sInstance is non-null, just let this object be garbage-collected
@@ -102,7 +115,8 @@
                 }
             }
         }
-        
+
+        @BinderThread
         public void setTextBeforeCursor(CharSequence textBeforeCursor, int seq) {
             synchronized (this) {
                 if (seq == mSeq) {
@@ -116,6 +130,7 @@
             }
         }
 
+        @BinderThread
         public void setTextAfterCursor(CharSequence textAfterCursor, int seq) {
             synchronized (this) {
                 if (seq == mSeq) {
@@ -129,6 +144,7 @@
             }
         }
 
+        @BinderThread
         public void setSelectedText(CharSequence selectedText, int seq) {
             synchronized (this) {
                 if (seq == mSeq) {
@@ -142,6 +158,7 @@
             }
         }
 
+        @BinderThread
         public void setCursorCapsMode(int capsMode, int seq) {
             synchronized (this) {
                 if (seq == mSeq) {
@@ -155,6 +172,7 @@
             }
         }
 
+        @BinderThread
         public void setExtractedText(ExtractedText extractedText, int seq) {
             synchronized (this) {
                 if (seq == mSeq) {
@@ -168,6 +186,7 @@
             }
         }
 
+        @BinderThread
         public void setRequestUpdateCursorAnchorInfoResult(boolean result, int seq) {
             synchronized (this) {
                 if (seq == mSeq) {
@@ -181,6 +200,7 @@
             }
         }
 
+        @BinderThread
         public void setCommitContentResult(boolean result, int seq) {
             synchronized (this) {
                 if (seq == mSeq) {
@@ -199,6 +219,7 @@
          * 
          * <p>The caller must be synchronized on this callback object.
          */
+        @AnyThread
         void waitForResultLocked() {
             long startTime = SystemClock.uptimeMillis();
             long endTime = startTime + MAX_WAIT_TIME_MILLIS;
@@ -219,13 +240,20 @@
 
     public InputConnectionWrapper(
             @NonNull WeakReference<AbstractInputMethodService> inputMethodService,
-            IInputContext inputContext, @MissingMethodFlags final int missingMethods) {
+            IInputContext inputContext, @MissingMethodFlags final int missingMethods,
+            @NonNull AtomicBoolean isUnbindIssued) {
         mInputMethodService = inputMethodService;
         mIInputContext = inputContext;
         mMissingMethods = missingMethods;
+        mIsUnbindIssued = isUnbindIssued;
     }
 
+    @AnyThread
     public CharSequence getTextAfterCursor(int length, int flags) {
+        if (mIsUnbindIssued.get()) {
+            return null;
+        }
+
         CharSequence value = null;
         try {
             InputContextCallback callback = InputContextCallback.getInstance();
@@ -242,8 +270,13 @@
         }
         return value;
     }
-    
+
+    @AnyThread
     public CharSequence getTextBeforeCursor(int length, int flags) {
+        if (mIsUnbindIssued.get()) {
+            return null;
+        }
+
         CharSequence value = null;
         try {
             InputContextCallback callback = InputContextCallback.getInstance();
@@ -261,7 +294,12 @@
         return value;
     }
 
+    @AnyThread
     public CharSequence getSelectedText(int flags) {
+        if (mIsUnbindIssued.get()) {
+            return null;
+        }
+
         if (isMethodMissing(MissingMethodFlags.GET_SELECTED_TEXT)) {
             // This method is not implemented.
             return null;
@@ -283,7 +321,12 @@
         return value;
     }
 
+    @AnyThread
     public int getCursorCapsMode(int reqModes) {
+        if (mIsUnbindIssued.get()) {
+            return 0;
+        }
+
         int value = 0;
         try {
             InputContextCallback callback = InputContextCallback.getInstance();
@@ -301,7 +344,12 @@
         return value;
     }
 
+    @AnyThread
     public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
+        if (mIsUnbindIssued.get()) {
+            return null;
+        }
+
         ExtractedText value = null;
         try {
             InputContextCallback callback = InputContextCallback.getInstance();
@@ -318,7 +366,8 @@
         }
         return value;
     }
-    
+
+    @AnyThread
     public boolean commitText(CharSequence text, int newCursorPosition) {
         try {
             mIInputContext.commitText(text, newCursorPosition);
@@ -328,6 +377,7 @@
         }
     }
 
+    @AnyThread
     public boolean commitCompletion(CompletionInfo text) {
         if (isMethodMissing(MissingMethodFlags.COMMIT_CORRECTION)) {
             // This method is not implemented.
@@ -341,6 +391,7 @@
         }
     }
 
+    @AnyThread
     public boolean commitCorrection(CorrectionInfo correctionInfo) {
         try {
             mIInputContext.commitCorrection(correctionInfo);
@@ -350,6 +401,7 @@
         }
     }
 
+    @AnyThread
     public boolean setSelection(int start, int end) {
         try {
             mIInputContext.setSelection(start, end);
@@ -358,7 +410,8 @@
             return false;
         }
     }
-    
+
+    @AnyThread
     public boolean performEditorAction(int actionCode) {
         try {
             mIInputContext.performEditorAction(actionCode);
@@ -367,7 +420,8 @@
             return false;
         }
     }
-    
+
+    @AnyThread
     public boolean performContextMenuAction(int id) {
         try {
             mIInputContext.performContextMenuAction(id);
@@ -377,6 +431,7 @@
         }
     }
 
+    @AnyThread
     public boolean setComposingRegion(int start, int end) {
         if (isMethodMissing(MissingMethodFlags.SET_COMPOSING_REGION)) {
             // This method is not implemented.
@@ -390,6 +445,7 @@
         }
     }
 
+    @AnyThread
     public boolean setComposingText(CharSequence text, int newCursorPosition) {
         try {
             mIInputContext.setComposingText(text, newCursorPosition);
@@ -399,6 +455,7 @@
         }
     }
 
+    @AnyThread
     public boolean finishComposingText() {
         try {
             mIInputContext.finishComposingText();
@@ -408,6 +465,7 @@
         }
     }
 
+    @AnyThread
     public boolean beginBatchEdit() {
         try {
             mIInputContext.beginBatchEdit();
@@ -416,7 +474,8 @@
             return false;
         }
     }
-    
+
+    @AnyThread
     public boolean endBatchEdit() {
         try {
             mIInputContext.endBatchEdit();
@@ -425,7 +484,8 @@
             return false;
         }
     }
-    
+
+    @AnyThread
     public boolean sendKeyEvent(KeyEvent event) {
         try {
             mIInputContext.sendKeyEvent(event);
@@ -435,6 +495,7 @@
         }
     }
 
+    @AnyThread
     public boolean clearMetaKeyStates(int states) {
         try {
             mIInputContext.clearMetaKeyStates(states);
@@ -444,6 +505,7 @@
         }
     }
 
+    @AnyThread
     public boolean deleteSurroundingText(int beforeLength, int afterLength) {
         try {
             mIInputContext.deleteSurroundingText(beforeLength, afterLength);
@@ -453,6 +515,7 @@
         }
     }
 
+    @AnyThread
     public boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength) {
         if (isMethodMissing(MissingMethodFlags.DELETE_SURROUNDING_TEXT_IN_CODE_POINTS)) {
             // This method is not implemented.
@@ -466,11 +529,13 @@
         }
     }
 
+    @AnyThread
     public boolean reportFullscreenMode(boolean enabled) {
         // Nothing should happen when called from input method.
         return false;
     }
 
+    @AnyThread
     public boolean performPrivateCommand(String action, Bundle data) {
         try {
             mIInputContext.performPrivateCommand(action, data);
@@ -480,7 +545,12 @@
         }
     }
 
+    @AnyThread
     public boolean requestCursorUpdates(int cursorUpdateMode) {
+        if (mIsUnbindIssued.get()) {
+            return false;
+        }
+
         boolean result = false;
         if (isMethodMissing(MissingMethodFlags.REQUEST_CURSOR_UPDATES)) {
             // This method is not implemented.
@@ -502,16 +572,23 @@
         return result;
     }
 
+    @AnyThread
     public Handler getHandler() {
         // Nothing should happen when called from input method.
         return null;
     }
 
+    @AnyThread
     public void closeConnection() {
         // Nothing should happen when called from input method.
     }
 
+    @AnyThread
     public boolean commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts) {
+        if (mIsUnbindIssued.get()) {
+            return false;
+        }
+
         boolean result = false;
         if (isMethodMissing(MissingMethodFlags.COMMIT_CONTENT)) {
             // This method is not implemented.
@@ -542,10 +619,12 @@
         return result;
     }
 
+    @AnyThread
     private boolean isMethodMissing(@MissingMethodFlags final int methodFlag) {
         return (mMissingMethods & methodFlag) == methodFlag;
     }
 
+    @AnyThread
     @Override
     public String toString() {
         return "InputConnectionWrapper{idHash=#"
diff --git a/com/android/internal/view/menu/ListMenuItemView.java b/com/android/internal/view/menu/ListMenuItemView.java
index f76c724..8f80bfe 100644
--- a/com/android/internal/view/menu/ListMenuItemView.java
+++ b/com/android/internal/view/menu/ListMenuItemView.java
@@ -319,13 +319,15 @@
     public void setGroupDividerEnabled(boolean groupDividerEnabled) {
         // If mHasListDivider is true, disabling the groupDivider.
         // Otherwise, checking enbling it according to groupDividerEnabled flag.
-        mGroupDivider.setVisibility(!mHasListDivider
-                && groupDividerEnabled ? View.VISIBLE : View.GONE);
+        if (mGroupDivider != null) {
+            mGroupDivider.setVisibility(!mHasListDivider
+                    && groupDividerEnabled ? View.VISIBLE : View.GONE);
+        }
     }
 
     @Override
     public void adjustListItemSelectionBounds(Rect rect) {
-        if (mGroupDivider.getVisibility() == View.VISIBLE) {
+        if (mGroupDivider != null && mGroupDivider.getVisibility() == View.VISIBLE) {
             // groupDivider is a part of MenuItemListView.
             // If ListMenuItem with divider enabled is hovered/clicked, divider also gets selected.
             // Clipping the selector bounds from the top divider portion when divider is enabled,
diff --git a/com/android/internal/view/menu/MenuItemImpl.java b/com/android/internal/view/menu/MenuItemImpl.java
index 15e271e..9d012de 100644
--- a/com/android/internal/view/menu/MenuItemImpl.java
+++ b/com/android/internal/view/menu/MenuItemImpl.java
@@ -658,7 +658,7 @@
 
     @Override
     public boolean requiresOverflow() {
-        return (mShowAsAction & SHOW_AS_OVERFLOW_ALWAYS) == SHOW_AS_OVERFLOW_ALWAYS;
+        return !requiresActionButton() && !requestsActionButton();
     }
 
     public void setIsActionButton(boolean isActionButton) {
diff --git a/com/android/internal/widget/FloatingToolbar.java b/com/android/internal/widget/FloatingToolbar.java
index f63b5a2..e3b1c01 100644
--- a/com/android/internal/widget/FloatingToolbar.java
+++ b/com/android/internal/widget/FloatingToolbar.java
@@ -64,6 +64,7 @@
 import com.android.internal.util.Preconditions;
 
 import java.util.ArrayList;
+import java.util.Comparator;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Objects;
@@ -118,6 +119,36 @@
     };
 
     /**
+     * Sorts the list of menu items to conform to certain requirements.
+     */
+    private final Comparator<MenuItem> mMenuItemComparator = (menuItem1, menuItem2) -> {
+        // Ensure the assist menu item is always the first item:
+        if (menuItem1.getItemId() == android.R.id.textAssist) {
+            return menuItem2.getItemId() == android.R.id.textAssist ? 0 : -1;
+        }
+        if (menuItem2.getItemId() == android.R.id.textAssist) {
+            return 1;
+        }
+
+        // Order by SHOW_AS_ACTION type:
+        if (menuItem1.requiresActionButton()) {
+            return menuItem2.requiresActionButton() ? 0 : -1;
+        }
+        if (menuItem2.requiresActionButton()) {
+            return 1;
+        }
+        if (menuItem1.requiresOverflow()) {
+            return menuItem2.requiresOverflow() ? 0 : 1;
+        }
+        if (menuItem2.requiresOverflow()) {
+            return -1;
+        }
+
+        // Order by order value:
+        return menuItem1.getOrder() - menuItem2.getOrder();
+    };
+
+    /**
      * Initializes a floating toolbar.
      */
     public FloatingToolbar(Window window) {
@@ -230,7 +261,7 @@
 
     private void doShow() {
         List<MenuItem> menuItems = getVisibleAndEnabledMenuItems(mMenu);
-        tidy(menuItems);
+        menuItems.sort(mMenuItemComparator);
         if (!isCurrentlyShowing(menuItems) || mWidthChanged) {
             mPopup.dismiss();
             mPopup.layoutMenuItems(menuItems, mMenuItemClickListener, mSuggestedWidth);
@@ -288,36 +319,6 @@
         return menuItems;
     }
 
-    /**
-     * Update the list of menu items to conform to certain requirements.
-     */
-    private void tidy(List<MenuItem> menuItems) {
-        int assistItemIndex = -1;
-        Drawable assistItemDrawable = null;
-
-        final int size = menuItems.size();
-        for (int i = 0; i < size; i++) {
-            final MenuItem menuItem = menuItems.get(i);
-
-            if (menuItem.getItemId() == android.R.id.textAssist) {
-                assistItemIndex = i;
-                assistItemDrawable = menuItem.getIcon();
-            }
-
-            // Remove icons for all menu items with text.
-            if (!TextUtils.isEmpty(menuItem.getTitle())) {
-                menuItem.setIcon(null);
-            }
-        }
-        if (assistItemIndex > -1) {
-            final MenuItem assistMenuItem = menuItems.remove(assistItemIndex);
-            // Ensure the assist menu item preserves its icon.
-            assistMenuItem.setIcon(assistItemDrawable);
-            // Ensure the assist menu item is always the first item.
-            menuItems.add(0, assistMenuItem);
-        }
-    }
-
     private void registerOrientationHandler() {
         unregisterOrientationHandler();
         mWindow.getDecorView().addOnLayoutChangeListener(mOrientationChangeHandler);
@@ -1148,7 +1149,8 @@
             // add the overflow menu items to the end of the remainingMenuItems list.
             final LinkedList<MenuItem> overflowMenuItems = new LinkedList();
             for (MenuItem menuItem : menuItems) {
-                if (menuItem.requiresOverflow()) {
+                if (menuItem.getItemId() != android.R.id.textAssist
+                        && menuItem.requiresOverflow()) {
                     overflowMenuItems.add(menuItem);
                 } else {
                     remainingMenuItems.add(menuItem);
@@ -1171,7 +1173,9 @@
                     break;
                 }
 
-                View menuItemButton = createMenuItemButton(mContext, menuItem, mIconTextSpacing);
+                final boolean showIcon = isFirstItem && menuItem.getItemId() == R.id.textAssist;
+                final View menuItemButton = createMenuItemButton(
+                        mContext, menuItem, mIconTextSpacing, showIcon);
 
                 // Adding additional start padding for the first button to even out button spacing.
                 if (isFirstItem) {
@@ -1193,16 +1197,17 @@
                 }
 
                 menuItemButton.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
-                final int menuItemButtonWidth = Math.min(menuItemButton.getMeasuredWidth(), toolbarWidth);
+                final int menuItemButtonWidth = Math.min(
+                        menuItemButton.getMeasuredWidth(), toolbarWidth);
 
                 final boolean isNewGroup = !isFirstItem && lastGroupId != menuItem.getGroupId();
                 final int extraPadding = isNewGroup ? menuItemButton.getPaddingEnd() * 2 : 0;
 
                 // Check if we can fit an item while reserving space for the overflowButton.
-                boolean canFitWithOverflow =
+                final boolean canFitWithOverflow =
                         menuItemButtonWidth <=
                                 availableWidth - mOverflowButtonSize.getWidth() - extraPadding;
-                boolean canFitNoOverflow =
+                final boolean canFitNoOverflow =
                         isLastItem && menuItemButtonWidth <= availableWidth - extraPadding;
                 if (canFitWithOverflow || canFitNoOverflow) {
                     if (isNewGroup) {
@@ -1211,7 +1216,8 @@
 
                         // Add extra padding to the end of the previous button.
                         // Half of the extra padding (less borderWidth) goes to the previous button.
-                        View previousButton = mMainPanel.getChildAt(mMainPanel.getChildCount() - 1);
+                        final View previousButton = mMainPanel.getChildAt(
+                                mMainPanel.getChildCount() - 1);
                         final int prevPaddingEnd = previousButton.getPaddingEnd()
                                 + extraPadding / 2 - dividerWidth;
                         previousButton.setPaddingRelative(
@@ -1612,7 +1618,8 @@
             public View getView(MenuItem menuItem, int minimumWidth, View convertView) {
                 Preconditions.checkNotNull(menuItem);
                 if (convertView != null) {
-                    updateMenuItemButton(convertView, menuItem, mIconTextSpacing);
+                    updateMenuItemButton(
+                            convertView, menuItem, mIconTextSpacing, shouldShowIcon(menuItem));
                 } else {
                     convertView = createMenuButton(menuItem);
                 }
@@ -1621,17 +1628,26 @@
             }
 
             public int calculateWidth(MenuItem menuItem) {
-                updateMenuItemButton(mCalculator, menuItem, mIconTextSpacing);
+                updateMenuItemButton(
+                        mCalculator, menuItem, mIconTextSpacing, shouldShowIcon(menuItem));
                 mCalculator.measure(
                         View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
                 return mCalculator.getMeasuredWidth();
             }
 
             private View createMenuButton(MenuItem menuItem) {
-                View button = createMenuItemButton(mContext, menuItem, mIconTextSpacing);
+                View button = createMenuItemButton(
+                        mContext, menuItem, mIconTextSpacing, shouldShowIcon(menuItem));
                 button.setPadding(mSidePadding, 0, mSidePadding, 0);
                 return button;
             }
+
+            private boolean shouldShowIcon(MenuItem menuItem) {
+                if (menuItem != null) {
+                    return menuItem.getGroupId() == android.R.id.textAssist;
+                }
+                return false;
+            }
         }
     }
 
@@ -1639,11 +1655,11 @@
      * Creates and returns a menu button for the specified menu item.
      */
     private static View createMenuItemButton(
-            Context context, MenuItem menuItem, int iconTextSpacing) {
+            Context context, MenuItem menuItem, int iconTextSpacing, boolean showIcon) {
         final View menuItemButton = LayoutInflater.from(context)
                 .inflate(R.layout.floating_popup_menu_button, null);
         if (menuItem != null) {
-            updateMenuItemButton(menuItemButton, menuItem, iconTextSpacing);
+            updateMenuItemButton(menuItemButton, menuItem, iconTextSpacing, showIcon);
         }
         return menuItemButton;
     }
@@ -1652,18 +1668,19 @@
      * Updates the specified menu item button with the specified menu item data.
      */
     private static void updateMenuItemButton(
-            View menuItemButton, MenuItem menuItem, int iconTextSpacing) {
-        final TextView buttonText = (TextView) menuItemButton.findViewById(
+            View menuItemButton, MenuItem menuItem, int iconTextSpacing, boolean showIcon) {
+        final TextView buttonText = menuItemButton.findViewById(
                 R.id.floating_toolbar_menu_item_text);
+        buttonText.setEllipsize(null);
         if (TextUtils.isEmpty(menuItem.getTitle())) {
             buttonText.setVisibility(View.GONE);
         } else {
             buttonText.setVisibility(View.VISIBLE);
             buttonText.setText(menuItem.getTitle());
         }
-        final ImageView buttonIcon = (ImageView) menuItemButton
-                .findViewById(R.id.floating_toolbar_menu_item_image);
-        if (menuItem.getIcon() == null) {
+        final ImageView buttonIcon = menuItemButton.findViewById(
+                R.id.floating_toolbar_menu_item_image);
+        if (menuItem.getIcon() == null || !showIcon) {
             buttonIcon.setVisibility(View.GONE);
             if (buttonText != null) {
                 buttonText.setPaddingRelative(0, 0, 0, 0);
diff --git a/com/android/internal/widget/Magnifier.java b/com/android/internal/widget/Magnifier.java
index 86e7b38..6d54d7b 100644
--- a/com/android/internal/widget/Magnifier.java
+++ b/com/android/internal/widget/Magnifier.java
@@ -36,11 +36,15 @@
 import com.android.internal.R;
 import com.android.internal.util.Preconditions;
 
+import java.util.Timer;
+import java.util.TimerTask;
+
 /**
  * Android magnifier widget. Can be used by any view which is attached to window.
  */
 public final class Magnifier {
     private static final String LOG_TAG = "magnifier";
+    private static final int MAGNIFIER_REFRESH_RATE_MS = 33; // ~30fps
     // The view for which this magnifier is attached.
     private final View mView;
     // The window containing the magnifier.
@@ -58,6 +62,10 @@
     // The callback of the pixel copy request will be invoked on this Handler when
     // the copy is finished.
     private final Handler mPixelCopyHandler = Handler.getMain();
+    // Current magnification scale.
+    private float mScale;
+    // Timer used to schedule the copy task.
+    private Timer mTimer;
 
     /**
      * Initializes a magnifier.
@@ -69,6 +77,7 @@
         mView = Preconditions.checkNotNull(view);
         final Context context = mView.getContext();
         final View content = LayoutInflater.from(context).inflate(R.layout.magnifier, null);
+        content.findViewById(R.id.magnifier_inner).setClipToOutline(true);
         mWindowWidth = context.getResources().getDimensionPixelSize(R.dimen.magnifier_width);
         mWindowHeight = context.getResources().getDimensionPixelSize(R.dimen.magnifier_height);
         final float elevation = context.getResources().getDimension(R.dimen.magnifier_elevation);
@@ -88,16 +97,47 @@
     /**
      * Shows the magnifier on the screen.
      *
-     * @param centerXOnScreen horizontal coordinate of the center point of the magnifier source
-     * @param centerYOnScreen vertical coordinate of the center point of the magnifier source
-     * @param scale the scale at which the magnifier zooms on the source content
+     * @param xPosInView horizontal coordinate of the center point of the magnifier source relative
+     *        to the view. The lower end is clamped to 0
+     * @param yPosInView vertical coordinate of the center point of the magnifier source
+     *        relative to the view. The lower end is clamped to 0
+     * @param scale the scale at which the magnifier zooms on the source content. The
+     *        lower end is clamped to 1 and the higher end to 4
      */
-    public void show(@FloatRange(from=0) float centerXOnScreen,
-            @FloatRange(from=0) float centerYOnScreen,
-            @FloatRange(from=1, to=10) float scale) {
-        maybeResizeBitmap(scale);
-        configureCoordinates(centerXOnScreen, centerYOnScreen);
-        performPixelCopy();
+    public void show(@FloatRange(from=0) float xPosInView,
+            @FloatRange(from=0) float yPosInView,
+            @FloatRange(from=1, to=4) float scale) {
+        if (scale > 4) {
+            scale = 4;
+        }
+
+        if (scale < 1) {
+            scale = 1;
+        }
+
+        if (xPosInView < 0) {
+            xPosInView = 0;
+        }
+
+        if (yPosInView < 0) {
+            yPosInView = 0;
+        }
+
+        if (mScale != scale) {
+            resizeBitmap(scale);
+        }
+        mScale = scale;
+        configureCoordinates(xPosInView, yPosInView);
+
+        if (mTimer == null) {
+            mTimer = new Timer();
+            mTimer.schedule(new TimerTask() {
+                @Override
+                public void run() {
+                    performPixelCopy();
+                }
+            }, 0 /* delay */, MAGNIFIER_REFRESH_RATE_MS);
+        }
 
         if (mWindow.isShowing()) {
             mWindow.update(mWindowCoords.x, mWindowCoords.y, mWindow.getWidth(),
@@ -113,6 +153,12 @@
      */
     public void dismiss() {
         mWindow.dismiss();
+
+        if (mTimer != null) {
+            mTimer.cancel();
+            mTimer.purge();
+            mTimer = null;
+        }
     }
 
     /**
@@ -129,39 +175,40 @@
         return mWindowWidth;
     }
 
-    private void maybeResizeBitmap(float scale) {
+    private void resizeBitmap(float scale) {
         final int bitmapWidth = (int) (mWindowWidth / scale);
         final int bitmapHeight = (int) (mWindowHeight / scale);
-        if (mBitmap.getWidth() != bitmapWidth || mBitmap.getHeight() != bitmapHeight) {
-            mBitmap.reconfigure(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888);
-            getImageView().setImageBitmap(mBitmap);
-        }
+        mBitmap.reconfigure(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888);
+        getImageView().setImageBitmap(mBitmap);
     }
 
-    private void configureCoordinates(float posXOnScreen, float posYOnScreen) {
+    private void configureCoordinates(float xPosInView, float yPosInView) {
+        final int[] coordinatesOnScreen = new int[2];
+        mView.getLocationOnScreen(coordinatesOnScreen);
+        final float posXOnScreen = xPosInView + coordinatesOnScreen[0];
+        final float posYOnScreen = yPosInView + coordinatesOnScreen[1];
+
         mCenterZoomCoords.x = (int) posXOnScreen;
         mCenterZoomCoords.y = (int) posYOnScreen;
 
         final int verticalMagnifierOffset = mView.getContext().getResources().getDimensionPixelSize(
                 R.dimen.magnifier_offset);
-        final int availableTopSpace = (mCenterZoomCoords.y - mWindowHeight / 2)
-                - verticalMagnifierOffset - (mBitmap.getHeight() / 2);
-
         mWindowCoords.x = mCenterZoomCoords.x - mWindowWidth / 2;
-        mWindowCoords.y = mCenterZoomCoords.y - mWindowHeight / 2
-                + verticalMagnifierOffset * (availableTopSpace > 0 ? -1 : 1);
+        mWindowCoords.y = mCenterZoomCoords.y - mWindowHeight / 2 - verticalMagnifierOffset;
     }
 
     private void performPixelCopy() {
-        int startX = mCenterZoomCoords.x - mBitmap.getWidth() / 2;
+        final int startY = mCenterZoomCoords.y - mBitmap.getHeight() / 2;
+        int rawStartX = mCenterZoomCoords.x - mBitmap.getWidth() / 2;
+
         // Clamp startX value to avoid distorting the rendering of the magnifier content.
-        if (startX < 0) {
-            startX = 0;
-        } else if (startX + mBitmap.getWidth() > mView.getWidth()) {
-            startX = mView.getWidth() - mBitmap.getWidth();
+        if (rawStartX < 0) {
+            rawStartX = 0;
+        } else if (rawStartX + mBitmap.getWidth() > mView.getWidth()) {
+            rawStartX = mView.getWidth() - mBitmap.getWidth();
         }
 
-        final int startY = mCenterZoomCoords.y - mBitmap.getHeight() / 2;
+        final int startX = rawStartX;
         final ViewRootImpl viewRootImpl = mView.getViewRootImpl();
 
         if (viewRootImpl != null && viewRootImpl.mSurface != null
diff --git a/com/android/keyguard/CarrierText.java b/com/android/keyguard/CarrierText.java
index 159ac4c..13c48d0 100644
--- a/com/android/keyguard/CarrierText.java
+++ b/com/android/keyguard/CarrierText.java
@@ -39,6 +39,7 @@
 import com.android.internal.telephony.IccCardConstants.State;
 import com.android.internal.telephony.TelephonyIntents;
 import com.android.settingslib.WirelessUtils;
+import android.telephony.TelephonyManager;
 
 public class CarrierText extends TextView {
     private static final boolean DEBUG = KeyguardConstants.DEBUG;
@@ -52,6 +53,8 @@
 
     private WifiManager mWifiManager;
 
+    private boolean[] mSimErrorState = new boolean[TelephonyManager.getDefault().getPhoneCount()];
+
     private KeyguardUpdateMonitorCallback mCallback = new KeyguardUpdateMonitorCallback() {
         @Override
         public void onRefreshCarrierInfo() {
@@ -65,6 +68,22 @@
         public void onStartedWakingUp() {
             setSelected(true);
         };
+
+        public void onSimStateChanged(int subId, int slotId, IccCardConstants.State simState) {
+            if (slotId < 0) {
+                Log.d(TAG, "onSimStateChanged() - slotId invalid: " + slotId);
+                return;
+            }
+
+            if (DEBUG) Log.d(TAG,"onSimStateChanged: " + getStatusForIccState(simState));
+            if (getStatusForIccState(simState) == StatusMode.SimIoError) {
+                mSimErrorState[slotId] = true;
+                updateCarrierText();
+            } else if (mSimErrorState[slotId]) {
+                mSimErrorState[slotId] = false;
+                updateCarrierText();
+            }
+        };
     };
     /**
      * The status of this lock screen. Primarily used for widgets on LockScreen.
@@ -77,7 +96,8 @@
         SimPukLocked, // SIM card is PUK locked because SIM entered wrong too many times
         SimLocked, // SIM card is currently locked
         SimPermDisabled, // SIM card is permanently disabled due to PUK unlock failure
-        SimNotReady; // SIM is not ready yet. May never be on devices w/o a SIM.
+        SimNotReady, // SIM is not ready yet. May never be on devices w/o a SIM.
+        SimIoError; // SIM card is faulty
     }
 
     public CarrierText(Context context) {
@@ -101,6 +121,35 @@
         mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
     }
 
+    /**
+     * Checks if there are faulty cards. Adds the text depending on the slot of the card
+     * @param text: current carrier text based on the sim state
+     * @param noSims: whether a valid sim card is inserted
+     * @return text
+    */
+    private CharSequence updateCarrierTextWithSimIoError(CharSequence text, boolean noSims) {
+        final CharSequence carrier = "";
+        CharSequence carrierTextForSimIOError = getCarrierTextForSimState(
+            IccCardConstants.State.CARD_IO_ERROR, carrier);
+        for (int index = 0; index < mSimErrorState.length; index++) {
+            if (mSimErrorState[index]) {
+                // In the case when no sim cards are detected but a faulty card is inserted
+                // overwrite the text and only show "Invalid card"
+                if (noSims) {
+                    return concatenate(carrierTextForSimIOError,
+                        getContext().getText(com.android.internal.R.string.emergency_calls_only));
+                } else if (index == 0) {
+                    // prepend "Invalid card" when faulty card is inserted in slot 0
+                    text = concatenate(carrierTextForSimIOError, text);
+                } else {
+                    // concatenate "Invalid card" when faulty card is inserted in slot 1
+                    text = concatenate(text, carrierTextForSimIOError);
+                }
+            }
+        }
+        return text;
+    }
+
     protected void updateCarrierText() {
         boolean allSimsMissing = true;
         boolean anySimReadyAndInService = false;
@@ -179,6 +228,7 @@
             }
         }
 
+        displayText = updateCarrierTextWithSimIoError(displayText, allSimsMissing);
         // APM (airplane mode) != no carrier state. There are carrier services
         // (e.g. WFC = Wi-Fi calling) which may operate in APM.
         if (!anySimReadyAndInService && WirelessUtils.isAirplaneModeOn(mContext)) {
@@ -270,6 +320,11 @@
                         getContext().getText(R.string.keyguard_sim_puk_locked_message),
                         text);
                 break;
+            case SimIoError:
+                carrierText = makeCarrierStringOnEmergencyCapable(
+                        getContext().getText(R.string.keyguard_sim_error_message_short),
+                        text);
+                break;
         }
 
         return carrierText;
@@ -319,6 +374,8 @@
                 return StatusMode.SimPermDisabled;
             case UNKNOWN:
                 return StatusMode.SimMissing;
+            case CARD_IO_ERROR:
+                return StatusMode.SimIoError;
         }
         return StatusMode.SimMissing;
     }
diff --git a/com/android/keyguard/KeyguardSecurityModel.java b/com/android/keyguard/KeyguardSecurityModel.java
index 7baa57e..0cb6423 100644
--- a/com/android/keyguard/KeyguardSecurityModel.java
+++ b/com/android/keyguard/KeyguardSecurityModel.java
@@ -57,16 +57,16 @@
     SecurityMode getSecurityMode(int userId) {
         KeyguardUpdateMonitor monitor = KeyguardUpdateMonitor.getInstance(mContext);
 
-        if (SubscriptionManager.isValidSubscriptionId(
-                monitor.getNextSubIdForState(IccCardConstants.State.PIN_REQUIRED))) {
-            return SecurityMode.SimPin;
-        }
-
         if (mIsPukScreenAvailable && SubscriptionManager.isValidSubscriptionId(
                 monitor.getNextSubIdForState(IccCardConstants.State.PUK_REQUIRED))) {
             return SecurityMode.SimPuk;
         }
 
+        if (SubscriptionManager.isValidSubscriptionId(
+                monitor.getNextSubIdForState(IccCardConstants.State.PIN_REQUIRED))) {
+            return SecurityMode.SimPin;
+        }
+
         final int security = mLockPatternUtils.getActivePasswordQuality(userId);
         switch (security) {
             case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC:
diff --git a/com/android/keyguard/KeyguardUpdateMonitor.java b/com/android/keyguard/KeyguardUpdateMonitor.java
index d83a6c6..2bb992c 100644
--- a/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -52,7 +52,6 @@
 import android.os.BatteryManager;
 import android.os.CancellationSignal;
 import android.os.Handler;
-import android.os.IBinder;
 import android.os.IRemoteCallback;
 import android.os.Message;
 import android.os.RemoteException;
@@ -78,7 +77,7 @@
 import com.android.internal.telephony.TelephonyIntents;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener;
+import com.android.systemui.recents.misc.TaskStackChangeListener;
 
 import com.google.android.collect.Lists;
 
@@ -902,6 +901,8 @@
                 }
             } else if (IccCardConstants.INTENT_VALUE_LOCKED_NETWORK.equals(stateExtra)) {
                 state = IccCardConstants.State.NETWORK_LOCKED;
+            } else if (IccCardConstants.INTENT_VALUE_ICC_CARD_IO_ERROR.equals(stateExtra)) {
+                state = IccCardConstants.State.CARD_IO_ERROR;
             } else if (IccCardConstants.INTENT_VALUE_ICC_LOADED.equals(stateExtra)
                         || IccCardConstants.INTENT_VALUE_ICC_IMSI.equals(stateExtra)) {
                 // This is required because telephony doesn't return to "READY" after
@@ -1771,7 +1772,7 @@
         }
     }
 
-    private final TaskStackListener mTaskStackListener = new TaskStackListener() {
+    private final TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
         @Override
         public void onTaskStackChangedBackground() {
             try {
diff --git a/com/android/layoutlib/bridge/Bridge.java b/com/android/layoutlib/bridge/Bridge.java
index 0cfc181..5dca8e7 100644
--- a/com/android/layoutlib/bridge/Bridge.java
+++ b/com/android/layoutlib/bridge/Bridge.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016 The Android Open Source Project
+ * Copyright (C) 2008 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.
@@ -14,62 +14,659 @@
  * limitations under the License.
  */
 
-package com.android.layoutlib.bridge;import com.android.ide.common.rendering.api.RenderSession;
+package com.android.layoutlib.bridge;
+
+import com.android.ide.common.rendering.api.Capability;
+import com.android.ide.common.rendering.api.DrawableParams;
+import com.android.ide.common.rendering.api.Features;
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.ide.common.rendering.api.RenderSession;
 import com.android.ide.common.rendering.api.Result;
 import com.android.ide.common.rendering.api.Result.Status;
 import com.android.ide.common.rendering.api.SessionParams;
+import com.android.layoutlib.bridge.android.RenderParamsFlags;
+import com.android.layoutlib.bridge.impl.RenderDrawable;
+import com.android.layoutlib.bridge.impl.RenderSessionImpl;
+import com.android.layoutlib.bridge.util.DynamicIdMap;
+import com.android.ninepatch.NinePatchChunk;
+import com.android.resources.ResourceType;
+import com.android.tools.layoutlib.create.MethodAdapter;
+import com.android.tools.layoutlib.create.OverrideMethod;
+import com.android.util.Pair;
 
-import java.awt.Graphics2D;
-import java.awt.image.BufferedImage;
+import android.annotation.NonNull;
+import android.content.res.BridgeAssetManager;
+import android.graphics.Bitmap;
+import android.graphics.FontFamily_Delegate;
+import android.graphics.Typeface;
+import android.graphics.Typeface_Delegate;
+import android.icu.util.ULocale;
+import android.os.Looper;
+import android.os.Looper_Accessor;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+
+import java.io.File;
+import java.lang.ref.SoftReference;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+import java.util.EnumMap;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.WeakHashMap;
+import java.util.concurrent.locks.ReentrantLock;
+
+import libcore.io.MemoryMappedFile_Delegate;
+
+import static com.android.ide.common.rendering.api.Result.Status.ERROR_UNKNOWN;
 
 /**
- * Legacy Bridge used in the SDK version of layoutlib
+ * Main entry point of the LayoutLib Bridge.
+ * <p/>To use this bridge, simply instantiate an object of type {@link Bridge} and call
+ * {@link #createSession(SessionParams)}
  */
 public final class Bridge extends com.android.ide.common.rendering.api.Bridge {
-    private static final String SDK_NOT_SUPPORTED = "The SDK layoutlib version is not supported";
-    private static final Result NOT_SUPPORTED_RESULT =
-            Status.NOT_IMPLEMENTED.createResult(SDK_NOT_SUPPORTED);
-    private static BufferedImage sImage;
 
-    private static class BridgeRenderSession extends RenderSession {
+    private static final String ICU_LOCALE_DIRECTION_RTL = "right-to-left";
 
-        @Override
-        public synchronized BufferedImage getImage() {
-            if (sImage == null) {
-                sImage = new BufferedImage(500, 500, BufferedImage.TYPE_INT_ARGB);
-                Graphics2D g = sImage.createGraphics();
-                g.clearRect(0, 0, 500, 500);
-                g.drawString(SDK_NOT_SUPPORTED, 20, 20);
-                g.dispose();
-            }
+    public static class StaticMethodNotImplementedException extends RuntimeException {
+        private static final long serialVersionUID = 1L;
 
-            return sImage;
-        }
-
-        @Override
-        public Result render(long timeout, boolean forceMeasure) {
-            return NOT_SUPPORTED_RESULT;
-        }
-
-        @Override
-        public Result measure(long timeout) {
-            return NOT_SUPPORTED_RESULT;
-        }
-
-        @Override
-        public Result getResult() {
-            return NOT_SUPPORTED_RESULT;
+        public StaticMethodNotImplementedException(String msg) {
+            super(msg);
         }
     }
 
+    /**
+     * Lock to ensure only one rendering/inflating happens at a time.
+     * This is due to some singleton in the Android framework.
+     */
+    private final static ReentrantLock sLock = new ReentrantLock();
 
-    @Override
-    public RenderSession createSession(SessionParams params) {
-        return new BridgeRenderSession();
-    }
+    /**
+     * Maps from id to resource type/name. This is for com.android.internal.R
+     */
+    @SuppressWarnings("deprecation")
+    private final static Map<Integer, Pair<ResourceType, String>> sRMap = new HashMap<>();
+
+    /**
+     * Reverse map compared to sRMap, resource type -> (resource name -> id).
+     * This is for com.android.internal.R.
+     */
+    private final static Map<ResourceType, Map<String, Integer>> sRevRMap = new EnumMap<>(ResourceType.class);
+
+    // framework resources are defined as 0x01XX#### where XX is the resource type (layout,
+    // drawable, etc...). Using FF as the type allows for 255 resource types before we get a
+    // collision which should be fine.
+    private final static int DYNAMIC_ID_SEED_START = 0x01ff0000;
+    private final static DynamicIdMap sDynamicIds = new DynamicIdMap(DYNAMIC_ID_SEED_START);
+
+    private final static Map<Object, Map<String, SoftReference<Bitmap>>> sProjectBitmapCache =
+            new WeakHashMap<>();
+    private final static Map<Object, Map<String, SoftReference<NinePatchChunk>>> sProject9PatchCache =
+            new WeakHashMap<>();
+
+    private final static Map<String, SoftReference<Bitmap>> sFrameworkBitmapCache = new HashMap<>();
+    private final static Map<String, SoftReference<NinePatchChunk>> sFramework9PatchCache =
+            new HashMap<>();
+
+    private static Map<String, Map<String, Integer>> sEnumValueMap;
+    private static Map<String, String> sPlatformProperties;
+
+    /**
+     * A default log than prints to stdout/stderr.
+     */
+    private final static LayoutLog sDefaultLog = new LayoutLog() {
+        @Override
+        public void error(String tag, String message, Object data) {
+            System.err.println(message);
+        }
+
+        @Override
+        public void error(String tag, String message, Throwable throwable, Object data) {
+            System.err.println(message);
+        }
+
+        @Override
+        public void warning(String tag, String message, Object data) {
+            System.out.println(message);
+        }
+    };
+
+    /**
+     * Current log.
+     */
+    private static LayoutLog sCurrentLog = sDefaultLog;
+
+    private static final int LAST_SUPPORTED_FEATURE = Features.THEME_PREVIEW_NAVIGATION_BAR;
 
     @Override
     public int getApiLevel() {
-        return 0;
+        return com.android.ide.common.rendering.api.Bridge.API_CURRENT;
+    }
+
+    @SuppressWarnings("deprecation")
+    @Override
+    @Deprecated
+    public EnumSet<Capability> getCapabilities() {
+        // The Capability class is deprecated and frozen. All Capabilities enumerated there are
+        // supported by this version of LayoutLibrary. So, it's safe to use EnumSet.allOf()
+        return EnumSet.allOf(Capability.class);
+    }
+
+    @Override
+    public boolean supports(int feature) {
+        return feature <= LAST_SUPPORTED_FEATURE;
+    }
+
+    @Override
+    public boolean init(Map<String,String> platformProperties,
+            File fontLocation,
+            Map<String, Map<String, Integer>> enumValueMap,
+            LayoutLog log) {
+        sPlatformProperties = platformProperties;
+        sEnumValueMap = enumValueMap;
+
+        BridgeAssetManager.initSystem();
+
+        // When DEBUG_LAYOUT is set and is not 0 or false, setup a default listener
+        // on static (native) methods which prints the signature on the console and
+        // throws an exception.
+        // This is useful when testing the rendering in ADT to identify static native
+        // methods that are ignored -- layoutlib_create makes them returns 0/false/null
+        // which is generally OK yet might be a problem, so this is how you'd find out.
+        //
+        // Currently layoutlib_create only overrides static native method.
+        // Static non-natives are not overridden and thus do not get here.
+        final String debug = System.getenv("DEBUG_LAYOUT");
+        if (debug != null && !debug.equals("0") && !debug.equals("false")) {
+
+            OverrideMethod.setDefaultListener(new MethodAdapter() {
+                @Override
+                public void onInvokeV(String signature, boolean isNative, Object caller) {
+                    sDefaultLog.error(null, "Missing Stub: " + signature +
+                            (isNative ? " (native)" : ""), null /*data*/);
+
+                    if (debug.equalsIgnoreCase("throw")) {
+                        // Throwing this exception doesn't seem that useful. It breaks
+                        // the layout editor yet doesn't display anything meaningful to the
+                        // user. Having the error in the console is just as useful. We'll
+                        // throw it only if the environment variable is "throw" or "THROW".
+                        throw new StaticMethodNotImplementedException(signature);
+                    }
+                }
+            });
+        }
+
+        // load the fonts.
+        FontFamily_Delegate.setFontLocation(fontLocation.getAbsolutePath());
+        MemoryMappedFile_Delegate.setDataDir(fontLocation.getAbsoluteFile().getParentFile());
+
+        // now parse com.android.internal.R (and only this one as android.R is a subset of
+        // the internal version), and put the content in the maps.
+        try {
+            Class<?> r = com.android.internal.R.class;
+            // Parse the styleable class first, since it may contribute to attr values.
+            parseStyleable();
+
+            for (Class<?> inner : r.getDeclaredClasses()) {
+                if (inner == com.android.internal.R.styleable.class) {
+                    // Already handled the styleable case. Not skipping attr, as there may be attrs
+                    // that are not referenced from styleables.
+                    continue;
+                }
+                String resTypeName = inner.getSimpleName();
+                ResourceType resType = ResourceType.getEnum(resTypeName);
+                if (resType != null) {
+                    Map<String, Integer> fullMap = null;
+                    switch (resType) {
+                        case ATTR:
+                            fullMap = sRevRMap.get(ResourceType.ATTR);
+                            break;
+                        case STRING:
+                        case STYLE:
+                            // Slightly less than thousand entries in each.
+                            fullMap = new HashMap<>(1280);
+                            // no break.
+                        default:
+                            if (fullMap == null) {
+                                fullMap = new HashMap<>();
+                            }
+                            sRevRMap.put(resType, fullMap);
+                    }
+
+                    for (Field f : inner.getDeclaredFields()) {
+                        // only process static final fields. Since the final attribute may have
+                        // been altered by layoutlib_create, we only check static
+                        if (!isValidRField(f)) {
+                            continue;
+                        }
+                        Class<?> type = f.getType();
+                        if (!type.isArray()) {
+                            Integer value = (Integer) f.get(null);
+                            //noinspection deprecation
+                            sRMap.put(value, Pair.of(resType, f.getName()));
+                            fullMap.put(f.getName(), value);
+                        }
+                    }
+                }
+            }
+        } catch (Exception throwable) {
+            if (log != null) {
+                log.error(LayoutLog.TAG_BROKEN,
+                        "Failed to load com.android.internal.R from the layout library jar",
+                        throwable, null);
+            }
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Tests if the field is pubic, static and one of int or int[].
+     */
+    private static boolean isValidRField(Field field) {
+        int modifiers = field.getModifiers();
+        boolean isAcceptable = Modifier.isPublic(modifiers) && Modifier.isStatic(modifiers);
+        Class<?> type = field.getType();
+        return isAcceptable && type == int.class ||
+                (type.isArray() && type.getComponentType() == int.class);
+
+    }
+
+    private static void parseStyleable() throws Exception {
+        // R.attr doesn't contain all the needed values. There are too many resources in the
+        // framework for all to be in the R class. Only the ones specified manually in
+        // res/values/symbols.xml are put in R class. Since, we need to create a map of all attr
+        // values, we try and find them from the styleables.
+
+        // There were 1500 elements in this map at M timeframe.
+        Map<String, Integer> revRAttrMap = new HashMap<>(2048);
+        sRevRMap.put(ResourceType.ATTR, revRAttrMap);
+        // There were 2000 elements in this map at M timeframe.
+        Map<String, Integer> revRStyleableMap = new HashMap<>(3072);
+        sRevRMap.put(ResourceType.STYLEABLE, revRStyleableMap);
+        Class<?> c = com.android.internal.R.styleable.class;
+        Field[] fields = c.getDeclaredFields();
+        // Sort the fields to bring all arrays to the beginning, so that indices into the array are
+        // able to refer back to the arrays (i.e. no forward references).
+        Arrays.sort(fields, (o1, o2) -> {
+            if (o1 == o2) {
+                return 0;
+            }
+            Class<?> t1 = o1.getType();
+            Class<?> t2 = o2.getType();
+            if (t1.isArray() && !t2.isArray()) {
+                return -1;
+            } else if (t2.isArray() && !t1.isArray()) {
+                return 1;
+            }
+            return o1.getName().compareTo(o2.getName());
+        });
+        Map<String, int[]> styleables = new HashMap<>();
+        for (Field field : fields) {
+            if (!isValidRField(field)) {
+                // Only consider public static fields that are int or int[].
+                // Don't check the final flag as it may have been modified by layoutlib_create.
+                continue;
+            }
+            String name = field.getName();
+            if (field.getType().isArray()) {
+                int[] styleableValue = (int[]) field.get(null);
+                styleables.put(name, styleableValue);
+                continue;
+            }
+            // Not an array.
+            String arrayName = name;
+            int[] arrayValue = null;
+            int index;
+            while ((index = arrayName.lastIndexOf('_')) >= 0) {
+                // Find the name of the corresponding styleable.
+                // Search in reverse order so that attrs like LinearLayout_Layout_layout_gravity
+                // are mapped to LinearLayout_Layout and not to LinearLayout.
+                arrayName = arrayName.substring(0, index);
+                arrayValue = styleables.get(arrayName);
+                if (arrayValue != null) {
+                    break;
+                }
+            }
+            index = (Integer) field.get(null);
+            if (arrayValue != null) {
+                String attrName = name.substring(arrayName.length() + 1);
+                int attrValue = arrayValue[index];
+                //noinspection deprecation
+                sRMap.put(attrValue, Pair.of(ResourceType.ATTR, attrName));
+                revRAttrMap.put(attrName, attrValue);
+            }
+            //noinspection deprecation
+            sRMap.put(index, Pair.of(ResourceType.STYLEABLE, name));
+            revRStyleableMap.put(name, index);
+        }
+    }
+
+    @Override
+    public boolean dispose() {
+        BridgeAssetManager.clearSystem();
+
+        // dispose of the default typeface.
+        Typeface_Delegate.resetDefaults();
+        Typeface.sDynamicTypefaceCache.evictAll();
+        sProject9PatchCache.clear();
+        sProjectBitmapCache.clear();
+
+        return true;
+    }
+
+    /**
+     * Starts a layout session by inflating and rendering it. The method returns a
+     * {@link RenderSession} on which further actions can be taken.
+     * <p/>
+     * If {@link SessionParams} includes the {@link RenderParamsFlags#FLAG_DO_NOT_RENDER_ON_CREATE},
+     * this method will only inflate the layout but will NOT render it.
+     * @param params the {@link SessionParams} object with all the information necessary to create
+     *           the scene.
+     * @return a new {@link RenderSession} object that contains the result of the layout.
+     * @since 5
+     */
+    @Override
+    public RenderSession createSession(SessionParams params) {
+        try {
+            Result lastResult;
+            RenderSessionImpl scene = new RenderSessionImpl(params);
+            try {
+                prepareThread();
+                lastResult = scene.init(params.getTimeout());
+                if (lastResult.isSuccess()) {
+                    lastResult = scene.inflate();
+
+                    boolean doNotRenderOnCreate = Boolean.TRUE.equals(
+                            params.getFlag(RenderParamsFlags.FLAG_DO_NOT_RENDER_ON_CREATE));
+                    if (lastResult.isSuccess() && !doNotRenderOnCreate) {
+                        lastResult = scene.render(true /*freshRender*/);
+                    }
+                }
+            } finally {
+                scene.release();
+                cleanupThread();
+            }
+
+            return new BridgeRenderSession(scene, lastResult);
+        } catch (Throwable t) {
+            // get the real cause of the exception.
+            Throwable t2 = t;
+            while (t2.getCause() != null) {
+                t2 = t2.getCause();
+            }
+            return new BridgeRenderSession(null,
+                    ERROR_UNKNOWN.createResult(t2.getMessage(), t));
+        }
+    }
+
+    @Override
+    public Result renderDrawable(DrawableParams params) {
+        try {
+            Result lastResult;
+            RenderDrawable action = new RenderDrawable(params);
+            try {
+                prepareThread();
+                lastResult = action.init(params.getTimeout());
+                if (lastResult.isSuccess()) {
+                    lastResult = action.render();
+                }
+            } finally {
+                action.release();
+                cleanupThread();
+            }
+
+            return lastResult;
+        } catch (Throwable t) {
+            // get the real cause of the exception.
+            Throwable t2 = t;
+            while (t2.getCause() != null) {
+                t2 = t.getCause();
+            }
+            return ERROR_UNKNOWN.createResult(t2.getMessage(), t);
+        }
+    }
+
+    @Override
+    public void clearCaches(Object projectKey) {
+        if (projectKey != null) {
+            sProjectBitmapCache.remove(projectKey);
+            sProject9PatchCache.remove(projectKey);
+        }
+    }
+
+    @Override
+    public Result getViewParent(Object viewObject) {
+        if (viewObject instanceof View) {
+            return Status.SUCCESS.createResult(((View)viewObject).getParent());
+        }
+
+        throw new IllegalArgumentException("viewObject is not a View");
+    }
+
+    @Override
+    public Result getViewIndex(Object viewObject) {
+        if (viewObject instanceof View) {
+            View view = (View) viewObject;
+            ViewParent parentView = view.getParent();
+
+            if (parentView instanceof ViewGroup) {
+                Status.SUCCESS.createResult(((ViewGroup) parentView).indexOfChild(view));
+            }
+
+            return Status.SUCCESS.createResult();
+        }
+
+        throw new IllegalArgumentException("viewObject is not a View");
+    }
+
+    @Override
+    public boolean isRtl(String locale) {
+        return isLocaleRtl(locale);
+    }
+
+    public static boolean isLocaleRtl(String locale) {
+        if (locale == null) {
+            locale = "";
+        }
+        ULocale uLocale = new ULocale(locale);
+        return uLocale.getCharacterOrientation().equals(ICU_LOCALE_DIRECTION_RTL);
+    }
+
+    /**
+     * Returns the lock for the bridge
+     */
+    public static ReentrantLock getLock() {
+        return sLock;
+    }
+
+    /**
+     * Prepares the current thread for rendering.
+     *
+     * Note that while this can be called several time, the first call to {@link #cleanupThread()}
+     * will do the clean-up, and make the thread unable to do further scene actions.
+     */
+    public synchronized static void prepareThread() {
+        // we need to make sure the Looper has been initialized for this thread.
+        // this is required for View that creates Handler objects.
+        if (Looper.myLooper() == null) {
+            Looper.prepareMainLooper();
+        }
+    }
+
+    /**
+     * Cleans up thread-specific data. After this, the thread cannot be used for scene actions.
+     * <p>
+     * Note that it doesn't matter how many times {@link #prepareThread()} was called, a single
+     * call to this will prevent the thread from doing further scene actions
+     */
+    public synchronized static void cleanupThread() {
+        // clean up the looper
+        Looper_Accessor.cleanupThread();
+    }
+
+    public static LayoutLog getLog() {
+        return sCurrentLog;
+    }
+
+    public static void setLog(LayoutLog log) {
+        // check only the thread currently owning the lock can do this.
+        if (!sLock.isHeldByCurrentThread()) {
+            throw new IllegalStateException("scene must be acquired first. see #acquire(long)");
+        }
+
+        if (log != null) {
+            sCurrentLog = log;
+        } else {
+            sCurrentLog = sDefaultLog;
+        }
+    }
+
+    /**
+     * Returns details of a framework resource from its integer value.
+     * @param value the integer value
+     * @return a Pair containing the resource type and name, or null if the id
+     *     does not match any resource.
+     */
+    @SuppressWarnings("deprecation")
+    public static Pair<ResourceType, String> resolveResourceId(int value) {
+        Pair<ResourceType, String> pair = sRMap.get(value);
+        if (pair == null) {
+            pair = sDynamicIds.resolveId(value);
+        }
+        return pair;
+    }
+
+    /**
+     * Returns the integer id of a framework resource, from a given resource type and resource name.
+     * <p/>
+     * If no resource is found, it creates a dynamic id for the resource.
+     *
+     * @param type the type of the resource
+     * @param name the name of the resource.
+     *
+     * @return an {@link Integer} containing the resource id.
+     */
+    @NonNull
+    public static Integer getResourceId(ResourceType type, String name) {
+        Map<String, Integer> map = sRevRMap.get(type);
+        Integer value = null;
+        if (map != null) {
+            value = map.get(name);
+        }
+
+        return value == null ? sDynamicIds.getId(type, name) : value;
+
+    }
+
+    /**
+     * Returns the list of possible enums for a given attribute name.
+     */
+    public static Map<String, Integer> getEnumValues(String attributeName) {
+        if (sEnumValueMap != null) {
+            return sEnumValueMap.get(attributeName);
+        }
+
+        return null;
+    }
+
+    /**
+     * Returns the platform build properties.
+     */
+    public static Map<String, String> getPlatformProperties() {
+        return sPlatformProperties;
+    }
+
+    /**
+     * Returns the bitmap for a specific path, from a specific project cache, or from the
+     * framework cache.
+     * @param value the path of the bitmap
+     * @param projectKey the key of the project, or null to query the framework cache.
+     * @return the cached Bitmap or null if not found.
+     */
+    public static Bitmap getCachedBitmap(String value, Object projectKey) {
+        if (projectKey != null) {
+            Map<String, SoftReference<Bitmap>> map = sProjectBitmapCache.get(projectKey);
+            if (map != null) {
+                SoftReference<Bitmap> ref = map.get(value);
+                if (ref != null) {
+                    return ref.get();
+                }
+            }
+        } else {
+            SoftReference<Bitmap> ref = sFrameworkBitmapCache.get(value);
+            if (ref != null) {
+                return ref.get();
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Sets a bitmap in a project cache or in the framework cache.
+     * @param value the path of the bitmap
+     * @param bmp the Bitmap object
+     * @param projectKey the key of the project, or null to put the bitmap in the framework cache.
+     */
+    public static void setCachedBitmap(String value, Bitmap bmp, Object projectKey) {
+        if (projectKey != null) {
+            Map<String, SoftReference<Bitmap>> map =
+                    sProjectBitmapCache.computeIfAbsent(projectKey, k -> new HashMap<>());
+
+            map.put(value, new SoftReference<>(bmp));
+        } else {
+            sFrameworkBitmapCache.put(value, new SoftReference<>(bmp));
+        }
+    }
+
+    /**
+     * Returns the 9 patch chunk for a specific path, from a specific project cache, or from the
+     * framework cache.
+     * @param value the path of the 9 patch
+     * @param projectKey the key of the project, or null to query the framework cache.
+     * @return the cached 9 patch or null if not found.
+     */
+    public static NinePatchChunk getCached9Patch(String value, Object projectKey) {
+        if (projectKey != null) {
+            Map<String, SoftReference<NinePatchChunk>> map = sProject9PatchCache.get(projectKey);
+
+            if (map != null) {
+                SoftReference<NinePatchChunk> ref = map.get(value);
+                if (ref != null) {
+                    return ref.get();
+                }
+            }
+        } else {
+            SoftReference<NinePatchChunk> ref = sFramework9PatchCache.get(value);
+            if (ref != null) {
+                return ref.get();
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Sets a 9 patch chunk in a project cache or in the framework cache.
+     * @param value the path of the 9 patch
+     * @param ninePatch the 9 patch object
+     * @param projectKey the key of the project, or null to put the bitmap in the framework cache.
+     */
+    public static void setCached9Patch(String value, NinePatchChunk ninePatch, Object projectKey) {
+        if (projectKey != null) {
+            Map<String, SoftReference<NinePatchChunk>> map =
+                    sProject9PatchCache.computeIfAbsent(projectKey, k -> new HashMap<>());
+
+            map.put(value, new SoftReference<>(ninePatch));
+        } else {
+            sFramework9PatchCache.put(value, new SoftReference<>(ninePatch));
+        }
     }
 }
diff --git a/com/android/providers/settings/DatabaseHelper.java b/com/android/providers/settings/DatabaseHelper.java
index 06d00be..1557d91 100644
--- a/com/android/providers/settings/DatabaseHelper.java
+++ b/com/android/providers/settings/DatabaseHelper.java
@@ -2319,8 +2319,6 @@
             loadBooleanSetting(stmt, Settings.System.SCREEN_BRIGHTNESS_MODE,
                     R.bool.def_screen_brightness_automatic_mode);
 
-            loadDefaultAnimationSettings(stmt);
-
             loadBooleanSetting(stmt, Settings.System.ACCELEROMETER_ROTATION,
                     R.bool.def_accelerometer_rotation);
 
@@ -2514,6 +2512,8 @@
             loadSetting(stmt, Settings.Global.MODE_RINGER,
                     AudioManager.RINGER_MODE_NORMAL);
 
+            loadDefaultAnimationSettings(stmt);
+
             // --- Previously in 'secure'
             loadBooleanSetting(stmt, Settings.Global.PACKAGE_VERIFIER_ENABLE,
                     R.bool.def_package_verifier_enable);
diff --git a/com/android/server/AppOpsService.java b/com/android/server/AppOpsService.java
index 50b8df2..4ffa5f1 100644
--- a/com/android/server/AppOpsService.java
+++ b/com/android/server/AppOpsService.java
@@ -491,7 +491,8 @@
             return Collections.emptyList();
         }
         synchronized (this) {
-            Ops pkgOps = getOpsRawLocked(uid, resolvedPackageName, false);
+            Ops pkgOps = getOpsRawLocked(uid, resolvedPackageName, false /* edit */,
+                    false /* uidMismatchExpected */);
             if (pkgOps == null) {
                 return null;
             }
@@ -530,7 +531,8 @@
 
     private void pruneOp(Op op, int uid, String packageName) {
         if (op.time == 0 && op.rejectTime == 0) {
-            Ops ops = getOpsRawLocked(uid, packageName, false);
+            Ops ops = getOpsRawLocked(uid, packageName, false /* edit */,
+                    false /* uidMismatchExpected */);
             if (ops != null) {
                 ops.remove(op.op);
                 if (ops.size() <= 0) {
@@ -1046,7 +1048,9 @@
     public int checkPackage(int uid, String packageName) {
         Preconditions.checkNotNull(packageName);
         synchronized (this) {
-            if (getOpsRawLocked(uid, packageName, true) != null) {
+            Ops ops = getOpsRawLocked(uid, packageName, true /* edit */,
+                    true /* uidMismatchExpected */);
+            if (ops != null) {
                 return AppOpsManager.MODE_ALLOWED;
             } else {
                 return AppOpsManager.MODE_ERRORED;
@@ -1090,7 +1094,8 @@
     private int noteOperationUnchecked(int code, int uid, String packageName,
             int proxyUid, String proxyPackageName) {
         synchronized (this) {
-            Ops ops = getOpsRawLocked(uid, packageName, true);
+            Ops ops = getOpsRawLocked(uid, packageName, true /* edit */,
+                    false /* uidMismatchExpected */);
             if (ops == null) {
                 if (DEBUG) Log.d(TAG, "noteOperation: no op for code " + code + " uid " + uid
                         + " package " + packageName);
@@ -1148,7 +1153,8 @@
         }
         ClientState client = (ClientState)token;
         synchronized (this) {
-            Ops ops = getOpsRawLocked(uid, resolvedPackageName, true);
+            Ops ops = getOpsRawLocked(uid, resolvedPackageName, true /* edit */,
+                    false /* uidMismatchExpected */);
             if (ops == null) {
                 if (DEBUG) Log.d(TAG, "startOperation: no op for code " + code + " uid " + uid
                         + " package " + resolvedPackageName);
@@ -1274,7 +1280,8 @@
         return uidState;
     }
 
-    private Ops getOpsRawLocked(int uid, String packageName, boolean edit) {
+    private Ops getOpsRawLocked(int uid, String packageName, boolean edit,
+            boolean uidMismatchExpected) {
         UidState uidState = getUidStateLocked(uid, edit);
         if (uidState == null) {
             return null;
@@ -1326,10 +1333,12 @@
                     if (pkgUid != uid) {
                         // Oops!  The package name is not valid for the uid they are calling
                         // under.  Abort.
-                        RuntimeException ex = new RuntimeException("here");
-                        ex.fillInStackTrace();
-                        Slog.w(TAG, "Bad call: specified package " + packageName
-                                + " under uid " + uid + " but it is really " + pkgUid, ex);
+                        if (!uidMismatchExpected) {
+                            RuntimeException ex = new RuntimeException("here");
+                            ex.fillInStackTrace();
+                            Slog.w(TAG, "Bad call: specified package " + packageName
+                                    + " under uid " + uid + " but it is really " + pkgUid, ex);
+                        }
                         return null;
                     }
                 } finally {
@@ -1359,7 +1368,8 @@
     }
 
     private Op getOpLocked(int code, int uid, String packageName, boolean edit) {
-        Ops ops = getOpsRawLocked(uid, packageName, edit);
+        Ops ops = getOpsRawLocked(uid, packageName, edit,
+                false /* uidMismatchExpected */);
         if (ops == null) {
             return null;
         }
@@ -1393,7 +1403,8 @@
                 if (AppOpsManager.opAllowSystemBypassRestriction(code)) {
                     // If we are the system, bypass user restrictions for certain codes
                     synchronized (this) {
-                        Ops ops = getOpsRawLocked(uid, packageName, true);
+                        Ops ops = getOpsRawLocked(uid, packageName, true /* edit */,
+                                false /* uidMismatchExpected */);
                         if ((ops != null) && ops.isPrivileged) {
                             return false;
                         }
@@ -1713,7 +1724,8 @@
                         out.startTag(null, "uid");
                         out.attribute(null, "n", Integer.toString(pkg.getUid()));
                         synchronized (this) {
-                            Ops ops = getOpsRawLocked(pkg.getUid(), pkg.getPackageName(), false);
+                            Ops ops = getOpsRawLocked(pkg.getUid(), pkg.getPackageName(),
+                                    false /* edit */, false /* uidMismatchExpected */);
                             // Should always be present as the list of PackageOps is generated
                             // from Ops.
                             if (ops != null) {
diff --git a/com/android/server/BatteryService.java b/com/android/server/BatteryService.java
index 5106c8d..46b671b 100644
--- a/com/android/server/BatteryService.java
+++ b/com/android/server/BatteryService.java
@@ -24,6 +24,7 @@
 import android.os.ResultReceiver;
 import android.os.ShellCallback;
 import android.os.ShellCommand;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IBatteryStats;
 import com.android.internal.util.DumpUtils;
 import com.android.server.am.BatteryStatsService;
@@ -35,9 +36,15 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
+import android.hidl.manager.V1_0.IServiceManager;
+import android.hidl.manager.V1_0.IServiceNotification;
+import android.hardware.health.V2_0.HealthInfo;
+import android.hardware.health.V2_0.IHealthInfoCallback;
+import android.hardware.health.V2_0.IHealth;
+import android.hardware.health.V2_0.Result;
 import android.os.BatteryManager;
 import android.os.BatteryManagerInternal;
-import android.os.BatteryProperties;
+import android.os.BatteryProperty;
 import android.os.Binder;
 import android.os.FileUtils;
 import android.os.Handler;
@@ -53,6 +60,7 @@
 import android.provider.Settings;
 import android.service.battery.BatteryServiceDumpProto;
 import android.util.EventLog;
+import android.util.MutableInt;
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
 
@@ -62,6 +70,10 @@
 import java.io.IOException;
 import java.io.PrintWriter;
 
+import java.util.Arrays;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.concurrent.atomic.AtomicReference;
 
 /**
  * <p>BatteryService monitors the charging status, and charge level of the device
@@ -100,6 +112,8 @@
 
     private static final int BATTERY_SCALE = 100;    // battery capacity is a percentage
 
+    private static final long HEALTH_HAL_WAIT_MS = 1000;
+
     // Used locally for determining when to make a last ditch effort to log
     // discharge stats before the device dies.
     private int mCriticalBatteryLevel;
@@ -118,8 +132,8 @@
 
     private final Object mLock = new Object();
 
-    private BatteryProperties mBatteryProps;
-    private final BatteryProperties mLastBatteryProps = new BatteryProperties();
+    private HealthInfo mHealthInfo;
+    private final HealthInfo mLastHealthInfo = new HealthInfo();
     private boolean mBatteryLevelCritical;
     private int mLastBatteryStatus;
     private int mLastBatteryHealth;
@@ -157,6 +171,10 @@
 
     private ActivityManagerInternal mActivityManagerInternal;
 
+    private HealthServiceWrapper mHealthServiceWrapper;
+    private HealthHalCallback mHealthHalCallback;
+    private BatteryPropertiesRegistrar mBatteryPropertiesRegistrar;
+
     public BatteryService(Context context) {
         super(context);
 
@@ -195,17 +213,12 @@
 
     @Override
     public void onStart() {
-        IBinder b = ServiceManager.getService("batteryproperties");
-        final IBatteryPropertiesRegistrar batteryPropertiesRegistrar =
-                IBatteryPropertiesRegistrar.Stub.asInterface(b);
-        try {
-            batteryPropertiesRegistrar.registerListener(new BatteryListener());
-        } catch (RemoteException e) {
-            // Should never happen.
-        }
+        registerHealthCallback();
 
         mBinderService = new BinderService();
         publishBinderService("battery", mBinderService);
+        mBatteryPropertiesRegistrar = new BatteryPropertiesRegistrar();
+        publishBinderService("batteryproperties", mBatteryPropertiesRegistrar);
         publishLocalService(BatteryManagerInternal.class, new LocalService());
     }
 
@@ -231,6 +244,49 @@
         }
     }
 
+    private void registerHealthCallback() {
+        mHealthServiceWrapper = new HealthServiceWrapper();
+        mHealthHalCallback = new HealthHalCallback();
+        // IHealth is lazily retrieved.
+        try {
+            mHealthServiceWrapper.init(mHealthHalCallback,
+                    new HealthServiceWrapper.IServiceManagerSupplier() {},
+                    new HealthServiceWrapper.IHealthSupplier() {});
+        } catch (RemoteException | NoSuchElementException ex) {
+            Slog.w(TAG, "health: cannot register callback. "
+                        + "BatteryService will be started with dummy values. Reason: "
+                        + ex.getClass().getSimpleName() + ": " + ex.getMessage());
+            update(new HealthInfo());
+            return;
+        }
+
+        // init register for new service notifications, and IServiceManager should return the
+        // existing service in a near future. Wait for this.update() to instantiate
+        // the initial mHealthInfo.
+        long timeWaited = 0;
+        synchronized (mLock) {
+            long beforeWait = SystemClock.uptimeMillis();
+            while (mHealthInfo == null &&
+                    (timeWaited = SystemClock.uptimeMillis() - beforeWait) < HEALTH_HAL_WAIT_MS) {
+                try {
+                    mLock.wait(HEALTH_HAL_WAIT_MS - timeWaited);
+                } catch (InterruptedException ex) {
+                    break;
+                }
+            }
+            if (mHealthInfo == null) {
+                Slog.w(TAG, "health: Waited " + timeWaited + "ms for callbacks but received "
+                        + "nothing. BatteryService will be started with dummy values.");
+                update(new HealthInfo());
+                return;
+            }
+        }
+
+        if (DEBUG) {
+            Slog.d(TAG, "health: Waited " + timeWaited + "ms and received the update.");
+        }
+    }
+
     private void updateBatteryWarningLevelLocked() {
         final ContentResolver resolver = mContext.getContentResolver();
         int defWarnLevel = mContext.getResources().getInteger(
@@ -251,16 +307,16 @@
     private boolean isPoweredLocked(int plugTypeSet) {
         // assume we are powered if battery state is unknown so
         // the "stay on while plugged in" option will work.
-        if (mBatteryProps.batteryStatus == BatteryManager.BATTERY_STATUS_UNKNOWN) {
+        if (mHealthInfo.legacy.batteryStatus == BatteryManager.BATTERY_STATUS_UNKNOWN) {
             return true;
         }
-        if ((plugTypeSet & BatteryManager.BATTERY_PLUGGED_AC) != 0 && mBatteryProps.chargerAcOnline) {
+        if ((plugTypeSet & BatteryManager.BATTERY_PLUGGED_AC) != 0 && mHealthInfo.legacy.chargerAcOnline) {
             return true;
         }
-        if ((plugTypeSet & BatteryManager.BATTERY_PLUGGED_USB) != 0 && mBatteryProps.chargerUsbOnline) {
+        if ((plugTypeSet & BatteryManager.BATTERY_PLUGGED_USB) != 0 && mHealthInfo.legacy.chargerUsbOnline) {
             return true;
         }
-        if ((plugTypeSet & BatteryManager.BATTERY_PLUGGED_WIRELESS) != 0 && mBatteryProps.chargerWirelessOnline) {
+        if ((plugTypeSet & BatteryManager.BATTERY_PLUGGED_WIRELESS) != 0 && mHealthInfo.legacy.chargerWirelessOnline) {
             return true;
         }
         return false;
@@ -277,15 +333,15 @@
          *   (becomes <= mLowBatteryWarningLevel).
          */
         return !plugged
-                && mBatteryProps.batteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN
-                && mBatteryProps.batteryLevel <= mLowBatteryWarningLevel
+                && mHealthInfo.legacy.batteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN
+                && mHealthInfo.legacy.batteryLevel <= mLowBatteryWarningLevel
                 && (oldPlugged || mLastBatteryLevel > mLowBatteryWarningLevel);
     }
 
     private void shutdownIfNoPowerLocked() {
         // shut down gracefully if our battery is critically low and we are not powered.
         // wait until the system has booted before attempting to display the shutdown dialog.
-        if (mBatteryProps.batteryLevel == 0 && !isPoweredLocked(BatteryManager.BATTERY_PLUGGED_ANY)) {
+        if (mHealthInfo.legacy.batteryLevel == 0 && !isPoweredLocked(BatteryManager.BATTERY_PLUGGED_ANY)) {
             mHandler.post(new Runnable() {
                 @Override
                 public void run() {
@@ -306,7 +362,7 @@
         // shut down gracefully if temperature is too high (> 68.0C by default)
         // wait until the system has booted before attempting to display the
         // shutdown dialog.
-        if (mBatteryProps.batteryTemperature > mShutdownBatteryTemperature) {
+        if (mHealthInfo.legacy.batteryTemperature > mShutdownBatteryTemperature) {
             mHandler.post(new Runnable() {
                 @Override
                 public void run() {
@@ -323,28 +379,51 @@
         }
     }
 
-    private void update(BatteryProperties props) {
+    private void update(HealthInfo info) {
         synchronized (mLock) {
             if (!mUpdatesStopped) {
-                mBatteryProps = props;
+                mHealthInfo = info;
                 // Process the new values.
                 processValuesLocked(false);
+                mLock.notifyAll(); // for any waiters on new info
             } else {
-                mLastBatteryProps.set(props);
+                copy(mLastHealthInfo, info);
             }
         }
     }
 
+    private static void copy(HealthInfo dst, HealthInfo src) {
+        dst.legacy.chargerAcOnline = src.legacy.chargerAcOnline;
+        dst.legacy.chargerUsbOnline = src.legacy.chargerUsbOnline;
+        dst.legacy.chargerWirelessOnline = src.legacy.chargerWirelessOnline;
+        dst.legacy.maxChargingCurrent = src.legacy.maxChargingCurrent;
+        dst.legacy.maxChargingVoltage = src.legacy.maxChargingVoltage;
+        dst.legacy.batteryStatus = src.legacy.batteryStatus;
+        dst.legacy.batteryHealth = src.legacy.batteryHealth;
+        dst.legacy.batteryPresent = src.legacy.batteryPresent;
+        dst.legacy.batteryLevel = src.legacy.batteryLevel;
+        dst.legacy.batteryVoltage = src.legacy.batteryVoltage;
+        dst.legacy.batteryTemperature = src.legacy.batteryTemperature;
+        dst.legacy.batteryCurrent = src.legacy.batteryCurrent;
+        dst.legacy.batteryCycleCount = src.legacy.batteryCycleCount;
+        dst.legacy.batteryFullCharge = src.legacy.batteryFullCharge;
+        dst.legacy.batteryChargeCounter = src.legacy.batteryChargeCounter;
+        dst.legacy.batteryTechnology = src.legacy.batteryTechnology;
+        dst.batteryCurrentAverage = src.batteryCurrentAverage;
+        dst.batteryCapacity = src.batteryCapacity;
+        dst.energyCounter = src.energyCounter;
+    }
+
     private void processValuesLocked(boolean force) {
         boolean logOutlier = false;
         long dischargeDuration = 0;
 
-        mBatteryLevelCritical = (mBatteryProps.batteryLevel <= mCriticalBatteryLevel);
-        if (mBatteryProps.chargerAcOnline) {
+        mBatteryLevelCritical = (mHealthInfo.legacy.batteryLevel <= mCriticalBatteryLevel);
+        if (mHealthInfo.legacy.chargerAcOnline) {
             mPlugType = BatteryManager.BATTERY_PLUGGED_AC;
-        } else if (mBatteryProps.chargerUsbOnline) {
+        } else if (mHealthInfo.legacy.chargerUsbOnline) {
             mPlugType = BatteryManager.BATTERY_PLUGGED_USB;
-        } else if (mBatteryProps.chargerWirelessOnline) {
+        } else if (mHealthInfo.legacy.chargerWirelessOnline) {
             mPlugType = BatteryManager.BATTERY_PLUGGED_WIRELESS;
         } else {
             mPlugType = BATTERY_PLUGGED_NONE;
@@ -352,30 +431,17 @@
 
         if (DEBUG) {
             Slog.d(TAG, "Processing new values: "
-                    + "chargerAcOnline=" + mBatteryProps.chargerAcOnline
-                    + ", chargerUsbOnline=" + mBatteryProps.chargerUsbOnline
-                    + ", chargerWirelessOnline=" + mBatteryProps.chargerWirelessOnline
-                    + ", maxChargingCurrent" + mBatteryProps.maxChargingCurrent
-                    + ", maxChargingVoltage" + mBatteryProps.maxChargingVoltage
-                    + ", batteryStatus=" + mBatteryProps.batteryStatus
-                    + ", batteryHealth=" + mBatteryProps.batteryHealth
-                    + ", batteryPresent=" + mBatteryProps.batteryPresent
-                    + ", batteryLevel=" + mBatteryProps.batteryLevel
-                    + ", batteryTechnology=" + mBatteryProps.batteryTechnology
-                    + ", batteryVoltage=" + mBatteryProps.batteryVoltage
-                    + ", batteryChargeCounter=" + mBatteryProps.batteryChargeCounter
-                    + ", batteryFullCharge=" + mBatteryProps.batteryFullCharge
-                    + ", batteryTemperature=" + mBatteryProps.batteryTemperature
+                    + "info=" + mHealthInfo
                     + ", mBatteryLevelCritical=" + mBatteryLevelCritical
                     + ", mPlugType=" + mPlugType);
         }
 
         // Let the battery stats keep track of the current level.
         try {
-            mBatteryStats.setBatteryState(mBatteryProps.batteryStatus, mBatteryProps.batteryHealth,
-                    mPlugType, mBatteryProps.batteryLevel, mBatteryProps.batteryTemperature,
-                    mBatteryProps.batteryVoltage, mBatteryProps.batteryChargeCounter,
-                    mBatteryProps.batteryFullCharge);
+            mBatteryStats.setBatteryState(mHealthInfo.legacy.batteryStatus, mHealthInfo.legacy.batteryHealth,
+                    mPlugType, mHealthInfo.legacy.batteryLevel, mHealthInfo.legacy.batteryTemperature,
+                    mHealthInfo.legacy.batteryVoltage, mHealthInfo.legacy.batteryChargeCounter,
+                    mHealthInfo.legacy.batteryFullCharge);
         } catch (RemoteException e) {
             // Should never happen.
         }
@@ -383,16 +449,16 @@
         shutdownIfNoPowerLocked();
         shutdownIfOverTempLocked();
 
-        if (force || (mBatteryProps.batteryStatus != mLastBatteryStatus ||
-                mBatteryProps.batteryHealth != mLastBatteryHealth ||
-                mBatteryProps.batteryPresent != mLastBatteryPresent ||
-                mBatteryProps.batteryLevel != mLastBatteryLevel ||
+        if (force || (mHealthInfo.legacy.batteryStatus != mLastBatteryStatus ||
+                mHealthInfo.legacy.batteryHealth != mLastBatteryHealth ||
+                mHealthInfo.legacy.batteryPresent != mLastBatteryPresent ||
+                mHealthInfo.legacy.batteryLevel != mLastBatteryLevel ||
                 mPlugType != mLastPlugType ||
-                mBatteryProps.batteryVoltage != mLastBatteryVoltage ||
-                mBatteryProps.batteryTemperature != mLastBatteryTemperature ||
-                mBatteryProps.maxChargingCurrent != mLastMaxChargingCurrent ||
-                mBatteryProps.maxChargingVoltage != mLastMaxChargingVoltage ||
-                mBatteryProps.batteryChargeCounter != mLastChargeCounter ||
+                mHealthInfo.legacy.batteryVoltage != mLastBatteryVoltage ||
+                mHealthInfo.legacy.batteryTemperature != mLastBatteryTemperature ||
+                mHealthInfo.legacy.maxChargingCurrent != mLastMaxChargingCurrent ||
+                mHealthInfo.legacy.maxChargingVoltage != mLastMaxChargingVoltage ||
+                mHealthInfo.legacy.batteryChargeCounter != mLastChargeCounter ||
                 mInvalidCharger != mLastInvalidCharger)) {
 
             if (mPlugType != mLastPlugType) {
@@ -401,33 +467,33 @@
 
                     // There's no value in this data unless we've discharged at least once and the
                     // battery level has changed; so don't log until it does.
-                    if (mDischargeStartTime != 0 && mDischargeStartLevel != mBatteryProps.batteryLevel) {
+                    if (mDischargeStartTime != 0 && mDischargeStartLevel != mHealthInfo.legacy.batteryLevel) {
                         dischargeDuration = SystemClock.elapsedRealtime() - mDischargeStartTime;
                         logOutlier = true;
                         EventLog.writeEvent(EventLogTags.BATTERY_DISCHARGE, dischargeDuration,
-                                mDischargeStartLevel, mBatteryProps.batteryLevel);
+                                mDischargeStartLevel, mHealthInfo.legacy.batteryLevel);
                         // make sure we see a discharge event before logging again
                         mDischargeStartTime = 0;
                     }
                 } else if (mPlugType == BATTERY_PLUGGED_NONE) {
                     // charging -> discharging or we just powered up
                     mDischargeStartTime = SystemClock.elapsedRealtime();
-                    mDischargeStartLevel = mBatteryProps.batteryLevel;
+                    mDischargeStartLevel = mHealthInfo.legacy.batteryLevel;
                 }
             }
-            if (mBatteryProps.batteryStatus != mLastBatteryStatus ||
-                    mBatteryProps.batteryHealth != mLastBatteryHealth ||
-                    mBatteryProps.batteryPresent != mLastBatteryPresent ||
+            if (mHealthInfo.legacy.batteryStatus != mLastBatteryStatus ||
+                    mHealthInfo.legacy.batteryHealth != mLastBatteryHealth ||
+                    mHealthInfo.legacy.batteryPresent != mLastBatteryPresent ||
                     mPlugType != mLastPlugType) {
                 EventLog.writeEvent(EventLogTags.BATTERY_STATUS,
-                        mBatteryProps.batteryStatus, mBatteryProps.batteryHealth, mBatteryProps.batteryPresent ? 1 : 0,
-                        mPlugType, mBatteryProps.batteryTechnology);
+                        mHealthInfo.legacy.batteryStatus, mHealthInfo.legacy.batteryHealth, mHealthInfo.legacy.batteryPresent ? 1 : 0,
+                        mPlugType, mHealthInfo.legacy.batteryTechnology);
             }
-            if (mBatteryProps.batteryLevel != mLastBatteryLevel) {
+            if (mHealthInfo.legacy.batteryLevel != mLastBatteryLevel) {
                 // Don't do this just from voltage or temperature changes, that is
                 // too noisy.
                 EventLog.writeEvent(EventLogTags.BATTERY_LEVEL,
-                        mBatteryProps.batteryLevel, mBatteryProps.batteryVoltage, mBatteryProps.batteryTemperature);
+                        mHealthInfo.legacy.batteryLevel, mHealthInfo.legacy.batteryVoltage, mHealthInfo.legacy.batteryTemperature);
             }
             if (mBatteryLevelCritical && !mLastBatteryLevelCritical &&
                     mPlugType == BATTERY_PLUGGED_NONE) {
@@ -440,16 +506,16 @@
             if (!mBatteryLevelLow) {
                 // Should we now switch in to low battery mode?
                 if (mPlugType == BATTERY_PLUGGED_NONE
-                        && mBatteryProps.batteryLevel <= mLowBatteryWarningLevel) {
+                        && mHealthInfo.legacy.batteryLevel <= mLowBatteryWarningLevel) {
                     mBatteryLevelLow = true;
                 }
             } else {
                 // Should we now switch out of low battery mode?
                 if (mPlugType != BATTERY_PLUGGED_NONE) {
                     mBatteryLevelLow = false;
-                } else if (mBatteryProps.batteryLevel >= mLowBatteryCloseWarningLevel)  {
+                } else if (mHealthInfo.legacy.batteryLevel >= mLowBatteryCloseWarningLevel)  {
                     mBatteryLevelLow = false;
-                } else if (force && mBatteryProps.batteryLevel >= mLowBatteryWarningLevel) {
+                } else if (force && mHealthInfo.legacy.batteryLevel >= mLowBatteryWarningLevel) {
                     // If being forced, the previous state doesn't matter, we will just
                     // absolutely check to see if we are now above the warning level.
                     mBatteryLevelLow = false;
@@ -496,7 +562,7 @@
                     }
                 });
             } else if (mSentLowBatteryBroadcast &&
-                    mBatteryProps.batteryLevel >= mLowBatteryCloseWarningLevel) {
+                    mHealthInfo.legacy.batteryLevel >= mLowBatteryCloseWarningLevel) {
                 mSentLowBatteryBroadcast = false;
                 final Intent statusIntent = new Intent(Intent.ACTION_BATTERY_OKAY);
                 statusIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
@@ -522,16 +588,16 @@
                 logOutlierLocked(dischargeDuration);
             }
 
-            mLastBatteryStatus = mBatteryProps.batteryStatus;
-            mLastBatteryHealth = mBatteryProps.batteryHealth;
-            mLastBatteryPresent = mBatteryProps.batteryPresent;
-            mLastBatteryLevel = mBatteryProps.batteryLevel;
+            mLastBatteryStatus = mHealthInfo.legacy.batteryStatus;
+            mLastBatteryHealth = mHealthInfo.legacy.batteryHealth;
+            mLastBatteryPresent = mHealthInfo.legacy.batteryPresent;
+            mLastBatteryLevel = mHealthInfo.legacy.batteryLevel;
             mLastPlugType = mPlugType;
-            mLastBatteryVoltage = mBatteryProps.batteryVoltage;
-            mLastBatteryTemperature = mBatteryProps.batteryTemperature;
-            mLastMaxChargingCurrent = mBatteryProps.maxChargingCurrent;
-            mLastMaxChargingVoltage = mBatteryProps.maxChargingVoltage;
-            mLastChargeCounter = mBatteryProps.batteryChargeCounter;
+            mLastBatteryVoltage = mHealthInfo.legacy.batteryVoltage;
+            mLastBatteryTemperature = mHealthInfo.legacy.batteryTemperature;
+            mLastMaxChargingCurrent = mHealthInfo.legacy.maxChargingCurrent;
+            mLastMaxChargingVoltage = mHealthInfo.legacy.maxChargingVoltage;
+            mLastChargeCounter = mHealthInfo.legacy.batteryChargeCounter;
             mLastBatteryLevelCritical = mBatteryLevelCritical;
             mLastInvalidCharger = mInvalidCharger;
         }
@@ -543,38 +609,26 @@
         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
                 | Intent.FLAG_RECEIVER_REPLACE_PENDING);
 
-        int icon = getIconLocked(mBatteryProps.batteryLevel);
+        int icon = getIconLocked(mHealthInfo.legacy.batteryLevel);
 
         intent.putExtra(BatteryManager.EXTRA_SEQUENCE, mSequence);
-        intent.putExtra(BatteryManager.EXTRA_STATUS, mBatteryProps.batteryStatus);
-        intent.putExtra(BatteryManager.EXTRA_HEALTH, mBatteryProps.batteryHealth);
-        intent.putExtra(BatteryManager.EXTRA_PRESENT, mBatteryProps.batteryPresent);
-        intent.putExtra(BatteryManager.EXTRA_LEVEL, mBatteryProps.batteryLevel);
+        intent.putExtra(BatteryManager.EXTRA_STATUS, mHealthInfo.legacy.batteryStatus);
+        intent.putExtra(BatteryManager.EXTRA_HEALTH, mHealthInfo.legacy.batteryHealth);
+        intent.putExtra(BatteryManager.EXTRA_PRESENT, mHealthInfo.legacy.batteryPresent);
+        intent.putExtra(BatteryManager.EXTRA_LEVEL, mHealthInfo.legacy.batteryLevel);
         intent.putExtra(BatteryManager.EXTRA_SCALE, BATTERY_SCALE);
         intent.putExtra(BatteryManager.EXTRA_ICON_SMALL, icon);
         intent.putExtra(BatteryManager.EXTRA_PLUGGED, mPlugType);
-        intent.putExtra(BatteryManager.EXTRA_VOLTAGE, mBatteryProps.batteryVoltage);
-        intent.putExtra(BatteryManager.EXTRA_TEMPERATURE, mBatteryProps.batteryTemperature);
-        intent.putExtra(BatteryManager.EXTRA_TECHNOLOGY, mBatteryProps.batteryTechnology);
+        intent.putExtra(BatteryManager.EXTRA_VOLTAGE, mHealthInfo.legacy.batteryVoltage);
+        intent.putExtra(BatteryManager.EXTRA_TEMPERATURE, mHealthInfo.legacy.batteryTemperature);
+        intent.putExtra(BatteryManager.EXTRA_TECHNOLOGY, mHealthInfo.legacy.batteryTechnology);
         intent.putExtra(BatteryManager.EXTRA_INVALID_CHARGER, mInvalidCharger);
-        intent.putExtra(BatteryManager.EXTRA_MAX_CHARGING_CURRENT, mBatteryProps.maxChargingCurrent);
-        intent.putExtra(BatteryManager.EXTRA_MAX_CHARGING_VOLTAGE, mBatteryProps.maxChargingVoltage);
-        intent.putExtra(BatteryManager.EXTRA_CHARGE_COUNTER, mBatteryProps.batteryChargeCounter);
+        intent.putExtra(BatteryManager.EXTRA_MAX_CHARGING_CURRENT, mHealthInfo.legacy.maxChargingCurrent);
+        intent.putExtra(BatteryManager.EXTRA_MAX_CHARGING_VOLTAGE, mHealthInfo.legacy.maxChargingVoltage);
+        intent.putExtra(BatteryManager.EXTRA_CHARGE_COUNTER, mHealthInfo.legacy.batteryChargeCounter);
         if (DEBUG) {
-            Slog.d(TAG, "Sending ACTION_BATTERY_CHANGED.  level:" + mBatteryProps.batteryLevel +
-                    ", scale:" + BATTERY_SCALE + ", status:" + mBatteryProps.batteryStatus +
-                    ", health:" + mBatteryProps.batteryHealth +
-                    ", present:" + mBatteryProps.batteryPresent +
-                    ", voltage: " + mBatteryProps.batteryVoltage +
-                    ", temperature: " + mBatteryProps.batteryTemperature +
-                    ", technology: " + mBatteryProps.batteryTechnology +
-                    ", AC powered:" + mBatteryProps.chargerAcOnline +
-                    ", USB powered:" + mBatteryProps.chargerUsbOnline +
-                    ", Wireless powered:" + mBatteryProps.chargerWirelessOnline +
-                    ", icon:" + icon  + ", invalid charger:" + mInvalidCharger +
-                    ", maxChargingCurrent:" + mBatteryProps.maxChargingCurrent +
-                    ", maxChargingVoltage:" + mBatteryProps.maxChargingVoltage +
-                    ", chargeCounter:" + mBatteryProps.batteryChargeCounter);
+            Slog.d(TAG, "Sending ACTION_BATTERY_CHANGED. scale:" + BATTERY_SCALE
+                    + ", info:" + mHealthInfo.toString());
         }
 
         mHandler.post(new Runnable() {
@@ -635,14 +689,14 @@
                 long durationThreshold = Long.parseLong(durationThresholdString);
                 int dischargeThreshold = Integer.parseInt(dischargeThresholdString);
                 if (duration <= durationThreshold &&
-                        mDischargeStartLevel - mBatteryProps.batteryLevel >= dischargeThreshold) {
+                        mDischargeStartLevel - mHealthInfo.legacy.batteryLevel >= dischargeThreshold) {
                     // If the discharge cycle is bad enough we want to know about it.
                     logBatteryStatsLocked();
                 }
                 if (DEBUG) Slog.v(TAG, "duration threshold: " + durationThreshold +
                         " discharge threshold: " + dischargeThreshold);
                 if (DEBUG) Slog.v(TAG, "duration: " + duration + " discharge: " +
-                        (mDischargeStartLevel - mBatteryProps.batteryLevel));
+                        (mDischargeStartLevel - mHealthInfo.legacy.batteryLevel));
             } catch (NumberFormatException e) {
                 Slog.e(TAG, "Invalid DischargeThresholds GService string: " +
                         durationThresholdString + " or " + dischargeThresholdString);
@@ -651,14 +705,14 @@
     }
 
     private int getIconLocked(int level) {
-        if (mBatteryProps.batteryStatus == BatteryManager.BATTERY_STATUS_CHARGING) {
+        if (mHealthInfo.legacy.batteryStatus == BatteryManager.BATTERY_STATUS_CHARGING) {
             return com.android.internal.R.drawable.stat_sys_battery_charge;
-        } else if (mBatteryProps.batteryStatus == BatteryManager.BATTERY_STATUS_DISCHARGING) {
+        } else if (mHealthInfo.legacy.batteryStatus == BatteryManager.BATTERY_STATUS_DISCHARGING) {
             return com.android.internal.R.drawable.stat_sys_battery;
-        } else if (mBatteryProps.batteryStatus == BatteryManager.BATTERY_STATUS_NOT_CHARGING
-                || mBatteryProps.batteryStatus == BatteryManager.BATTERY_STATUS_FULL) {
+        } else if (mHealthInfo.legacy.batteryStatus == BatteryManager.BATTERY_STATUS_NOT_CHARGING
+                || mHealthInfo.legacy.batteryStatus == BatteryManager.BATTERY_STATUS_FULL) {
             if (isPoweredLocked(BatteryManager.BATTERY_PLUGGED_ANY)
-                    && mBatteryProps.batteryLevel >= 100) {
+                    && mHealthInfo.legacy.batteryLevel >= 100) {
                 return com.android.internal.R.drawable.stat_sys_battery_charge;
             } else {
                 return com.android.internal.R.drawable.stat_sys_battery;
@@ -720,11 +774,11 @@
                 getContext().enforceCallingOrSelfPermission(
                         android.Manifest.permission.DEVICE_POWER, null);
                 if (!mUpdatesStopped) {
-                    mLastBatteryProps.set(mBatteryProps);
+                    copy(mLastHealthInfo, mHealthInfo);
                 }
-                mBatteryProps.chargerAcOnline = false;
-                mBatteryProps.chargerUsbOnline = false;
-                mBatteryProps.chargerWirelessOnline = false;
+                mHealthInfo.legacy.chargerAcOnline = false;
+                mHealthInfo.legacy.chargerUsbOnline = false;
+                mHealthInfo.legacy.chargerWirelessOnline = false;
                 long ident = Binder.clearCallingIdentity();
                 try {
                     mUpdatesStopped = true;
@@ -751,30 +805,30 @@
                 }
                 try {
                     if (!mUpdatesStopped) {
-                        mLastBatteryProps.set(mBatteryProps);
+                        copy(mLastHealthInfo, mHealthInfo);
                     }
                     boolean update = true;
                     switch (key) {
                         case "present":
-                            mBatteryProps.batteryPresent = Integer.parseInt(value) != 0;
+                            mHealthInfo.legacy.batteryPresent = Integer.parseInt(value) != 0;
                             break;
                         case "ac":
-                            mBatteryProps.chargerAcOnline = Integer.parseInt(value) != 0;
+                            mHealthInfo.legacy.chargerAcOnline = Integer.parseInt(value) != 0;
                             break;
                         case "usb":
-                            mBatteryProps.chargerUsbOnline = Integer.parseInt(value) != 0;
+                            mHealthInfo.legacy.chargerUsbOnline = Integer.parseInt(value) != 0;
                             break;
                         case "wireless":
-                            mBatteryProps.chargerWirelessOnline = Integer.parseInt(value) != 0;
+                            mHealthInfo.legacy.chargerWirelessOnline = Integer.parseInt(value) != 0;
                             break;
                         case "status":
-                            mBatteryProps.batteryStatus = Integer.parseInt(value);
+                            mHealthInfo.legacy.batteryStatus = Integer.parseInt(value);
                             break;
                         case "level":
-                            mBatteryProps.batteryLevel = Integer.parseInt(value);
+                            mHealthInfo.legacy.batteryLevel = Integer.parseInt(value);
                             break;
                         case "temp":
-                            mBatteryProps.batteryTemperature = Integer.parseInt(value);
+                            mHealthInfo.legacy.batteryTemperature = Integer.parseInt(value);
                             break;
                         case "invalid":
                             mInvalidCharger = Integer.parseInt(value);
@@ -806,7 +860,7 @@
                 try {
                     if (mUpdatesStopped) {
                         mUpdatesStopped = false;
-                        mBatteryProps.set(mLastBatteryProps);
+                        copy(mHealthInfo, mLastHealthInfo);
                         processValuesFromShellLocked(pw, opts);
                     }
                 } finally {
@@ -833,20 +887,20 @@
                 if (mUpdatesStopped) {
                     pw.println("  (UPDATES STOPPED -- use 'reset' to restart)");
                 }
-                pw.println("  AC powered: " + mBatteryProps.chargerAcOnline);
-                pw.println("  USB powered: " + mBatteryProps.chargerUsbOnline);
-                pw.println("  Wireless powered: " + mBatteryProps.chargerWirelessOnline);
-                pw.println("  Max charging current: " + mBatteryProps.maxChargingCurrent);
-                pw.println("  Max charging voltage: " + mBatteryProps.maxChargingVoltage);
-                pw.println("  Charge counter: " + mBatteryProps.batteryChargeCounter);
-                pw.println("  status: " + mBatteryProps.batteryStatus);
-                pw.println("  health: " + mBatteryProps.batteryHealth);
-                pw.println("  present: " + mBatteryProps.batteryPresent);
-                pw.println("  level: " + mBatteryProps.batteryLevel);
+                pw.println("  AC powered: " + mHealthInfo.legacy.chargerAcOnline);
+                pw.println("  USB powered: " + mHealthInfo.legacy.chargerUsbOnline);
+                pw.println("  Wireless powered: " + mHealthInfo.legacy.chargerWirelessOnline);
+                pw.println("  Max charging current: " + mHealthInfo.legacy.maxChargingCurrent);
+                pw.println("  Max charging voltage: " + mHealthInfo.legacy.maxChargingVoltage);
+                pw.println("  Charge counter: " + mHealthInfo.legacy.batteryChargeCounter);
+                pw.println("  status: " + mHealthInfo.legacy.batteryStatus);
+                pw.println("  health: " + mHealthInfo.legacy.batteryHealth);
+                pw.println("  present: " + mHealthInfo.legacy.batteryPresent);
+                pw.println("  level: " + mHealthInfo.legacy.batteryLevel);
                 pw.println("  scale: " + BATTERY_SCALE);
-                pw.println("  voltage: " + mBatteryProps.batteryVoltage);
-                pw.println("  temperature: " + mBatteryProps.batteryTemperature);
-                pw.println("  technology: " + mBatteryProps.batteryTechnology);
+                pw.println("  voltage: " + mHealthInfo.legacy.batteryVoltage);
+                pw.println("  temperature: " + mHealthInfo.legacy.batteryTemperature);
+                pw.println("  technology: " + mHealthInfo.legacy.batteryTechnology);
             } else {
                 Shell shell = new Shell();
                 shell.exec(mBinderService, null, fd, null, args, null, new ResultReceiver(null));
@@ -860,25 +914,25 @@
         synchronized (mLock) {
             proto.write(BatteryServiceDumpProto.ARE_UPDATES_STOPPED, mUpdatesStopped);
             int batteryPluggedValue = BatteryServiceDumpProto.BATTERY_PLUGGED_NONE;
-            if (mBatteryProps.chargerAcOnline) {
+            if (mHealthInfo.legacy.chargerAcOnline) {
                 batteryPluggedValue = BatteryServiceDumpProto.BATTERY_PLUGGED_AC;
-            } else if (mBatteryProps.chargerUsbOnline) {
+            } else if (mHealthInfo.legacy.chargerUsbOnline) {
                 batteryPluggedValue = BatteryServiceDumpProto.BATTERY_PLUGGED_USB;
-            } else if (mBatteryProps.chargerWirelessOnline) {
+            } else if (mHealthInfo.legacy.chargerWirelessOnline) {
                 batteryPluggedValue = BatteryServiceDumpProto.BATTERY_PLUGGED_WIRELESS;
             }
             proto.write(BatteryServiceDumpProto.PLUGGED, batteryPluggedValue);
-            proto.write(BatteryServiceDumpProto.MAX_CHARGING_CURRENT, mBatteryProps.maxChargingCurrent);
-            proto.write(BatteryServiceDumpProto.MAX_CHARGING_VOLTAGE, mBatteryProps.maxChargingVoltage);
-            proto.write(BatteryServiceDumpProto.CHARGE_COUNTER, mBatteryProps.batteryChargeCounter);
-            proto.write(BatteryServiceDumpProto.STATUS, mBatteryProps.batteryStatus);
-            proto.write(BatteryServiceDumpProto.HEALTH, mBatteryProps.batteryHealth);
-            proto.write(BatteryServiceDumpProto.IS_PRESENT, mBatteryProps.batteryPresent);
-            proto.write(BatteryServiceDumpProto.LEVEL, mBatteryProps.batteryLevel);
+            proto.write(BatteryServiceDumpProto.MAX_CHARGING_CURRENT, mHealthInfo.legacy.maxChargingCurrent);
+            proto.write(BatteryServiceDumpProto.MAX_CHARGING_VOLTAGE, mHealthInfo.legacy.maxChargingVoltage);
+            proto.write(BatteryServiceDumpProto.CHARGE_COUNTER, mHealthInfo.legacy.batteryChargeCounter);
+            proto.write(BatteryServiceDumpProto.STATUS, mHealthInfo.legacy.batteryStatus);
+            proto.write(BatteryServiceDumpProto.HEALTH, mHealthInfo.legacy.batteryHealth);
+            proto.write(BatteryServiceDumpProto.IS_PRESENT, mHealthInfo.legacy.batteryPresent);
+            proto.write(BatteryServiceDumpProto.LEVEL, mHealthInfo.legacy.batteryLevel);
             proto.write(BatteryServiceDumpProto.SCALE, BATTERY_SCALE);
-            proto.write(BatteryServiceDumpProto.VOLTAGE, mBatteryProps.batteryVoltage);
-            proto.write(BatteryServiceDumpProto.TEMPERATURE, mBatteryProps.batteryTemperature);
-            proto.write(BatteryServiceDumpProto.TECHNOLOGY, mBatteryProps.batteryTechnology);
+            proto.write(BatteryServiceDumpProto.VOLTAGE, mHealthInfo.legacy.batteryVoltage);
+            proto.write(BatteryServiceDumpProto.TEMPERATURE, mHealthInfo.legacy.batteryTemperature);
+            proto.write(BatteryServiceDumpProto.TECHNOLOGY, mHealthInfo.legacy.batteryTechnology);
         }
         proto.flush();
     }
@@ -911,8 +965,8 @@
          * Synchronize on BatteryService.
          */
         public void updateLightsLocked() {
-            final int level = mBatteryProps.batteryLevel;
-            final int status = mBatteryProps.batteryStatus;
+            final int level = mHealthInfo.legacy.batteryLevel;
+            final int status = mHealthInfo.legacy.batteryStatus;
             if (level < mLowBatteryWarningLevel) {
                 if (status == BatteryManager.BATTERY_STATUS_CHARGING) {
                     // Solid red when battery is charging
@@ -938,15 +992,43 @@
         }
     }
 
-    private final class BatteryListener extends IBatteryPropertiesListener.Stub {
-        @Override public void batteryPropertiesChanged(BatteryProperties props) {
-            final long identity = Binder.clearCallingIdentity();
+    private final class HealthHalCallback extends IHealthInfoCallback.Stub
+            implements HealthServiceWrapper.Callback {
+        @Override public void healthInfoChanged(HealthInfo props) {
+            BatteryService.this.update(props);
+        }
+        // on new service registered
+        @Override public void onRegistration(IHealth oldService, IHealth newService,
+                String instance) {
+            if (newService == null) return;
+
             try {
-                BatteryService.this.update(props);
-            } finally {
-                Binder.restoreCallingIdentity(identity);
+                if (oldService != null) {
+                    int r = oldService.unregisterCallback(this);
+                    if (r != Result.SUCCESS) {
+                        Slog.w(TAG, "health: cannot unregister previous callback: " +
+                                Result.toString(r));
+                    }
+                }
+            } catch (RemoteException ex) {
+                Slog.w(TAG, "health: cannot unregister previous callback (transaction error): "
+                            + ex.getMessage());
             }
-       }
+
+            try {
+                int r = newService.registerCallback(this);
+                if (r != Result.SUCCESS) {
+                    Slog.w(TAG, "health: cannot register callback: " + Result.toString(r));
+                    return;
+                }
+                // registerCallback does NOT guarantee that update is called
+                // immediately, so request a manual update here.
+                newService.update();
+            } catch (RemoteException ex) {
+                Slog.e(TAG, "health: cannot register callback (transaction error): "
+                        + ex.getMessage());
+            }
+        }
     }
 
     private final class BinderService extends Binder {
@@ -967,6 +1049,63 @@
         }
     }
 
+    // Reduced IBatteryPropertiesRegistrar that only implements getProperty for usage
+    // in BatteryManager.
+    private final class BatteryPropertiesRegistrar extends IBatteryPropertiesRegistrar.Stub {
+        public void registerListener(IBatteryPropertiesListener listener) {
+            Slog.e(TAG, "health: must not call registerListener on battery properties");
+        }
+        public void unregisterListener(IBatteryPropertiesListener listener) {
+            Slog.e(TAG, "health: must not call unregisterListener on battery properties");
+        }
+        public int getProperty(int id, final BatteryProperty prop) throws RemoteException {
+            IHealth service = mHealthServiceWrapper.getLastService();
+            final MutableInt outResult = new MutableInt(Result.NOT_SUPPORTED);
+            switch(id) {
+                case BatteryManager.BATTERY_PROPERTY_CHARGE_COUNTER:
+                    service.getChargeCounter((int result, int value) -> {
+                        outResult.value = result;
+                        if (result == Result.SUCCESS) prop.setLong(value);
+                    });
+                    break;
+                case BatteryManager.BATTERY_PROPERTY_CURRENT_NOW:
+                    service.getCurrentNow((int result, int value) -> {
+                        outResult.value = result;
+                        if (result == Result.SUCCESS) prop.setLong(value);
+                    });
+                    break;
+                case BatteryManager.BATTERY_PROPERTY_CURRENT_AVERAGE:
+                    service.getCurrentAverage((int result, int value) -> {
+                        outResult.value = result;
+                        if (result == Result.SUCCESS) prop.setLong(value);
+                    });
+                    break;
+                case BatteryManager.BATTERY_PROPERTY_CAPACITY:
+                    service.getCapacity((int result, int value) -> {
+                        outResult.value = result;
+                        if (result == Result.SUCCESS) prop.setLong(value);
+                    });
+                    break;
+                case BatteryManager.BATTERY_PROPERTY_STATUS:
+                    service.getChargeStatus((int result, int value) -> {
+                        outResult.value = result;
+                        if (result == Result.SUCCESS) prop.setLong(value);
+                    });
+                    break;
+                case BatteryManager.BATTERY_PROPERTY_ENERGY_COUNTER:
+                    service.getEnergyCounter((int result, long value) -> {
+                        outResult.value = result;
+                        if (result == Result.SUCCESS) prop.setLong(value);
+                    });
+                    break;
+            }
+            return outResult.value;
+        }
+        public void scheduleUpdate() {
+            Slog.e(TAG, "health: must not call scheduleUpdate on battery properties");
+        }
+    }
+
     private final class LocalService extends BatteryManagerInternal {
         @Override
         public boolean isPowered(int plugTypeSet) {
@@ -985,7 +1124,7 @@
         @Override
         public int getBatteryLevel() {
             synchronized (mLock) {
-                return mBatteryProps.batteryLevel;
+                return mHealthInfo.legacy.batteryLevel;
             }
         }
 
@@ -1003,4 +1142,138 @@
             }
         }
     }
+
+    /**
+     * HealthServiceWrapper wraps the internal IHealth service and refreshes the service when
+     * necessary.
+     *
+     * On new registration of IHealth service, {@link #onRegistration onRegistration} is called and
+     * the internal service is refreshed.
+     * On death of an existing IHealth service, the internal service is NOT cleared to avoid
+     * race condition between death notification and new service notification. Hence,
+     * a caller must check for transaction errors when calling into the service.
+     *
+     * @hide Should only be used internally.
+     */
+    @VisibleForTesting
+    static final class HealthServiceWrapper {
+        private static final String TAG = "HealthServiceWrapper";
+        public static final String INSTANCE_HEALTHD = "backup";
+        public static final String INSTANCE_VENDOR = "default";
+        // All interesting instances, sorted by priority high -> low.
+        private static final List<String> sAllInstances =
+                Arrays.asList(INSTANCE_VENDOR, INSTANCE_HEALTHD);
+
+        private final IServiceNotification mNotification = new Notification();
+        private Callback mCallback;
+        private IHealthSupplier mHealthSupplier;
+
+        private final Object mLastServiceSetLock = new Object();
+        // Last IHealth service received.
+        // set must be also be guarded with mLastServiceSetLock to ensure ordering.
+        private final AtomicReference<IHealth> mLastService = new AtomicReference<>();
+
+        /**
+         * init should be called after constructor. For testing purposes, init is not called by
+         * constructor.
+         */
+        HealthServiceWrapper() {
+        }
+
+        IHealth getLastService() {
+            return mLastService.get();
+        }
+
+        /**
+         * Start monitoring registration of new IHealth services. Only instances that are in
+         * {@code sAllInstances} and in device / framework manifest are used. This function should
+         * only be called once.
+         * @throws RemoteException transaction error when talking to IServiceManager
+         * @throws NoSuchElementException if one of the following cases:
+         *         - No service manager;
+         *         - none of {@code sAllInstances} are in manifests (i.e. not
+         *           available on this device), or none of these instances are available to current
+         *           process.
+         * @throws NullPointerException when callback is null or supplier is null
+         */
+        void init(Callback callback,
+                  IServiceManagerSupplier managerSupplier,
+                  IHealthSupplier healthSupplier)
+                throws RemoteException, NoSuchElementException, NullPointerException {
+            if (callback == null || managerSupplier == null || healthSupplier == null)
+                throw new NullPointerException();
+
+            mCallback = callback;
+            mHealthSupplier = healthSupplier;
+
+            IServiceManager manager = managerSupplier.get();
+            for (String name : sAllInstances) {
+                if (manager.getTransport(IHealth.kInterfaceName, name) ==
+                        IServiceManager.Transport.EMPTY) {
+                    continue;
+                }
+
+                manager.registerForNotifications(IHealth.kInterfaceName, name, mNotification);
+                Slog.i(TAG, "health: HealthServiceWrapper listening to instance " + name);
+                return;
+            }
+
+            throw new NoSuchElementException(String.format(
+                    "No IHealth service instance among %s is available. Perhaps no permission?",
+                    sAllInstances.toString()));
+        }
+
+        interface Callback {
+            /**
+             * This function is invoked asynchronously when a new and related IServiceNotification
+             * is received.
+             * @param service the recently retrieved service from IServiceManager.
+             * Can be a dead service before service notification of a new service is delivered.
+             * Implementation must handle cases for {@link RemoteException}s when calling
+             * into service.
+             * @param instance instance name.
+             */
+            void onRegistration(IHealth oldService, IHealth newService, String instance);
+        }
+
+        /**
+         * Supplier of services.
+         * Must not return null; throw {@link NoSuchElementException} if a service is not available.
+         */
+        interface IServiceManagerSupplier {
+            default IServiceManager get() throws NoSuchElementException, RemoteException {
+                return IServiceManager.getService();
+            }
+        }
+        /**
+         * Supplier of services.
+         * Must not return null; throw {@link NoSuchElementException} if a service is not available.
+         */
+        interface IHealthSupplier {
+            default IHealth get(String name) throws NoSuchElementException, RemoteException {
+                return IHealth.getService(name);
+            }
+        }
+
+        private class Notification extends IServiceNotification.Stub {
+            @Override
+            public final void onRegistration(String interfaceName, String instanceName,
+                    boolean preexisting) {
+                if (!IHealth.kInterfaceName.equals(interfaceName)) return;
+                if (!sAllInstances.contains(instanceName)) return;
+                try {
+                    // ensures the order of multiple onRegistration on different threads.
+                    synchronized (mLastServiceSetLock) {
+                        IHealth newService = mHealthSupplier.get(instanceName);
+                        IHealth oldService = mLastService.getAndSet(newService);
+                        Slog.i(TAG, "health: new instance registered " + instanceName);
+                        mCallback.onRegistration(oldService, newService, instanceName);
+                    }
+                } catch (NoSuchElementException | RemoteException ex) {
+                    Slog.e(TAG, "health: Cannot get instance '" + instanceName + "': " +
+                           ex.getMessage() + ". Perhaps no permission?");
+                }
+            }
+        }
+    }
 }
diff --git a/com/android/server/DeviceIdleController.java b/com/android/server/DeviceIdleController.java
index 2d9baf6..0921a00 100644
--- a/com/android/server/DeviceIdleController.java
+++ b/com/android/server/DeviceIdleController.java
@@ -1333,6 +1333,10 @@
         public int[] getPowerSaveWhitelistUserAppIds() {
             return DeviceIdleController.this.getPowerSaveWhitelistUserAppIds();
         }
+
+        public int[] getPowerSaveTempWhitelistAppIds() {
+            return DeviceIdleController.this.getAppIdTempWhitelistInternal();
+        }
     }
 
     public DeviceIdleController(Context context) {
diff --git a/com/android/server/InputMethodManagerService.java b/com/android/server/InputMethodManagerService.java
index c23757f..9da3757 100644
--- a/com/android/server/InputMethodManagerService.java
+++ b/com/android/server/InputMethodManagerService.java
@@ -52,6 +52,7 @@
 import android.annotation.BinderThread;
 import android.annotation.ColorInt;
 import android.annotation.IntDef;
+import android.annotation.MainThread;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
@@ -3190,6 +3191,7 @@
         }
     }
 
+    @MainThread
     @Override
     public boolean handleMessage(Message msg) {
         SomeArgs args;
diff --git a/com/android/server/IntentResolver.java b/com/android/server/IntentResolver.java
index 40499c9..119c9df 100644
--- a/com/android/server/IntentResolver.java
+++ b/com/android/server/IntentResolver.java
@@ -38,6 +38,8 @@
 
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.util.proto.ProtoOutputStream;
+
 import com.android.internal.util.FastPrintWriter;
 
 /**
@@ -279,6 +281,31 @@
         return printedSomething;
     }
 
+    void writeProtoMap(ProtoOutputStream proto, long fieldId, ArrayMap<String, F[]> map) {
+        int N = map.size();
+        for (int mapi = 0; mapi < N; mapi++) {
+            long token = proto.start(fieldId);
+            proto.write(IntentResolverProto.ArrayMapEntry.KEY, map.keyAt(mapi));
+            for (F f : map.valueAt(mapi)) {
+                if (f != null) {
+                    proto.write(IntentResolverProto.ArrayMapEntry.VALUES, f.toString());
+                }
+            }
+            proto.end(token);
+        }
+    }
+
+    public void writeToProto(ProtoOutputStream proto, long fieldId) {
+        long token = proto.start(fieldId);
+        writeProtoMap(proto, IntentResolverProto.FULL_MIME_TYPES, mTypeToFilter);
+        writeProtoMap(proto, IntentResolverProto.BASE_MIME_TYPES, mBaseTypeToFilter);
+        writeProtoMap(proto, IntentResolverProto.WILD_MIME_TYPES, mWildTypeToFilter);
+        writeProtoMap(proto, IntentResolverProto.SCHEMES, mSchemeToFilter);
+        writeProtoMap(proto, IntentResolverProto.NON_DATA_ACTIONS, mActionToFilter);
+        writeProtoMap(proto, IntentResolverProto.MIME_TYPED_ACTIONS, mTypedActionToFilter);
+        proto.end(token);
+    }
+
     public boolean dump(PrintWriter out, String title, String prefix, String packageName,
             boolean printFilter, boolean collapseDuplicates) {
         String innerPrefix = prefix + "  ";
diff --git a/com/android/server/IpSecService.java b/com/android/server/IpSecService.java
index 2e1f142..cf1d33c 100644
--- a/com/android/server/IpSecService.java
+++ b/com/android/server/IpSecService.java
@@ -882,8 +882,14 @@
         for (int direction : DIRECTIONS) {
             IpSecAlgorithm crypt = config.getEncryption(direction);
             IpSecAlgorithm auth = config.getAuthentication(direction);
-            if (crypt == null && auth == null) {
-                throw new IllegalArgumentException("Encryption and Authentication are both null");
+            IpSecAlgorithm authenticatedEncryption = config.getAuthenticatedEncryption(direction);
+            if (authenticatedEncryption == null && crypt == null && auth == null) {
+                throw new IllegalArgumentException(
+                        "No Encryption or Authentication algorithms specified");
+            } else if (authenticatedEncryption != null && (auth != null || crypt != null)) {
+                throw new IllegalArgumentException(
+                        "Authenticated Encryption is mutually"
+                                + " exclusive with other Authentication or Encryption algorithms");
             }
 
             if (mSpiRecords.getAndCheckOwner(config.getSpiResourceId(direction)) == null) {
@@ -922,6 +928,7 @@
         for (int direction : DIRECTIONS) {
             IpSecAlgorithm auth = c.getAuthentication(direction);
             IpSecAlgorithm crypt = c.getEncryption(direction);
+            IpSecAlgorithm authCrypt = c.getAuthenticatedEncryption(direction);
 
             spis[direction] = mSpiRecords.getAndCheckOwner(c.getSpiResourceId(direction));
             int spi = spis[direction].getSpi();
@@ -942,6 +949,9 @@
                                 (crypt != null) ? crypt.getName() : "",
                                 (crypt != null) ? crypt.getKey() : null,
                                 (crypt != null) ? crypt.getTruncationLengthBits() : 0,
+                                (authCrypt != null) ? authCrypt.getName() : "",
+                                (authCrypt != null) ? authCrypt.getKey() : null,
+                                (authCrypt != null) ? authCrypt.getTruncationLengthBits() : 0,
                                 encapType,
                                 encapLocalPort,
                                 encapRemotePort);
diff --git a/com/android/server/NetworkManagementService.java b/com/android/server/NetworkManagementService.java
index ba3afc3..c60d7b0 100644
--- a/com/android/server/NetworkManagementService.java
+++ b/com/android/server/NetworkManagementService.java
@@ -1980,7 +1980,8 @@
 
         final String[] domainStrs = domains == null ? new String[0] : domains.split(" ");
         final int[] params = { sampleValidity, successThreshold, minSamples, maxSamples };
-        final boolean useTls = false;
+        final boolean useTls = Settings.Global.getInt(resolver,
+                Settings.Global.DNS_TLS_DISABLED, 0) == 0;
         final String tlsHostname = "";
         final String[] tlsFingerprints = new String[0];
         try {
diff --git a/com/android/server/StorageManagerService.java b/com/android/server/StorageManagerService.java
index 55391b3..cd25610 100644
--- a/com/android/server/StorageManagerService.java
+++ b/com/android/server/StorageManagerService.java
@@ -56,6 +56,7 @@
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
+import android.os.IStoraged;
 import android.os.IVold;
 import android.os.IVoldListener;
 import android.os.IVoldTaskListener;
@@ -395,6 +396,7 @@
     private final Context mContext;
 
     private volatile IVold mVold;
+    private volatile IStoraged mStoraged;
 
     private volatile boolean mSystemReady = false;
     private volatile boolean mBootCompleted = false;
@@ -809,6 +811,7 @@
                 }
                 for (int userId : systemUnlockedUsers) {
                     mVold.onUserStarted(userId);
+                    mStoraged.onUserStarted(userId);
                 }
             } catch (Exception e) {
                 Slog.wtf(TAG, e);
@@ -824,6 +827,7 @@
         // bind mount against.
         try {
             mVold.onUserStarted(userId);
+            mStoraged.onUserStarted(userId);
         } catch (Exception e) {
             Slog.wtf(TAG, e);
         }
@@ -850,6 +854,7 @@
 
         try {
             mVold.onUserStopped(userId);
+            mStoraged.onUserStopped(userId);
         } catch (Exception e) {
             Slog.wtf(TAG, e);
         }
@@ -1335,13 +1340,36 @@
     }
 
     private void connect() {
-        IBinder binder = ServiceManager.getService("vold");
+        IBinder binder = ServiceManager.getService("storaged");
+        if (binder != null) {
+            try {
+                binder.linkToDeath(new DeathRecipient() {
+                    @Override
+                    public void binderDied() {
+                        Slog.w(TAG, "storaged died; reconnecting");
+                        mStoraged = null;
+                        connect();
+                    }
+                }, 0);
+            } catch (RemoteException e) {
+                binder = null;
+            }
+        }
+
+        if (binder != null) {
+            mStoraged = IStoraged.Stub.asInterface(binder);
+        } else {
+            Slog.w(TAG, "storaged not found; trying again");
+        }
+
+        binder = ServiceManager.getService("vold");
         if (binder != null) {
             try {
                 binder.linkToDeath(new DeathRecipient() {
                     @Override
                     public void binderDied() {
                         Slog.w(TAG, "vold died; reconnecting");
+                        mVold = null;
                         connect();
                     }
                 }, 0);
@@ -1354,18 +1382,21 @@
             mVold = IVold.Stub.asInterface(binder);
             try {
                 mVold.setListener(mListener);
-                onDaemonConnected();
-                return;
             } catch (RemoteException e) {
+                mVold = null;
                 Slog.w(TAG, "vold listener rejected; trying again", e);
             }
         } else {
             Slog.w(TAG, "vold not found; trying again");
         }
 
-        BackgroundThread.getHandler().postDelayed(() -> {
-            connect();
-        }, DateUtils.SECOND_IN_MILLIS);
+        if (mStoraged == null || mVold == null) {
+            BackgroundThread.getHandler().postDelayed(() -> {
+                connect();
+            }, DateUtils.SECOND_IN_MILLIS);
+        } else {
+            onDaemonConnected();
+        }
     }
 
     private void systemReady() {
diff --git a/com/android/server/SystemServer.java b/com/android/server/SystemServer.java
index 92cbd3d..d9db22e 100644
--- a/com/android/server/SystemServer.java
+++ b/com/android/server/SystemServer.java
@@ -125,6 +125,7 @@
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Future;
 
+import static android.os.IServiceManager.DUMP_PRIORITY_CRITICAL;
 import static android.view.Display.DEFAULT_DISPLAY;
 
 public final class SystemServer {
@@ -205,7 +206,8 @@
             "com.android.server.autofill.AutofillManagerService";
     private static final String TIME_ZONE_RULES_MANAGER_SERVICE_CLASS =
             "com.android.server.timezone.RulesManagerService$Lifecycle";
-
+    private static final String IOT_SERVICE_CLASS =
+            "com.google.android.things.services.IoTSystemService";
     private static final String PERSISTENT_DATA_BLOCK_PROP = "ro.frp.pst";
 
     private static final String UNCRYPT_PACKAGE_FILE = "/cache/recovery/uncrypt_file";
@@ -824,7 +826,8 @@
             wm = WindowManagerService.main(context, inputManager,
                     mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL,
                     !mFirstBoot, mOnlyCore, new PhoneWindowManager());
-            ServiceManager.addService(Context.WINDOW_SERVICE, wm);
+            ServiceManager.addService(Context.WINDOW_SERVICE, wm, /* allowIsolated= */ false,
+                    DUMP_PRIORITY_CRITICAL);
             ServiceManager.addService(Context.INPUT_SERVICE, inputManager);
             traceEnd();
 
@@ -1542,10 +1545,11 @@
             traceEnd();
         }
 
-        // Statsd helper
-        traceBeginAndSlog("StartStatsCompanionService");
-        mSystemServiceManager.startService(StatsCompanionService.Lifecycle.class);
-        traceEnd();
+        if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_EMBEDDED)) {
+            traceBeginAndSlog("StartIoTSystemService");
+            mSystemServiceManager.startService(IOT_SERVICE_CLASS);
+            traceEnd();
+        }
 
         // Before things start rolling, be sure we have decided whether
         // we are in safe mode.
@@ -1640,11 +1644,7 @@
         traceEnd();
 
         traceBeginAndSlog("MakePackageManagerServiceReady");
-        try {
-            mPackageManagerService.systemReady();
-        } catch (Throwable e) {
-            reportWtf("making Package Manager Service ready", e);
-        }
+        mPackageManagerService.systemReady();
         traceEnd();
 
         traceBeginAndSlog("MakeDisplayManagerServiceReady");
@@ -1658,6 +1658,25 @@
 
         mSystemServiceManager.setSafeMode(safeMode);
 
+        // Start device specific services
+        traceBeginAndSlog("StartDeviceSpecificServices");
+        final String[] classes = mSystemContext.getResources().getStringArray(
+                R.array.config_deviceSpecificSystemServices);
+        for (final String className : classes) {
+            traceBeginAndSlog("StartDeviceSpecificServices " + className);
+            try {
+                mSystemServiceManager.startService(className);
+            } catch (Throwable e) {
+                reportWtf("starting " + className, e);
+            }
+            traceEnd();
+        }
+        traceEnd();
+
+        traceBeginAndSlog("StartBootPhaseDeviceSpecificServicesReady");
+        mSystemServiceManager.startBootPhase(SystemService.PHASE_DEVICE_SPECIFIC_SERVICES_READY);
+        traceEnd();
+
         // These are needed to propagate to the runnable below.
         final NetworkManagementService networkManagementF = networkManagement;
         final NetworkStatsService networkStatsF = networkStats;
diff --git a/com/android/server/SystemService.java b/com/android/server/SystemService.java
index 94397d0..58731d2 100644
--- a/com/android/server/SystemService.java
+++ b/com/android/server/SystemService.java
@@ -64,6 +64,11 @@
     public static final int PHASE_SYSTEM_SERVICES_READY = 500;
 
     /**
+     * After receiving this boot phase, services can safely call into device specific services.
+     */
+    public static final int PHASE_DEVICE_SPECIFIC_SERVICES_READY = 520;
+
+    /**
      * After receiving this boot phase, services can broadcast Intents.
      */
     public static final int PHASE_ACTIVITY_MANAGER_READY = 550;
diff --git a/com/android/server/VibratorService.java b/com/android/server/VibratorService.java
index 046eb76..8b79b9d 100644
--- a/com/android/server/VibratorService.java
+++ b/com/android/server/VibratorService.java
@@ -373,12 +373,24 @@
             if (mCurrentVibration.hasLongerTimeout(newOneShot.getTiming())
                     && newOneShot.getAmplitude() == currentOneShot.getAmplitude()) {
                 if (DEBUG) {
-                    Slog.e(TAG, "Ignoring incoming vibration in favor of current vibration");
+                    Slog.d(TAG, "Ignoring incoming vibration in favor of current vibration");
                 }
                 return;
             }
         }
 
+        // If the current vibration is repeating and the incoming one is non-repeating, then ignore
+        // the non-repeating vibration. This is so that we don't cancel vibrations that are meant
+        // to grab the attention of the user, like ringtones and alarms, in favor of one-shot
+        // vibrations that are likely quite short.
+        if (!isRepeatingVibration(effect)
+                && mCurrentVibration != null && isRepeatingVibration(mCurrentVibration.mEffect)) {
+            if (DEBUG) {
+                Slog.d(TAG, "Ignoring incoming vibration in favor of alarm vibration");
+            }
+            return;
+        }
+
         Vibration vib = new Vibration(token, effect, usageHint, uid, opPkg);
 
         // Only link against waveforms since they potentially don't have a finish if
@@ -404,6 +416,16 @@
         }
     }
 
+    private static boolean isRepeatingVibration(VibrationEffect effect) {
+        if (effect instanceof VibrationEffect.Waveform) {
+            final VibrationEffect.Waveform waveform = (VibrationEffect.Waveform) effect;
+            if (waveform.getRepeatIndex() >= 0) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     private void addToPreviousVibrationsLocked(Vibration vib) {
         if (mPreviousVibrations.size() > mPreviousVibrationsLimit) {
             mPreviousVibrations.removeFirst();
diff --git a/com/android/server/accessibility/AccessibilityInputFilter.java b/com/android/server/accessibility/AccessibilityInputFilter.java
index c60647f..f6fcaae 100644
--- a/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -19,6 +19,9 @@
 import android.content.Context;
 import android.os.Handler;
 import android.os.PowerManager;
+import android.util.DebugUtils;
+import android.util.ExceptionUtils;
+import android.util.Log;
 import android.util.Pools.SimplePool;
 import android.util.Slog;
 import android.util.SparseBooleanArray;
@@ -31,6 +34,7 @@
 import android.view.WindowManagerPolicy;
 import android.view.accessibility.AccessibilityEvent;
 
+import com.android.internal.util.BitUtils;
 import com.android.server.LocalServices;
 
 /**
@@ -188,6 +192,7 @@
         }
 
         if (mEventHandler == null) {
+            if (DEBUG) Slog.d(TAG, "mEventHandler == null for event " + event);
             super.onInputEvent(event, policyFlags);
             return;
         }
@@ -339,6 +344,8 @@
             MotionEvent transformedEvent = MotionEvent.obtain(event);
             mEventHandler.onMotionEvent(transformedEvent, event, policyFlags);
             transformedEvent.recycle();
+        } else {
+            if (DEBUG) Slog.d(TAG, "mEventHandler == null for " + event);
         }
     }
 
@@ -366,11 +373,20 @@
     }
 
     @Override
+    public EventStreamTransformation getNext() {
+        return null;
+    }
+
+    @Override
     public void clearEvents(int inputSource) {
         /* do nothing */
     }
 
     void setUserAndEnabledFeatures(int userId, int enabledFeatures) {
+        if (DEBUG) {
+            Slog.i(TAG, "setUserAndEnabledFeatures(userId = " + userId + ", enabledFeatures = 0x"
+                    + Integer.toHexString(enabledFeatures) + ")");
+        }
         if (mEnabledFeatures == enabledFeatures && mUserId == userId) {
             return;
         }
@@ -397,6 +413,8 @@
     }
 
     private void enableFeatures() {
+        if (DEBUG) Slog.i(TAG, "enableFeatures()");
+
         resetStreamState();
 
         if ((mEnabledFeatures & FLAG_FEATURE_AUTOCLICK) != 0) {
@@ -443,7 +461,7 @@
      */
     private void addFirstEventHandler(EventStreamTransformation handler) {
         if (mEventHandler != null) {
-           handler.setNext(mEventHandler);
+            handler.setNext(mEventHandler);
         } else {
             handler.setNext(this);
         }
diff --git a/com/android/server/accessibility/AutoclickController.java b/com/android/server/accessibility/AutoclickController.java
index 892e9da..f5b0eb1 100644
--- a/com/android/server/accessibility/AutoclickController.java
+++ b/com/android/server/accessibility/AutoclickController.java
@@ -23,15 +23,12 @@
 import android.net.Uri;
 import android.os.Handler;
 import android.os.SystemClock;
-import android.os.UserHandle;
 import android.provider.Settings;
-import android.util.Slog;
 import android.view.InputDevice;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.MotionEvent.PointerCoords;
 import android.view.MotionEvent.PointerProperties;
-import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
 
 /**
@@ -55,11 +52,10 @@
  *
  * Each instance is associated to a single user (and it does not handle user switch itself).
  */
-public class AutoclickController implements EventStreamTransformation {
+public class AutoclickController extends BaseEventStreamTransformation {
 
     private static final String LOG_TAG = AutoclickController.class.getSimpleName();
 
-    private EventStreamTransformation mNext;
     private final Context mContext;
     private final int mUserId;
 
@@ -88,9 +84,7 @@
             mClickScheduler.cancel();
         }
 
-        if (mNext != null) {
-            mNext.onMotionEvent(event, rawEvent, policyFlags);
-        }
+        super.onMotionEvent(event, rawEvent, policyFlags);
     }
 
     @Override
@@ -103,21 +97,7 @@
             }
         }
 
-        if (mNext != null) {
-          mNext.onKeyEvent(event, policyFlags);
-        }
-    }
-
-    @Override
-    public void onAccessibilityEvent(AccessibilityEvent event) {
-        if (mNext != null) {
-            mNext.onAccessibilityEvent(event);
-        }
-    }
-
-    @Override
-    public void setNext(EventStreamTransformation next) {
-        mNext = next;
+        super.onKeyEvent(event, policyFlags);
     }
 
     @Override
@@ -126,9 +106,7 @@
             mClickScheduler.cancel();
         }
 
-        if (mNext != null) {
-            mNext.clearEvents(inputSource);
-        }
+        super.clearEvents(inputSource);
     }
 
     @Override
@@ -418,7 +396,7 @@
          * Creates and forwards click event sequence.
          */
         private void sendClick() {
-            if (mLastMotionEvent == null || mNext == null) {
+            if (mLastMotionEvent == null || getNext() == null) {
                 return;
             }
 
@@ -448,10 +426,10 @@
             MotionEvent upEvent = MotionEvent.obtain(downEvent);
             upEvent.setAction(MotionEvent.ACTION_UP);
 
-            mNext.onMotionEvent(downEvent, downEvent, mEventPolicyFlags);
+            AutoclickController.super.onMotionEvent(downEvent, downEvent, mEventPolicyFlags);
             downEvent.recycle();
 
-            mNext.onMotionEvent(upEvent, upEvent, mEventPolicyFlags);
+            AutoclickController.super.onMotionEvent(upEvent, upEvent, mEventPolicyFlags);
             upEvent.recycle();
         }
 
diff --git a/com/android/server/accessibility/BaseEventStreamTransformation.java b/com/android/server/accessibility/BaseEventStreamTransformation.java
new file mode 100644
index 0000000..ce54586
--- /dev/null
+++ b/com/android/server/accessibility/BaseEventStreamTransformation.java
@@ -0,0 +1,31 @@
+/*
+ ** Copyright 2017, 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.accessibility;
+
+abstract class BaseEventStreamTransformation implements EventStreamTransformation {
+    private EventStreamTransformation mNext;
+
+    @Override
+    public void setNext(EventStreamTransformation next) {
+        mNext = next;
+    }
+
+    @Override
+    public EventStreamTransformation getNext() {
+        return mNext;
+    }
+}
\ No newline at end of file
diff --git a/com/android/server/accessibility/EventStreamTransformation.java b/com/android/server/accessibility/EventStreamTransformation.java
index fdc4098..7982996 100644
--- a/com/android/server/accessibility/EventStreamTransformation.java
+++ b/com/android/server/accessibility/EventStreamTransformation.java
@@ -65,7 +65,12 @@
      * @param rawEvent The raw motion event.
      * @param policyFlags Policy flags for the event.
      */
-    public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags);
+    default void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        EventStreamTransformation next = getNext();
+        if (next != null) {
+            next.onMotionEvent(event, rawEvent, policyFlags);
+        }
+    }
 
     /**
      * Receives a key event.
@@ -73,14 +78,24 @@
      * @param event The key event.
      * @param policyFlags Policy flags for the event.
      */
-    public void onKeyEvent(KeyEvent event, int policyFlags);
+    default void onKeyEvent(KeyEvent event, int policyFlags) {
+        EventStreamTransformation next = getNext();
+        if (next != null) {
+            next.onKeyEvent(event, policyFlags);
+        }
+    }
 
     /**
      * Receives an accessibility event.
      *
      * @param event The accessibility event.
      */
-    public void onAccessibilityEvent(AccessibilityEvent event);
+    default void onAccessibilityEvent(AccessibilityEvent event) {
+        EventStreamTransformation next = getNext();
+        if (next != null) {
+            next.onAccessibilityEvent(event);
+        }
+    };
 
     /**
      * Sets the next transformation.
@@ -90,14 +105,26 @@
     public void setNext(EventStreamTransformation next);
 
     /**
+     * Gets the next transformation.
+     *
+     * @return The next transformation.
+     */
+    public EventStreamTransformation getNext();
+
+    /**
      * Clears internal state associated with events from specific input source.
      *
      * @param inputSource The input source class for which transformation state should be cleared.
      */
-    public void clearEvents(int inputSource);
+    default void clearEvents(int inputSource) {
+        EventStreamTransformation next = getNext();
+        if (next != null) {
+            next.clearEvents(inputSource);
+        }
+    }
 
     /**
      * Destroys this transformation.
      */
-    public void onDestroy();
+    default void onDestroy() {}
 }
diff --git a/com/android/server/accessibility/KeyboardInterceptor.java b/com/android/server/accessibility/KeyboardInterceptor.java
index f00a954..7724945 100644
--- a/com/android/server/accessibility/KeyboardInterceptor.java
+++ b/com/android/server/accessibility/KeyboardInterceptor.java
@@ -22,14 +22,12 @@
 import android.util.Pools;
 import android.util.Slog;
 import android.view.KeyEvent;
-import android.view.MotionEvent;
 import android.view.WindowManagerPolicy;
-import android.view.accessibility.AccessibilityEvent;
 
 /**
  * Intercepts key events and forwards them to accessibility manager service.
  */
-public class KeyboardInterceptor implements EventStreamTransformation, Handler.Callback {
+public class KeyboardInterceptor extends BaseEventStreamTransformation implements Handler.Callback {
     private static final int MESSAGE_PROCESS_QUEUED_EVENTS = 1;
     private static final String LOG_TAG = "KeyboardInterceptor";
 
@@ -37,7 +35,6 @@
     private final WindowManagerPolicy mPolicy;
     private final Handler mHandler;
 
-    private EventStreamTransformation mNext;
     private KeyEventHolder mEventQueueStart;
     private KeyEventHolder mEventQueueEnd;
 
@@ -65,13 +62,6 @@
     }
 
     @Override
-    public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
-        if (mNext != null) {
-            mNext.onMotionEvent(event, rawEvent, policyFlags);
-        }
-    }
-
-    @Override
     public void onKeyEvent(KeyEvent event, int policyFlags) {
         /*
          * Certain keys have system-level behavior that affects accessibility services.
@@ -90,29 +80,6 @@
     }
 
     @Override
-    public void onAccessibilityEvent(AccessibilityEvent event) {
-        if (mNext != null) {
-            mNext.onAccessibilityEvent(event);
-        }
-    }
-
-    @Override
-    public void setNext(EventStreamTransformation next) {
-        mNext = next;
-    }
-
-    @Override
-    public void clearEvents(int inputSource) {
-        if (mNext != null) {
-            mNext.clearEvents(inputSource);
-        }
-    }
-
-    @Override
-    public void onDestroy() {
-    }
-
-    @Override
     public boolean handleMessage(Message msg) {
         if (msg.what != MESSAGE_PROCESS_QUEUED_EVENTS) {
             Slog.e(LOG_TAG, "Unexpected message type");
diff --git a/com/android/server/accessibility/MagnificationController.java b/com/android/server/accessibility/MagnificationController.java
index 98b8e6b..a10b7a2 100644
--- a/com/android/server/accessibility/MagnificationController.java
+++ b/com/android/server/accessibility/MagnificationController.java
@@ -56,6 +56,7 @@
  * constraints.
  */
 class MagnificationController implements Handler.Callback {
+    private static final boolean DEBUG = false;
     private static final String LOG_TAG = "MagnificationController";
 
     public static final float MIN_SCALE = 1.0f;
@@ -509,6 +510,12 @@
 
     private boolean setScaleAndCenterLocked(float scale, float centerX, float centerY,
             boolean animate, int id) {
+        if (DEBUG) {
+            Slog.i(LOG_TAG,
+                    "setScaleAndCenterLocked(scale = " + scale + ", centerX = " + centerX
+                            + ", centerY = " + centerY + ", animate = " + animate + ", id = " + id
+                            + ")");
+        }
         final boolean changed = updateMagnificationSpecLocked(scale, centerX, centerY);
         sendSpecToAnimation(mCurrentMagnificationSpec, animate);
         if (isMagnifying() && (id != INVALID_ID)) {
@@ -535,7 +542,9 @@
 
             final float nonNormOffsetX = mCurrentMagnificationSpec.offsetX - offsetX;
             final float nonNormOffsetY = mCurrentMagnificationSpec.offsetY - offsetY;
-            updateCurrentSpecWithOffsetsLocked(nonNormOffsetX, nonNormOffsetY);
+            if (updateCurrentSpecWithOffsetsLocked(nonNormOffsetX, nonNormOffsetY)) {
+                onMagnificationChangedLocked();
+            }
             if (id != INVALID_ID) {
                 mIdOfLastServiceToMagnify = id;
             }
@@ -633,6 +642,11 @@
     }
 
     private boolean updateCurrentSpecWithOffsetsLocked(float nonNormOffsetX, float nonNormOffsetY) {
+        if (DEBUG) {
+            Slog.i(LOG_TAG,
+                    "updateCurrentSpecWithOffsetsLocked(nonNormOffsetX = " + nonNormOffsetX
+                            + ", nonNormOffsetY = " + nonNormOffsetY + ")");
+        }
         boolean changed = false;
         final float offsetX = MathUtils.constrain(nonNormOffsetX, getMinOffsetXLocked(), 0);
         if (Float.compare(mCurrentMagnificationSpec.offsetX, offsetX) != 0) {
@@ -750,6 +764,9 @@
     }
 
     private void sendSpecToAnimation(MagnificationSpec spec, boolean animate) {
+        if (DEBUG) {
+            Slog.i(LOG_TAG, "sendSpecToAnimation(spec = " + spec + ", animate = " + animate + ")");
+        }
         if (Thread.currentThread().getId() == mMainThreadId) {
             mSpecAnimationBridge.updateSentSpecMainThread(spec, animate);
         } else {
diff --git a/com/android/server/accessibility/MagnificationGestureHandler.java b/com/android/server/accessibility/MagnificationGestureHandler.java
index d6452f8..9b2b4eb 100644
--- a/com/android/server/accessibility/MagnificationGestureHandler.java
+++ b/com/android/server/accessibility/MagnificationGestureHandler.java
@@ -42,14 +42,12 @@
 import android.util.TypedValue;
 import android.view.GestureDetector;
 import android.view.GestureDetector.SimpleOnGestureListener;
-import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.MotionEvent.PointerCoords;
 import android.view.MotionEvent.PointerProperties;
 import android.view.ScaleGestureDetector;
 import android.view.ScaleGestureDetector.OnScaleGestureListener;
 import android.view.ViewConfiguration;
-import android.view.accessibility.AccessibilityEvent;
 
 import com.android.internal.annotations.VisibleForTesting;
 
@@ -102,31 +100,23 @@
  * 7. The magnification scale will be persisted in settings and in the cloud.
  */
 @SuppressWarnings("WeakerAccess")
-class MagnificationGestureHandler implements EventStreamTransformation {
-    private static final String LOG_TAG = "MagnificationEventHandler";
+class MagnificationGestureHandler extends BaseEventStreamTransformation {
+    private static final String LOG_TAG = "MagnificationGestureHandler";
 
     private static final boolean DEBUG_ALL = false;
     private static final boolean DEBUG_STATE_TRANSITIONS = false || DEBUG_ALL;
     private static final boolean DEBUG_DETECTING = false || DEBUG_ALL;
-    private static final boolean DEBUG_PANNING = false || DEBUG_ALL;
-
-    /** @see #handleMotionEventStateDelegating */
-    @VisibleForTesting static final int STATE_DELEGATING = 1;
-    /** @see DetectingStateHandler */
-    @VisibleForTesting static final int STATE_DETECTING = 2;
-    /** @see ViewportDraggingStateHandler */
-    @VisibleForTesting static final int STATE_VIEWPORT_DRAGGING = 3;
-    /** @see PanningScalingStateHandler */
-    @VisibleForTesting static final int STATE_PANNING_SCALING = 4;
+    private static final boolean DEBUG_PANNING_SCALING = false || DEBUG_ALL;
 
     private static final float MIN_SCALE = 2.0f;
     private static final float MAX_SCALE = 5.0f;
 
     @VisibleForTesting final MagnificationController mMagnificationController;
 
-    @VisibleForTesting final DetectingStateHandler mDetectingStateHandler;
-    @VisibleForTesting final PanningScalingStateHandler mPanningScalingStateHandler;
-    @VisibleForTesting final ViewportDraggingStateHandler mViewportDraggingStateHandler;
+    @VisibleForTesting final DelegatingState mDelegatingState;
+    @VisibleForTesting final DetectingState mDetectingState;
+    @VisibleForTesting final PanningScalingState mPanningScalingState;
+    @VisibleForTesting final ViewportDraggingState mViewportDraggingState;
 
     private final ScreenStateReceiver mScreenStateReceiver;
 
@@ -138,21 +128,12 @@
     final boolean mDetectTripleTap;
 
     /**
-     * Whether {@link #mShortcutTriggered shortcut} is enabled
+     * Whether {@link DetectingState#mShortcutTriggered shortcut} is enabled
      */
     final boolean mDetectShortcutTrigger;
 
-    EventStreamTransformation mNext;
-
-    @VisibleForTesting int mCurrentState;
-    @VisibleForTesting int mPreviousState;
-
-    @VisibleForTesting boolean mShortcutTriggered;
-
-    /**
-     * Time of last {@link MotionEvent#ACTION_DOWN} while in {@link #STATE_DELEGATING}
-     */
-    long mDelegatingStateDownTime;
+    @VisibleForTesting State mCurrentState;
+    @VisibleForTesting State mPreviousState;
 
     private PointerCoords[] mTempPointerCoords;
     private PointerProperties[] mTempPointerProperties;
@@ -174,10 +155,10 @@
             boolean detectShortcutTrigger) {
         mMagnificationController = magnificationController;
 
-        mDetectingStateHandler = new DetectingStateHandler(context);
-        mViewportDraggingStateHandler = new ViewportDraggingStateHandler();
-        mPanningScalingStateHandler =
-                new PanningScalingStateHandler(context);
+        mDelegatingState = new DelegatingState();
+        mDetectingState = new DetectingState(context);
+        mViewportDraggingState = new ViewportDraggingState();
+        mPanningScalingState = new PanningScalingState(context);
 
         mDetectTripleTap = detectTripleTap;
         mDetectShortcutTrigger = detectShortcutTrigger;
@@ -189,62 +170,29 @@
             mScreenStateReceiver = null;
         }
 
-        transitionTo(STATE_DETECTING);
+        transitionTo(mDetectingState);
     }
 
     @Override
     public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        if (DEBUG_ALL) Slog.i(LOG_TAG, "onMotionEvent(" + event + ")");
+
         if ((!mDetectTripleTap && !mDetectShortcutTrigger)
                 || !event.isFromSource(SOURCE_TOUCHSCREEN)) {
             dispatchTransformedEvent(event, rawEvent, policyFlags);
             return;
         }
-        // Local copy to avoid dispatching the same event to more than one state handler
-        // in case mPanningScalingStateHandler changes mCurrentState
-        int currentState = mCurrentState;
-        mPanningScalingStateHandler.onMotionEvent(event, rawEvent, policyFlags);
-        switch (currentState) {
-            case STATE_DELEGATING: {
-                handleMotionEventStateDelegating(event, rawEvent, policyFlags);
-            }
-            break;
-            case STATE_DETECTING: {
-                mDetectingStateHandler.onMotionEvent(event, rawEvent, policyFlags);
-            }
-            break;
-            case STATE_VIEWPORT_DRAGGING: {
-                mViewportDraggingStateHandler.onMotionEvent(event, rawEvent, policyFlags);
-            }
-            break;
-            case STATE_PANNING_SCALING: {
-                // mPanningScalingStateHandler handles events only
-                // if this is the current state since it uses ScaleGestureDetector
-                // and a GestureDetector which need well formed event stream.
-            }
-            break;
-            default: {
-                throw new IllegalStateException("Unknown state: " + currentState);
-            }
-        }
+
+        handleEventWith(mCurrentState, event, rawEvent, policyFlags);
     }
 
-    @Override
-    public void onKeyEvent(KeyEvent event, int policyFlags) {
-        if (mNext != null) {
-            mNext.onKeyEvent(event, policyFlags);
-        }
-    }
+    private void handleEventWith(State stateHandler,
+            MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        // To keep InputEventConsistencyVerifiers within GestureDetectors happy
+        mPanningScalingState.mScrollGestureDetector.onTouchEvent(event);
+        mPanningScalingState.mScaleGestureDetector.onTouchEvent(event);
 
-    @Override
-    public void onAccessibilityEvent(AccessibilityEvent event) {
-        if (mNext != null) {
-            mNext.onAccessibilityEvent(event);
-        }
-    }
-
-    @Override
-    public void setNext(EventStreamTransformation next) {
-        mNext = next;
+        stateHandler.onMotionEvent(event, rawEvent, policyFlags);
     }
 
     @Override
@@ -253,13 +201,16 @@
             clearAndTransitionToStateDetecting();
         }
 
-        if (mNext != null) {
-            mNext.clearEvents(inputSource);
-        }
+        super.clearEvents(inputSource);
     }
 
     @Override
     public void onDestroy() {
+        if (DEBUG_STATE_TRANSITIONS) {
+            Slog.i(LOG_TAG, "onDestroy(); delayed = "
+                    + MotionEventInfo.toString(mDetectingState.mDelayedEventQueue));
+        }
+
         if (mScreenStateReceiver != null) {
             mScreenStateReceiver.unregister();
         }
@@ -272,59 +223,21 @@
             if (wasMagnifying) {
                 clearAndTransitionToStateDetecting();
             } else {
-                toggleShortcutTriggered();
+                mDetectingState.toggleShortcutTriggered();
             }
         }
     }
 
-    private void toggleShortcutTriggered() {
-        setShortcutTriggered(!mShortcutTriggered);
-    }
-
-    private void setShortcutTriggered(boolean state) {
-        if (mShortcutTriggered == state) {
-            return;
-        }
-
-        mShortcutTriggered = state;
-        mMagnificationController.setForceShowMagnifiableBounds(state);
-    }
-
     void clearAndTransitionToStateDetecting() {
-        setShortcutTriggered(false);
-        mCurrentState = STATE_DETECTING;
-        mDetectingStateHandler.clear();
-        mViewportDraggingStateHandler.clear();
-        mPanningScalingStateHandler.clear();
-    }
-
-    private void handleMotionEventStateDelegating(MotionEvent event,
-            MotionEvent rawEvent, int policyFlags) {
-        if (event.getActionMasked() == ACTION_UP) {
-            transitionTo(STATE_DETECTING);
-        }
-        delegateEvent(event, rawEvent, policyFlags);
-    }
-
-    void delegateEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
-        if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
-            mDelegatingStateDownTime = event.getDownTime();
-        }
-        if (mNext != null) {
-            // We cache some events to see if the user wants to trigger magnification.
-            // If no magnification is triggered we inject these events with adjusted
-            // time and down time to prevent subsequent transformations being confused
-            // by stale events. After the cached events, which always have a down, are
-            // injected we need to also update the down time of all subsequent non cached
-            // events. All delegated events cached and non-cached are delivered here.
-            event.setDownTime(mDelegatingStateDownTime);
-            dispatchTransformedEvent(event, rawEvent, policyFlags);
-        }
+        mCurrentState = mDelegatingState;
+        mDetectingState.clear();
+        mViewportDraggingState.clear();
+        mPanningScalingState.clear();
     }
 
     private void dispatchTransformedEvent(MotionEvent event, MotionEvent rawEvent,
             int policyFlags) {
-        if (mNext == null) return; // Nowhere to dispatch to
+        if (DEBUG_ALL) Slog.i(LOG_TAG, "dispatchTransformedEvent(event = " + event + ")");
 
         // If the touchscreen event is within the magnified portion of the screen we have
         // to change its location to be where the user thinks he is poking the
@@ -351,7 +264,7 @@
                     coords, 0, 0, 1.0f, 1.0f, event.getDeviceId(), 0, event.getSource(),
                     event.getFlags());
         }
-        mNext.onMotionEvent(event, rawEvent, policyFlags);
+        super.onMotionEvent(event, rawEvent, policyFlags);
     }
 
     private PointerCoords[] getTempPointerCoordsWithMinSize(int size) {
@@ -386,9 +299,10 @@
         return mTempPointerProperties;
     }
 
-    private void transitionTo(int state) {
+    private void transitionTo(State state) {
         if (DEBUG_STATE_TRANSITIONS) {
-            Slog.i(LOG_TAG, (stateToString(mCurrentState) + " -> " + stateToString(state)
+            Slog.i(LOG_TAG,
+                    (State.nameOf(mCurrentState) + " -> " + State.nameOf(state)
                     + " at " + asList(copyOfRange(new RuntimeException().getStackTrace(), 1, 5)))
                     .replace(getClass().getName(), ""));
         }
@@ -396,40 +310,40 @@
         mCurrentState = state;
     }
 
-    private static String stateToString(int state) {
-        switch (state) {
-            case STATE_DELEGATING: return "STATE_DELEGATING";
-            case STATE_DETECTING: return "STATE_DETECTING";
-            case STATE_VIEWPORT_DRAGGING: return "STATE_VIEWPORT_DRAGGING";
-            case STATE_PANNING_SCALING: return "STATE_PANNING_SCALING";
-            case 0: return "0";
-            default: throw new IllegalArgumentException("Unknown state: " + state);
-        }
-    }
-
-    private interface MotionEventHandler {
-
+    interface State {
         void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags);
 
-        void clear();
+        default void clear() {}
+
+        default String name() {
+            return getClass().getSimpleName();
+        }
+
+        static String nameOf(@Nullable State s) {
+            return s != null ? s.name() : "null";
+        }
     }
 
     /**
      * This class determines if the user is performing a scale or pan gesture.
      *
-     * @see #STATE_PANNING_SCALING
+     * Unlike when {@link ViewportDraggingState dragging the viewport}, in panning mode the viewport
+     * moves in the same direction as the fingers, and allows to easily and precisely scale the
+     * magnification level.
+     * This makes it the preferred mode for one-off adjustments, due to its precision and ease of
+     * triggering.
      */
-    final class PanningScalingStateHandler extends SimpleOnGestureListener
-            implements OnScaleGestureListener, MotionEventHandler {
+    final class PanningScalingState extends SimpleOnGestureListener
+            implements OnScaleGestureListener, State {
 
         private final ScaleGestureDetector mScaleGestureDetector;
-        private final GestureDetector mGestureDetector;
+        private final GestureDetector mScrollGestureDetector;
         final float mScalingThreshold;
 
         float mInitialScaleFactor = -1;
         boolean mScaling;
 
-        public PanningScalingStateHandler(Context context) {
+        public PanningScalingState(Context context) {
             final TypedValue scaleValue = new TypedValue();
             context.getResources().getValue(
                     com.android.internal.R.dimen.config_screen_magnification_scaling_threshold,
@@ -437,35 +351,27 @@
             mScalingThreshold = scaleValue.getFloat();
             mScaleGestureDetector = new ScaleGestureDetector(context, this);
             mScaleGestureDetector.setQuickScaleEnabled(false);
-            mGestureDetector = new GestureDetector(context, this);
+            mScrollGestureDetector = new GestureDetector(context, this);
         }
 
         @Override
         public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
-            // Dispatches #onScaleBegin, #onScale, #onScaleEnd
-            mScaleGestureDetector.onTouchEvent(event);
-            // Dispatches #onScroll
-            mGestureDetector.onTouchEvent(event);
-
-            if (mCurrentState != STATE_PANNING_SCALING) {
-                return;
-            }
-
             int action = event.getActionMasked();
+
             if (action == ACTION_POINTER_UP
                     && event.getPointerCount() == 2 // includes the pointer currently being released
-                    && mPreviousState == STATE_VIEWPORT_DRAGGING) {
+                    && mPreviousState == mViewportDraggingState) {
 
-                persistScaleAndTransitionTo(STATE_VIEWPORT_DRAGGING);
+                persistScaleAndTransitionTo(mViewportDraggingState);
 
             } else if (action == ACTION_UP) {
 
-                persistScaleAndTransitionTo(STATE_DETECTING);
+                persistScaleAndTransitionTo(mDetectingState);
 
             }
         }
 
-        public void persistScaleAndTransitionTo(int state) {
+        public void persistScaleAndTransitionTo(State state) {
             mMagnificationController.persistScale();
             clear();
             transitionTo(state);
@@ -474,16 +380,16 @@
         @Override
         public boolean onScroll(MotionEvent first, MotionEvent second,
                 float distanceX, float distanceY) {
-            if (mCurrentState != STATE_PANNING_SCALING) {
+            if (mCurrentState != mPanningScalingState) {
                 return true;
             }
-            if (DEBUG_PANNING) {
+            if (DEBUG_PANNING_SCALING) {
                 Slog.i(LOG_TAG, "Panned content by scrollX: " + distanceX
                         + " scrollY: " + distanceY);
             }
             mMagnificationController.offsetMagnifiedRegion(distanceX, distanceY,
                     AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
-            return true;
+            return /* event consumed: */ true;
         }
 
         @Override
@@ -494,12 +400,8 @@
                     return false;
                 }
                 final float deltaScale = detector.getScaleFactor() - mInitialScaleFactor;
-                if (abs(deltaScale) > mScalingThreshold) {
-                    mScaling = true;
-                    return true;
-                } else {
-                    return false;
-                }
+                mScaling = abs(deltaScale) > mScalingThreshold;
+                return mScaling;
             }
 
             final float initialScale = mMagnificationController.getScale();
@@ -523,14 +425,15 @@
 
             final float pivotX = detector.getFocusX();
             final float pivotY = detector.getFocusY();
+            if (DEBUG_PANNING_SCALING) Slog.i(LOG_TAG, "Scaled content to: " + scale + "x");
             mMagnificationController.setScale(scale, pivotX, pivotY, false,
                     AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
-            return true;
+            return /* handled: */ true;
         }
 
         @Override
         public boolean onScaleBegin(ScaleGestureDetector detector) {
-            return (mCurrentState == STATE_PANNING_SCALING);
+            return /* continue recognizing: */ (mCurrentState == mPanningScalingState);
         }
 
         @Override
@@ -546,7 +449,7 @@
 
         @Override
         public String toString() {
-            return "MagnifiedContentInteractionStateHandler{" +
+            return "PanningScalingState{" +
                     "mInitialScaleFactor=" + mInitialScaleFactor +
                     ", mScaling=" + mScaling +
                     '}';
@@ -558,9 +461,11 @@
      * determined that the user is performing a single-finger drag of the
      * magnification viewport.
      *
-     * @see #STATE_VIEWPORT_DRAGGING
+     * Unlike when {@link PanningScalingState panning}, the viewport moves in the opposite direction
+     * of the finger, and any part of the screen is reachable without lifting the finger.
+     * This makes it the preferable mode for tasks like reading text spanning full screen width.
      */
-    final class ViewportDraggingStateHandler implements MotionEventHandler {
+    final class ViewportDraggingState implements State {
 
         /** Whether to disable zoom after dragging ends */
         boolean mZoomedInBeforeDrag;
@@ -572,7 +477,7 @@
             switch (action) {
                 case ACTION_POINTER_DOWN: {
                     clear();
-                    transitionTo(STATE_PANNING_SCALING);
+                    transitionTo(mPanningScalingState);
                 }
                 break;
                 case ACTION_MOVE: {
@@ -594,7 +499,7 @@
                 case ACTION_UP: {
                     if (!mZoomedInBeforeDrag) zoomOff();
                     clear();
-                    transitionTo(STATE_DETECTING);
+                    transitionTo(mDetectingState);
                 }
                 break;
 
@@ -613,25 +518,51 @@
 
         @Override
         public String toString() {
-            return "ViewportDraggingStateHandler{" +
+            return "ViewportDraggingState{" +
                     "mZoomedInBeforeDrag=" + mZoomedInBeforeDrag +
                     ", mLastMoveOutsideMagnifiedRegion=" + mLastMoveOutsideMagnifiedRegion +
                     '}';
         }
     }
 
+    final class DelegatingState implements State {
+        /**
+         * Time of last {@link MotionEvent#ACTION_DOWN} while in {@link DelegatingState}
+         */
+        public long mLastDelegatedDownEventTime;
+
+        @Override
+        public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+            if (event.getActionMasked() == ACTION_UP) {
+                transitionTo(mDetectingState);
+            }
+
+            if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
+                mLastDelegatedDownEventTime = event.getDownTime();
+            }
+            if (getNext() != null) {
+                // We cache some events to see if the user wants to trigger magnification.
+                // If no magnification is triggered we inject these events with adjusted
+                // time and down time to prevent subsequent transformations being confused
+                // by stale events. After the cached events, which always have a down, are
+                // injected we need to also update the down time of all subsequent non cached
+                // events. All delegated events cached and non-cached are delivered here.
+                event.setDownTime(mLastDelegatedDownEventTime);
+                dispatchTransformedEvent(event, rawEvent, policyFlags);
+            }
+        }
+    }
+
     /**
      * This class handles motion events when the event dispatch has not yet
      * determined what the user is doing. It watches for various tap events.
-     *
-     * @see #STATE_DETECTING
      */
-    final class DetectingStateHandler implements MotionEventHandler, Handler.Callback {
+    final class DetectingState implements State, Handler.Callback {
 
         private static final int MESSAGE_ON_TRIPLE_TAP_AND_HOLD = 1;
         private static final int MESSAGE_TRANSITION_TO_DELEGATING_STATE = 2;
 
-        final int mLongTapMinDelay = ViewConfiguration.getJumpTapTimeout();
+        final int mLongTapMinDelay;
         final int mSwipeMinDistance;
         final int mMultiTapMaxDelay;
         final int mMultiTapMaxDistance;
@@ -642,9 +573,12 @@
         private MotionEvent mLastUp;
         private MotionEvent mPreLastUp;
 
+        @VisibleForTesting boolean mShortcutTriggered;
+
         Handler mHandler = new Handler(this);
 
-        public DetectingStateHandler(Context context) {
+        public DetectingState(Context context) {
+            mLongTapMinDelay = ViewConfiguration.getLongPressTimeout();
             mMultiTapMaxDelay = ViewConfiguration.getDoubleTapTimeout()
                     + context.getResources().getInteger(
                     com.android.internal.R.integer.config_screen_magnification_multi_tap_adjustment);
@@ -661,7 +595,7 @@
                 }
                 break;
                 case MESSAGE_TRANSITION_TO_DELEGATING_STATE: {
-                    transitionToDelegatingState(/* andClear */ true);
+                    transitionToDelegatingStateAndClear();
                 }
                 break;
                 default: {
@@ -682,12 +616,12 @@
                     if (!mMagnificationController.magnificationRegionContains(
                             event.getX(), event.getY())) {
 
-                        transitionToDelegatingState(/* andClear */ !mShortcutTriggered);
+                        transitionToDelegatingStateAndClear();
 
                     } else if (isMultiTapTriggered(2 /* taps */)) {
 
                         // 3tap and hold
-                        delayedTransitionToDraggingState(event);
+                        afterLongTapTimeoutTransitionToDraggingState(event);
 
                     } else if (mDetectTripleTap
                             // If magnified, delay an ACTION_DOWN for mMultiTapMaxDelay
@@ -695,21 +629,21 @@
                             // STATE_PANNING_SCALING(triggerable with ACTION_POINTER_DOWN)
                             || mMagnificationController.isMagnifying()) {
 
-                        delayedTransitionToDelegatingState();
+                        afterMultiTapTimeoutTransitionToDelegatingState();
 
                     } else {
 
                         // Delegate pending events without delay
-                        transitionToDelegatingState(/* andClear */ true);
+                        transitionToDelegatingStateAndClear();
                     }
                 }
                 break;
                 case ACTION_POINTER_DOWN: {
                     if (mMagnificationController.isMagnifying()) {
-                        transitionTo(STATE_PANNING_SCALING);
+                        transitionTo(mPanningScalingState);
                         clear();
                     } else {
-                        transitionToDelegatingState(/* andClear */ true);
+                        transitionToDelegatingStateAndClear();
                     }
                 }
                 break;
@@ -722,7 +656,7 @@
                             && !isMultiTapTriggered(2 /* taps */)) {
 
                         // Swipe detected - delegate skipping timeout
-                        transitionToDelegatingState(/* andClear */ true);
+                        transitionToDelegatingStateAndClear();
                     }
                 }
                 break;
@@ -733,7 +667,7 @@
                     if (!mMagnificationController.magnificationRegionContains(
                             event.getX(), event.getY())) {
 
-                        transitionToDelegatingState(/* andClear */ !mShortcutTriggered);
+                        transitionToDelegatingStateAndClear();
 
                     } else if (isMultiTapTriggered(3 /* taps */)) {
 
@@ -742,12 +676,11 @@
                     } else if (
                             // Possible to be false on: 3tap&drag -> scale -> PTR_UP -> UP
                             isFingerDown()
-                                //TODO long tap should never happen here
-                            && (timeBetween(mLastDown, /* mLastUp */ event) >= mLongTapMinDelay)
-                                    || distance(mLastDown, /* mLastUp */ event)
-                                            >= mSwipeMinDistance) {
+                            //TODO long tap should never happen here
+                            && ((timeBetween(mLastDown, mLastUp) >= mLongTapMinDelay)
+                                    || (distance(mLastDown, mLastUp) >= mSwipeMinDistance))) {
 
-                        transitionToDelegatingState(/* andClear */ true);
+                        transitionToDelegatingStateAndClear();
 
                     }
                 }
@@ -795,15 +728,15 @@
             return MotionEventInfo.countOf(mDelayedEventQueue, ACTION_UP);
         }
 
-        /** -> {@link #STATE_DELEGATING} */
-        public void delayedTransitionToDelegatingState() {
+        /** -> {@link DelegatingState} */
+        public void afterMultiTapTimeoutTransitionToDelegatingState() {
             mHandler.sendEmptyMessageDelayed(
                     MESSAGE_TRANSITION_TO_DELEGATING_STATE,
                     mMultiTapMaxDelay);
         }
 
-        /** -> {@link #STATE_VIEWPORT_DRAGGING} */
-        public void delayedTransitionToDraggingState(MotionEvent event) {
+        /** -> {@link ViewportDraggingState} */
+        public void afterLongTapTimeoutTransitionToDraggingState(MotionEvent event) {
             mHandler.sendMessageDelayed(
                     mHandler.obtainMessage(MESSAGE_ON_TRIPLE_TAP_AND_HOLD, event),
                     ViewConfiguration.getLongPressTimeout());
@@ -846,11 +779,7 @@
                 MotionEventInfo info = mDelayedEventQueue;
                 mDelayedEventQueue = info.mNext;
 
-                // Because MagnifiedInteractionStateHandler requires well-formed event stream
-                mPanningScalingStateHandler.onMotionEvent(
-                        info.event, info.rawEvent, info.policyFlags);
-
-                delegateEvent(info.event, info.rawEvent, info.policyFlags);
+                handleEventWith(mDelegatingState, info.event, info.rawEvent, info.policyFlags);
 
                 info.recycle();
             }
@@ -868,10 +797,10 @@
             mLastUp = null;
         }
 
-        void transitionToDelegatingState(boolean andClear) {
-            transitionTo(STATE_DELEGATING);
+        void transitionToDelegatingStateAndClear() {
+            transitionTo(mDelegatingState);
             sendDelayedMotionEvents();
-            if (andClear) clear();
+            clear();
         }
 
         private void onTripleTap(MotionEvent up) {
@@ -895,24 +824,40 @@
             if (DEBUG_DETECTING) Slog.i(LOG_TAG, "onTripleTapAndHold()");
             clear();
 
-            mViewportDraggingStateHandler.mZoomedInBeforeDrag =
+            mViewportDraggingState.mZoomedInBeforeDrag =
                     mMagnificationController.isMagnifying();
 
             zoomOn(down.getX(), down.getY());
 
-            transitionTo(STATE_VIEWPORT_DRAGGING);
+            transitionTo(mViewportDraggingState);
         }
 
         @Override
         public String toString() {
-            return "DetectingStateHandler{" +
+            return "DetectingState{" +
                     "tapCount()=" + tapCount() +
+                    ", mShortcutTriggered=" + mShortcutTriggered +
                     ", mDelayedEventQueue=" + MotionEventInfo.toString(mDelayedEventQueue) +
                     '}';
         }
+
+        void toggleShortcutTriggered() {
+            setShortcutTriggered(!mShortcutTriggered);
+        }
+
+        void setShortcutTriggered(boolean state) {
+            if (mShortcutTriggered == state) {
+                return;
+            }
+
+            mShortcutTriggered = state;
+            mMagnificationController.setForceShowMagnifiableBounds(state);
+        }
     }
 
     private void zoomOn(float centerX, float centerY) {
+        if (DEBUG_DETECTING) Slog.i(LOG_TAG, "zoomOn(" + centerX + ", " + centerY + ")");
+
         final float scale = MathUtils.constrain(
                 mMagnificationController.getPersistedScale(),
                 MIN_SCALE, MAX_SCALE);
@@ -923,6 +868,8 @@
     }
 
     private void zoomOff() {
+        if (DEBUG_DETECTING) Slog.i(LOG_TAG, "zoomOff()");
+
         mMagnificationController.reset(/* animate */ true);
     }
 
@@ -935,16 +882,15 @@
 
     @Override
     public String toString() {
-        return "MagnificationGestureHandler{" +
-                "mDetectingStateHandler=" + mDetectingStateHandler +
-                ", mMagnifiedInteractionStateHandler=" + mPanningScalingStateHandler +
-                ", mViewportDraggingStateHandler=" + mViewportDraggingStateHandler +
+        return "MagnificationGesture{" +
+                "mDetectingState=" + mDetectingState +
+                ", mDelegatingState=" + mDelegatingState +
+                ", mMagnifiedInteractionState=" + mPanningScalingState +
+                ", mViewportDraggingState=" + mViewportDraggingState +
                 ", mDetectTripleTap=" + mDetectTripleTap +
                 ", mDetectShortcutTrigger=" + mDetectShortcutTrigger +
-                ", mCurrentState=" + stateToString(mCurrentState) +
-                ", mPreviousState=" + stateToString(mPreviousState) +
-                ", mShortcutTriggered=" + mShortcutTriggered +
-                ", mDelegatingStateDownTime=" + mDelegatingStateDownTime +
+                ", mCurrentState=" + State.nameOf(mCurrentState) +
+                ", mPreviousState=" + State.nameOf(mPreviousState) +
                 ", mMagnificationController=" + mMagnificationController +
                 '}';
     }
@@ -1051,7 +997,7 @@
 
         @Override
         public void onReceive(Context context, Intent intent) {
-            mGestureHandler.setShortcutTriggered(false);
+            mGestureHandler.mDetectingState.setShortcutTriggered(false);
         }
     }
 }
diff --git a/com/android/server/accessibility/MotionEventInjector.java b/com/android/server/accessibility/MotionEventInjector.java
index 48041ad..b6b7812 100644
--- a/com/android/server/accessibility/MotionEventInjector.java
+++ b/com/android/server/accessibility/MotionEventInjector.java
@@ -30,13 +30,13 @@
 import android.util.SparseArray;
 import android.util.SparseIntArray;
 import android.view.InputDevice;
-import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.WindowManagerPolicy;
-import android.view.accessibility.AccessibilityEvent;
+
 import com.android.internal.os.SomeArgs;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 
 /**
@@ -45,7 +45,7 @@
  * <p>
  * All methods except {@code injectEvents} must be called only from the main thread.
  */
-public class MotionEventInjector implements EventStreamTransformation, Handler.Callback {
+public class MotionEventInjector extends BaseEventStreamTransformation implements Handler.Callback {
     private static final String LOG_TAG = "MotionEventInjector";
     private static final int MESSAGE_SEND_MOTION_EVENT = 1;
     private static final int MESSAGE_INJECT_EVENTS = 2;
@@ -68,7 +68,6 @@
     private final Handler mHandler;
     private final SparseArray<Boolean> mOpenGesturesInProgress = new SparseArray<>();
 
-    private EventStreamTransformation mNext;
     private IAccessibilityServiceClient mServiceInterfaceForCurrentGesture;
     private IntArray mSequencesInProgress = new IntArray(5);
     private boolean mIsDestroyed = false;
@@ -117,25 +116,6 @@
     }
 
     @Override
-    public void onKeyEvent(KeyEvent event, int policyFlags) {
-        if (mNext != null) {
-            mNext.onKeyEvent(event, policyFlags);
-        }
-    }
-
-    @Override
-    public void onAccessibilityEvent(AccessibilityEvent event) {
-        if (mNext != null) {
-            mNext.onAccessibilityEvent(event);
-        }
-    }
-
-    @Override
-    public void setNext(EventStreamTransformation next) {
-        mNext = next;
-    }
-
-    @Override
     public void clearEvents(int inputSource) {
         /*
          * Reset state for motion events passing through so we won't send a cancel event for
@@ -187,7 +167,7 @@
             return;
         }
 
-        if (mNext == null) {
+        if (getNext() == null) {
             notifyService(serviceInterface, sequence, false);
             return;
         }
@@ -262,17 +242,24 @@
                 int continuedPointerId = mStrokeIdToPointerId
                         .get(touchPoint.mContinuedStrokeId, -1);
                 if (continuedPointerId == -1) {
+                    Slog.w(LOG_TAG, "Can't continue gesture due to unknown continued stroke id in "
+                            + touchPoint);
                     return false;
                 }
                 mStrokeIdToPointerId.put(touchPoint.mStrokeId, continuedPointerId);
                 int lastPointIndex = findPointByStrokeId(
                         mLastTouchPoints, mNumLastTouchPoints, touchPoint.mContinuedStrokeId);
                 if (lastPointIndex < 0) {
+                    Slog.w(LOG_TAG, "Can't continue gesture due continued gesture id of "
+                            + touchPoint + " not matching any previous strokes in "
+                            + Arrays.asList(mLastTouchPoints));
                     return false;
                 }
                 if (mLastTouchPoints[lastPointIndex].mIsEndOfPath
                         || (mLastTouchPoints[lastPointIndex].mX != touchPoint.mX)
                         || (mLastTouchPoints[lastPointIndex].mY != touchPoint.mY)) {
+                    Slog.w(LOG_TAG, "Can't continue gesture due to points mismatch between "
+                            + mLastTouchPoints[lastPointIndex] + " and " + touchPoint);
                     return false;
                 }
                 // Update the last touch point to match the continuation, so the gestures will
@@ -292,8 +279,8 @@
 
     private void sendMotionEventToNext(MotionEvent event, MotionEvent rawEvent,
             int policyFlags) {
-        if (mNext != null) {
-            mNext.onMotionEvent(event, rawEvent, policyFlags);
+        if (getNext() != null) {
+            super.onMotionEvent(event, rawEvent, policyFlags);
             if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
                 mOpenGesturesInProgress.put(event.getSource(), true);
             }
@@ -305,7 +292,7 @@
     }
 
     private void cancelAnyGestureInProgress(int source) {
-        if ((mNext != null) && mOpenGesturesInProgress.get(source, false)) {
+        if ((getNext() != null) && mOpenGesturesInProgress.get(source, false)) {
             long now = SystemClock.uptimeMillis();
             MotionEvent cancelEvent =
                     obtainMotionEvent(now, now, MotionEvent.ACTION_CANCEL, getLastTouchPoints(), 1);
diff --git a/com/android/server/accessibility/TouchExplorer.java b/com/android/server/accessibility/TouchExplorer.java
index e380f2c..a32686d 100644
--- a/com/android/server/accessibility/TouchExplorer.java
+++ b/com/android/server/accessibility/TouchExplorer.java
@@ -55,7 +55,8 @@
  *
  * @hide
  */
-class TouchExplorer implements EventStreamTransformation, AccessibilityGestureDetector.Listener {
+class TouchExplorer extends BaseEventStreamTransformation
+        implements AccessibilityGestureDetector.Listener {
 
     private static final boolean DEBUG = false;
 
@@ -131,9 +132,6 @@
     // the two dragging pointers as opposed to use the location of the primary one.
     private final int mScaledMinPointerDistanceToUseMiddleLocation;
 
-    // The handler to which to delegate events.
-    private EventStreamTransformation mNext;
-
     // Helper class to track received pointers.
     private final ReceivedPointerTracker mReceivedPointerTracker;
 
@@ -198,9 +196,7 @@
         if (inputSource == InputDevice.SOURCE_TOUCHSCREEN) {
             clear();
         }
-        if (mNext != null) {
-            mNext.clearEvents(inputSource);
-        }
+        super.clearEvents(inputSource);
     }
 
     @Override
@@ -258,16 +254,9 @@
     }
 
     @Override
-    public void setNext(EventStreamTransformation next) {
-        mNext = next;
-    }
-
-    @Override
     public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
         if (!event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN)) {
-            if (mNext != null) {
-                mNext.onMotionEvent(event, rawEvent, policyFlags);
-            }
+            super.onMotionEvent(event, rawEvent, policyFlags);
             return;
         }
 
@@ -311,13 +300,6 @@
     }
 
     @Override
-    public void onKeyEvent(KeyEvent event, int policyFlags) {
-        if (mNext != null) {
-            mNext.onKeyEvent(event, policyFlags);
-        }
-    }
-
-    @Override
     public void onAccessibilityEvent(AccessibilityEvent event) {
         final int eventType = event.getEventType();
 
@@ -353,9 +335,7 @@
                 mLastTouchedWindowId = event.getWindowId();
             } break;
         }
-        if (mNext != null) {
-            mNext.onAccessibilityEvent(event);
-        }
+        super.onAccessibilityEvent(event);
     }
 
     @Override
@@ -969,12 +949,10 @@
 
         // Make sure that the user will see the event.
         policyFlags |= WindowManagerPolicy.FLAG_PASS_TO_USER;
-        if (mNext != null) {
-            // TODO: For now pass null for the raw event since the touch
-            //       explorer is the last event transformation and it does
-            //       not care about the raw event.
-            mNext.onMotionEvent(event, null, policyFlags);
-        }
+        // TODO: For now pass null for the raw event since the touch
+        //       explorer is the last event transformation and it does
+        //       not care about the raw event.
+        super.onMotionEvent(event, null, policyFlags);
 
         mInjectedPointerTracker.onMotionEvent(event);
 
diff --git a/com/android/server/accounts/AccountManagerService.java b/com/android/server/accounts/AccountManagerService.java
index 4e15e5d..c684032 100644
--- a/com/android/server/accounts/AccountManagerService.java
+++ b/com/android/server/accounts/AccountManagerService.java
@@ -2248,12 +2248,10 @@
                         Log.v(TAG, getClass().getSimpleName() + " calling onResult() on response "
                                 + response);
                     }
-                    Bundle result2 = new Bundle();
-                    result2.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, removalAllowed);
                     try {
-                        response.onResult(result2);
+                        response.onResult(result);
                     } catch (RemoteException e) {
-                        // ignore
+                        Slog.e(TAG, "Error calling onResult()", e);
                     }
                 }
             }
diff --git a/com/android/server/am/ActivityDisplay.java b/com/android/server/am/ActivityDisplay.java
index 8bcbfbe..089db87 100644
--- a/com/android/server/am/ActivityDisplay.java
+++ b/com/android/server/am/ActivityDisplay.java
@@ -16,12 +16,11 @@
 
 package com.android.server.am;
 
-import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
-import static android.app.ActivityManager.StackId.getStackIdForWindowingMode;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
@@ -48,13 +47,14 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.wm.ConfigurationContainer;
 
+import java.io.PrintWriter;
 import java.util.ArrayList;
 
 /**
  * Exactly one of these classes per Display in the system. Capable of holding zero or more
  * attached {@link ActivityStack}s.
  */
-class ActivityDisplay extends ConfigurationContainer {
+class ActivityDisplay extends ConfigurationContainer<ActivityStack> {
     private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityDisplay" : TAG_AM;
     private static final String TAG_STACK = TAG + POSTFIX_STACK;
 
@@ -68,7 +68,7 @@
 
     /** All of the stacks on this display. Order matters, topmost stack is in front of all other
      * stacks, bottommost behind. Accessed directly by ActivityManager package classes */
-    final ArrayList<ActivityStack> mStacks = new ArrayList<>();
+    private final ArrayList<ActivityStack> mStacks = new ArrayList<>();
 
     /** Array of all UIDs that are present on the display. */
     private IntArray mDisplayAccessUIDs = new IntArray();
@@ -80,6 +80,13 @@
 
     private boolean mSleeping;
 
+    // Cached reference to some special stacks we tend to get a lot so we don't need to loop
+    // through the list to find them.
+    private ActivityStack mHomeStack = null;
+    private ActivityStack mRecentsStack = null;
+    private ActivityStack mPinnedStack = null;
+    private ActivityStack mSplitScreenPrimaryStack = null;
+
     ActivityDisplay(ActivityStackSupervisor supervisor, int displayId) {
         mSupervisor = supervisor;
         mDisplayId = displayId;
@@ -98,6 +105,7 @@
         }
         if (DEBUG_STACK) Slog.v(TAG_STACK, "addChild: attaching " + stack
                 + " to displayId=" + mDisplayId + " position=" + position);
+        addStackReferenceIfNeeded(stack);
         positionChildAt(stack, position);
         mSupervisor.mService.updateSleepIfNeededLocked();
     }
@@ -106,6 +114,7 @@
         if (DEBUG_STACK) Slog.v(TAG_STACK, "removeChild: detaching " + stack
                 + " from displayId=" + mDisplayId);
         mStacks.remove(stack);
+        removeStackReferenceIfNeeded(stack);
         mSupervisor.mService.updateSleepIfNeededLocked();
     }
 
@@ -150,9 +159,19 @@
      * @see ConfigurationContainer#isCompatible(int, int)
      */
     <T extends ActivityStack> T getStack(int windowingMode, int activityType) {
+        if (activityType == ACTIVITY_TYPE_HOME) {
+            return (T) mHomeStack;
+        } else if (activityType == ACTIVITY_TYPE_RECENTS) {
+            return (T) mRecentsStack;
+        }
+        if (windowingMode == WINDOWING_MODE_PINNED) {
+            return (T) mPinnedStack;
+        } else if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
+            return (T) mSplitScreenPrimaryStack;
+        }
+
         for (int i = mStacks.size() - 1; i >= 0; --i) {
             final ActivityStack stack = mStacks.get(i);
-            // TODO: Should undefined windowing and activity type be compatible with standard type?
             if (stack.isCompatible(windowingMode, activityType)) {
                 return (T) stack;
             }
@@ -160,15 +179,28 @@
         return null;
     }
 
+    private boolean alwaysCreateStack(int windowingMode, int activityType) {
+        // Always create a stack for fullscreen, freeform, and split-screen-secondary windowing
+        // modes so that we can manage visual ordering and return types correctly.
+        return activityType == ACTIVITY_TYPE_STANDARD
+                && (windowingMode == WINDOWING_MODE_FULLSCREEN
+                || windowingMode == WINDOWING_MODE_FREEFORM
+                || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+    }
+
     /**
+     * Returns an existing stack compatible with the windowing mode and activity type or creates one
+     * if a compatible stack doesn't exist.
      * @see #getStack(int, int)
      * @see #createStack(int, int, boolean)
      */
     <T extends ActivityStack> T getOrCreateStack(int windowingMode, int activityType,
             boolean onTop) {
-        T stack = getStack(windowingMode, activityType);
-        if (stack != null) {
-            return stack;
+        if (!alwaysCreateStack(windowingMode, activityType)) {
+            T stack = getStack(windowingMode, activityType);
+            if (stack != null) {
+                return stack;
+            }
         }
         return createStack(windowingMode, activityType, onTop);
     }
@@ -213,39 +245,16 @@
         if (windowingMode == WINDOWING_MODE_UNDEFINED) {
             // TODO: Should be okay to have stacks with with undefined windowing mode long term, but
             // have to set them to something for now due to logic that depending on them.
-            windowingMode = WINDOWING_MODE_FULLSCREEN;
-        }
-
-        final boolean inSplitScreenMode = hasSplitScreenStack();
-        if (!inSplitScreenMode
-                && windowingMode == WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY) {
-            // Switch to fullscreen windowing mode if we are not in split-screen mode and we are
-            // trying to launch in split-screen secondary.
-            windowingMode = WINDOWING_MODE_FULLSCREEN;
-        } else if (inSplitScreenMode && windowingMode == WINDOWING_MODE_FULLSCREEN
-                && WindowConfiguration.supportSplitScreenWindowingMode(
-                        windowingMode, activityType)) {
-            windowingMode = WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
-        }
-
-        int stackId = INVALID_STACK_ID;
-        if (mDisplayId == DEFAULT_DISPLAY && (activityType == ACTIVITY_TYPE_STANDARD
-                || activityType == ACTIVITY_TYPE_UNDEFINED)) {
-            // TODO: Will be removed once we are no longer using static stack ids.
-            stackId = getStackIdForWindowingMode(windowingMode);
-            if (stackId == INVALID_STACK_ID) {
-                // Whatever...put in fullscreen stack for now.
-                stackId = FULLSCREEN_WORKSPACE_STACK_ID;
-            }
-            final T stack = getStack(stackId);
-            if (stack != null) {
-                return stack;
+            windowingMode = getWindowingMode(); // Put in current display's windowing mode
+            if (windowingMode == WINDOWING_MODE_UNDEFINED) {
+                // Else fullscreen for now...
+                windowingMode = WINDOWING_MODE_FULLSCREEN;
             }
         }
 
-        if (stackId == INVALID_STACK_ID) {
-            stackId = mSupervisor.getNextStackId();
-        }
+        windowingMode = updateWindowingModeForSplitScreenIfNeeded(windowingMode, activityType);
+
+        final int stackId = mSupervisor.getNextStackId();
 
         final T stack = createStackUnchecked(windowingMode, activityType, stackId, onTop);
 
@@ -291,7 +300,7 @@
                 if (stack.getWindowingMode() != windowingMode) {
                     continue;
                 }
-                mSupervisor.removeStackLocked(stack.mStackId);
+                mSupervisor.removeStack(stack);
             }
         }
     }
@@ -306,12 +315,63 @@
             for (int i = mStacks.size() - 1; i >= 0; --i) {
                 final ActivityStack stack = mStacks.get(i);
                 if (stack.getActivityType() == activityType) {
-                    mSupervisor.removeStackLocked(stack.mStackId);
+                    mSupervisor.removeStack(stack);
                 }
             }
         }
     }
 
+    void onStackWindowingModeChanged(ActivityStack stack) {
+        removeStackReferenceIfNeeded(stack);
+        addStackReferenceIfNeeded(stack);
+    }
+
+    private void addStackReferenceIfNeeded(ActivityStack stack) {
+        final int activityType = stack.getActivityType();
+        final int windowingMode = stack.getWindowingMode();
+
+        if (activityType == ACTIVITY_TYPE_HOME) {
+            if (mHomeStack != null && mHomeStack != stack) {
+                throw new IllegalArgumentException("addStackReferenceIfNeeded: home stack="
+                        + mHomeStack + " already exist on display=" + this + " stack=" + stack);
+            }
+            mHomeStack = stack;
+        } else if (activityType == ACTIVITY_TYPE_RECENTS) {
+            if (mRecentsStack != null && mRecentsStack != stack) {
+                throw new IllegalArgumentException("addStackReferenceIfNeeded: recents stack="
+                        + mRecentsStack + " already exist on display=" + this + " stack=" + stack);
+            }
+            mRecentsStack = stack;
+        }
+        if (windowingMode == WINDOWING_MODE_PINNED) {
+            if (mPinnedStack != null && mPinnedStack != stack) {
+                throw new IllegalArgumentException("addStackReferenceIfNeeded: pinned stack="
+                        + mPinnedStack + " already exist on display=" + this
+                        + " stack=" + stack);
+            }
+            mPinnedStack = stack;
+        } else if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
+            if (mSplitScreenPrimaryStack != null && mSplitScreenPrimaryStack != stack) {
+                throw new IllegalArgumentException("addStackReferenceIfNeeded:"
+                        + " split-screen-primary" + " stack=" + mSplitScreenPrimaryStack
+                        + " already exist on display=" + this + " stack=" + stack);
+            }
+            mSplitScreenPrimaryStack = stack;
+        }
+    }
+
+    private void removeStackReferenceIfNeeded(ActivityStack stack) {
+        if (stack == mHomeStack) {
+            mHomeStack = null;
+        } else if (stack == mRecentsStack) {
+            mRecentsStack = null;
+        } else if (stack == mPinnedStack) {
+            mPinnedStack = null;
+        } else if (stack == mSplitScreenPrimaryStack) {
+            mSplitScreenPrimaryStack = null;
+        }
+    }
+
     /** Returns the top visible stack activity type that isn't in the exclude windowing mode. */
     int getTopVisibleStackActivityType(int excludeWindowingMode) {
         for (int i = mStacks.size() - 1; i >= 0; --i) {
@@ -326,20 +386,57 @@
         return ACTIVITY_TYPE_UNDEFINED;
     }
 
-    ActivityStack getSplitScreenStack() {
-        return getStack(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_UNDEFINED);
+    /**
+     * Get the topmost stack on the display. It may be different from focused stack, because
+     * focus may be on another display.
+     */
+    ActivityStack getTopStack() {
+        return mStacks.isEmpty() ? null : mStacks.get(mStacks.size() - 1);
     }
 
-    boolean hasSplitScreenStack() {
-        return getSplitScreenStack() != null;
+    boolean isTopStack(ActivityStack stack) {
+        return stack == getTopStack();
+    }
+
+    int getIndexOf(ActivityStack stack) {
+        return mStacks.indexOf(stack);
+    }
+
+    void onLockTaskPackagesUpdated() {
+        for (int i = mStacks.size() - 1; i >= 0; --i) {
+            mStacks.get(i).onLockTaskPackagesUpdated();
+        }
+    }
+
+    ActivityStack getSplitScreenPrimaryStack() {
+        return mSplitScreenPrimaryStack;
+    }
+
+    boolean hasSplitScreenPrimaryStack() {
+        return mSplitScreenPrimaryStack != null;
     }
 
     PinnedActivityStack getPinnedStack() {
-        return getStack(WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED);
+        return (PinnedActivityStack) mPinnedStack;
     }
 
     boolean hasPinnedStack() {
-        return getPinnedStack() != null;
+        return mPinnedStack != null;
+    }
+
+    int updateWindowingModeForSplitScreenIfNeeded(int windowingMode, int activityType) {
+        final boolean inSplitScreenMode = hasSplitScreenPrimaryStack();
+        if (!inSplitScreenMode
+                && windowingMode == WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY) {
+            // Switch to fullscreen windowing mode if we are not in split-screen mode and we are
+            // trying to launch in split-screen secondary.
+            return WINDOWING_MODE_FULLSCREEN;
+        } else if (inSplitScreenMode && windowingMode == WINDOWING_MODE_FULLSCREEN
+                && WindowConfiguration.supportSplitScreenWindowingMode(
+                windowingMode, activityType)) {
+            return WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+        }
+        return windowingMode;
     }
 
     @Override
@@ -353,7 +450,7 @@
     }
 
     @Override
-    protected ConfigurationContainer getChildAt(int index) {
+    protected ActivityStack getChildAt(int index) {
         return mStacks.get(index);
     }
 
@@ -401,6 +498,10 @@
         mSleeping = asleep;
     }
 
+    public void dump(PrintWriter pw, String prefix) {
+        pw.println(prefix + "displayId=" + mDisplayId + " mStacks=" + mStacks);
+    }
+
     public void writeToProto(ProtoOutputStream proto, long fieldId) {
         final long token = proto.start(fieldId);
         super.writeToProto(proto, CONFIGURATION_CONTAINER);
diff --git a/com/android/server/am/ActivityManagerDebugConfig.java b/com/android/server/am/ActivityManagerDebugConfig.java
index 3a9bf12..ceb2ad6 100644
--- a/com/android/server/am/ActivityManagerDebugConfig.java
+++ b/com/android/server/am/ActivityManagerDebugConfig.java
@@ -59,7 +59,6 @@
     static final boolean DEBUG_FOCUS = false;
     static final boolean DEBUG_IDLE = DEBUG_ALL_ACTIVITIES || false;
     static final boolean DEBUG_IMMERSIVE = DEBUG_ALL || false;
-    static final boolean DEBUG_LOCKSCREEN = DEBUG_ALL || false;
     static final boolean DEBUG_LOCKTASK = DEBUG_ALL || false;
     static final boolean DEBUG_LRU = DEBUG_ALL || false;
     static final boolean DEBUG_MU = DEBUG_ALL || false;
@@ -74,10 +73,10 @@
     static final boolean DEBUG_PROVIDER = DEBUG_ALL || false;
     static final boolean DEBUG_PSS = DEBUG_ALL || false;
     static final boolean DEBUG_RECENTS = DEBUG_ALL || false;
+    static final boolean DEBUG_RECENTS_TRIM_TASKS = DEBUG_RECENTS || false;
     static final boolean DEBUG_RELEASE = DEBUG_ALL_ACTIVITIES || false;
     static final boolean DEBUG_RESULTS = DEBUG_ALL || false;
     static final boolean DEBUG_SAVED_STATE = DEBUG_ALL_ACTIVITIES || false;
-    static final boolean DEBUG_SCREENSHOTS = DEBUG_ALL_ACTIVITIES || false;
     static final boolean DEBUG_SERVICE = DEBUG_ALL || false;
     static final boolean DEBUG_FOREGROUND_SERVICE = DEBUG_ALL || false;
     static final boolean DEBUG_SERVICE_EXECUTING = DEBUG_ALL || false;
@@ -85,7 +84,6 @@
     static final boolean DEBUG_STATES = DEBUG_ALL_ACTIVITIES || false;
     static final boolean DEBUG_SWITCH = DEBUG_ALL || false;
     static final boolean DEBUG_TASKS = DEBUG_ALL || false;
-    static final boolean DEBUG_THUMBNAILS = DEBUG_ALL || false;
     static final boolean DEBUG_TRANSITION = DEBUG_ALL || false;
     static final boolean DEBUG_UID_OBSERVERS = DEBUG_ALL || false;
     static final boolean DEBUG_URI_PERMISSION = DEBUG_ALL || false;
@@ -105,7 +103,6 @@
     static final String POSTFIX_FOCUS = (APPEND_CATEGORY_NAME) ? "_Focus" : "";
     static final String POSTFIX_IDLE = (APPEND_CATEGORY_NAME) ? "_Idle" : "";
     static final String POSTFIX_IMMERSIVE = (APPEND_CATEGORY_NAME) ? "_Immersive" : "";
-    static final String POSTFIX_LOCKSCREEN = (APPEND_CATEGORY_NAME) ? "_LockScreen" : "";
     static final String POSTFIX_LOCKTASK = (APPEND_CATEGORY_NAME) ? "_LockTask" : "";
     static final String POSTFIX_LRU = (APPEND_CATEGORY_NAME) ? "_LRU" : "";
     static final String POSTFIX_MU = "_MU";
@@ -122,7 +119,6 @@
     static final String POSTFIX_RELEASE = (APPEND_CATEGORY_NAME) ? "_Release" : "";
     static final String POSTFIX_RESULTS = (APPEND_CATEGORY_NAME) ? "_Results" : "";
     static final String POSTFIX_SAVED_STATE = (APPEND_CATEGORY_NAME) ? "_SavedState" : "";
-    static final String POSTFIX_SCREENSHOTS = (APPEND_CATEGORY_NAME) ? "_Screenshots" : "";
     static final String POSTFIX_SERVICE = (APPEND_CATEGORY_NAME) ? "_Service" : "";
     static final String POSTFIX_SERVICE_EXECUTING =
             (APPEND_CATEGORY_NAME) ? "_ServiceExecuting" : "";
@@ -130,13 +126,11 @@
     static final String POSTFIX_STATES = (APPEND_CATEGORY_NAME) ? "_States" : "";
     static final String POSTFIX_SWITCH = (APPEND_CATEGORY_NAME) ? "_Switch" : "";
     static final String POSTFIX_TASKS = (APPEND_CATEGORY_NAME) ? "_Tasks" : "";
-    static final String POSTFIX_THUMBNAILS = (APPEND_CATEGORY_NAME) ? "_Thumbnails" : "";
     static final String POSTFIX_TRANSITION = (APPEND_CATEGORY_NAME) ? "_Transition" : "";
     static final String POSTFIX_UID_OBSERVERS = (APPEND_CATEGORY_NAME)
             ? "_UidObservers" : "";
     static final String POSTFIX_URI_PERMISSION = (APPEND_CATEGORY_NAME) ? "_UriPermission" : "";
     static final String POSTFIX_USER_LEAVING = (APPEND_CATEGORY_NAME) ? "_UserLeaving" : "";
     static final String POSTFIX_VISIBILITY = (APPEND_CATEGORY_NAME) ? "_Visibility" : "";
-    static final String POSTFIX_VISIBLE_BEHIND = (APPEND_CATEGORY_NAME) ? "_VisibleBehind" : "";
 
 }
diff --git a/com/android/server/am/ActivityManagerService.java b/com/android/server/am/ActivityManagerService.java
index e6fe620..e1e53b3 100644
--- a/com/android/server/am/ActivityManagerService.java
+++ b/com/android/server/am/ActivityManagerService.java
@@ -27,18 +27,10 @@
 import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
 import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
 import static android.app.ActivityManager.RESIZE_MODE_PRESERVE_WINDOW;
-import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
-import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.FIRST_DYNAMIC_STACK_ID;
-import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
 import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
-import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
-import static android.app.ActivityManager.StackId.getWindowingModeForStackId;
-import static android.app.ActivityManager.StackId.isStaticStack;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
 import static android.content.pm.PackageManager.FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS;
@@ -57,6 +49,9 @@
 import static android.net.NetworkPolicyManager.isProcStateAllowedWhileIdleOrPowerSaveMode;
 import static android.net.NetworkPolicyManager.isProcStateAllowedWhileOnRestrictBackground;
 import static android.os.Build.VERSION_CODES.N;
+import static android.os.IServiceManager.DUMP_PRIORITY_CRITICAL;
+import static android.os.IServiceManager.DUMP_PRIORITY_HIGH;
+import static android.os.IServiceManager.DUMP_PRIORITY_NORMAL;
 import static android.os.Process.BLUETOOTH_UID;
 import static android.os.Process.FIRST_APPLICATION_UID;
 import static android.os.Process.FIRST_ISOLATED_UID;
@@ -137,7 +132,6 @@
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PROCESS_OBSERVERS;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PROVIDER;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PSS;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_RECENTS;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_SERVICE;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_STACK;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_SWITCH;
@@ -182,7 +176,6 @@
 import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_DONT_LOCK;
 import static com.android.server.am.TaskRecord.REPARENT_KEEP_STACK_AT_FRONT;
 import static com.android.server.am.TaskRecord.REPARENT_LEAVE_STACK_IN_PLACE;
-import static com.android.server.am.proto.ActivityManagerServiceProto.ACTIVITIES;
 import static com.android.server.wm.AppTransition.TRANSIT_ACTIVITY_OPEN;
 import static com.android.server.wm.AppTransition.TRANSIT_NONE;
 import static com.android.server.wm.AppTransition.TRANSIT_TASK_IN_PLACE;
@@ -199,7 +192,6 @@
 import android.app.Activity;
 import android.app.ActivityManager;
 import android.app.ActivityManager.RunningTaskInfo;
-import android.app.ActivityManager.StackId;
 import android.app.ActivityManager.StackInfo;
 import android.app.ActivityManager.TaskSnapshot;
 import android.app.ActivityManagerInternal;
@@ -216,7 +208,6 @@
 import android.app.Dialog;
 import android.app.IActivityController;
 import android.app.IActivityManager;
-import android.app.IAppTask;
 import android.app.IApplicationThread;
 import android.app.IInstrumentationWatcher;
 import android.app.INotificationManager;
@@ -404,6 +395,9 @@
 import com.android.server.ThreadPriorityBooster;
 import com.android.server.Watchdog;
 import com.android.server.am.ActivityStack.ActivityState;
+import com.android.server.am.proto.ActivityManagerServiceProto;
+import com.android.server.am.proto.BroadcastProto;
+import com.android.server.am.proto.StickyBroadcastProto;
 import com.android.server.firewall.IntentFirewall;
 import com.android.server.job.JobSchedulerInternal;
 import com.android.server.pm.Installer;
@@ -739,9 +733,6 @@
                 doDump(fd, pw, new String[] {"associations"});
             }
             doDump(fd, pw, new String[] {"processes"});
-            doDump(fd, pw, new String[] {"-v", "all"});
-            doDump(fd, pw, new String[] {"service", "all"});
-            doDump(fd, pw, new String[] {"provider", "all"});
         }
 
         @Override
@@ -753,6 +744,8 @@
     public boolean canShowErrorDialogs() {
         return mShowDialogs && !mSleeping && !mShuttingDown
                 && !mKeyguardController.isKeyguardShowing(DEFAULT_DISPLAY)
+                && !mUserController.hasUserRestriction(UserManager.DISALLOW_SYSTEM_ERROR_DIALOGS,
+                        mUserController.getCurrentUserId())
                 && !(UserManager.isDeviceInDemoMode(mContext)
                         && mUserController.getCurrentUser().isDemo());
     }
@@ -1717,7 +1710,6 @@
     static final int PUSH_TEMP_WHITELIST_UI_MSG = 68;
     static final int SERVICE_FOREGROUND_CRASH_MSG = 69;
     static final int DISPATCH_OOM_ADJ_OBSERVER_MSG = 70;
-    static final int TOP_APP_KILLED_BY_LMK_MSG = 73;
     static final int NOTIFY_VR_KEYGUARD_MSG = 74;
 
     static final int FIRST_ACTIVITY_STACK_MSG = 100;
@@ -1738,9 +1730,6 @@
      */
     private boolean mUserIsMonkey;
 
-    /** Flag whether the device has a Recents UI */
-    boolean mHasRecents;
-
     /** The dimensions of the thumbnails in the Recents UI. */
     int mThumbnailWidth;
     int mThumbnailHeight;
@@ -1946,17 +1935,6 @@
                 dispatchProcessDied(pid, uid);
                 break;
             }
-            case TOP_APP_KILLED_BY_LMK_MSG: {
-                final String appName = (String) msg.obj;
-                final AlertDialog d = new BaseErrorDialog(mUiContext);
-                d.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ERROR);
-                d.setTitle(mUiContext.getText(R.string.top_app_killed_title));
-                d.setMessage(mUiContext.getString(R.string.top_app_killed_message, appName));
-                d.setButton(DialogInterface.BUTTON_POSITIVE, mUiContext.getText(R.string.close),
-                        obtainMessage(DISMISS_DIALOG_UI_MSG, d));
-                d.show();
-                break;
-            }
             case DISPATCH_UIDS_CHANGED_UI_MSG: {
                 dispatchUidsChanged();
             } break;
@@ -2111,7 +2089,8 @@
                     String text = mContext.getString(R.string.heavy_weight_notification,
                             context.getApplicationInfo().loadLabel(context.getPackageManager()));
                     Notification notification =
-                            new Notification.Builder(context, SystemNotificationChannels.DEVELOPER)
+                            new Notification.Builder(context,
+                                    SystemNotificationChannels.HEAVY_WEIGHT_APP)
                             .setSmallIcon(com.android.internal.R.drawable.stat_sys_adb)
                             .setWhen(0)
                             .setOngoing(true)
@@ -2145,7 +2124,7 @@
                 }
                 try {
                     inm.cancelNotificationWithTag("android", null,
-                            SystemMessage.NOTE_HEAVY_WEIGHT_NOTIFICATION,  msg.arg1);
+                            SystemMessage.NOTE_HEAVY_WEIGHT_NOTIFICATION, msg.arg1);
                 } catch (RuntimeException e) {
                     Slog.w(ActivityManagerService.TAG,
                             "Error canceling notification for service", e);
@@ -2503,13 +2482,16 @@
 
     public void setSystemProcess() {
         try {
-            ServiceManager.addService(Context.ACTIVITY_SERVICE, this, true);
+            ServiceManager.addService(Context.ACTIVITY_SERVICE, this, /* allowIsolated= */ true,
+                    DUMP_PRIORITY_CRITICAL | DUMP_PRIORITY_NORMAL);
             ServiceManager.addService(ProcessStats.SERVICE_NAME, mProcessStats);
-            ServiceManager.addService("meminfo", new MemBinder(this));
+            ServiceManager.addService("meminfo", new MemBinder(this), /* allowIsolated= */ false,
+                    DUMP_PRIORITY_HIGH | DUMP_PRIORITY_NORMAL);
             ServiceManager.addService("gfxinfo", new GraphicsBinder(this));
             ServiceManager.addService("dbinfo", new DbBinder(this));
             if (MONITOR_CPU_USAGE) {
-                ServiceManager.addService("cpuinfo", new CpuBinder(this));
+                ServiceManager.addService("cpuinfo", new CpuBinder(this),
+                        /* allowIsolated= */ false, DUMP_PRIORITY_CRITICAL);
             }
             ServiceManager.addService("permission", new PermissionController(this));
             ServiceManager.addService("processinfo", new ProcessInfoService(this));
@@ -2540,7 +2522,6 @@
         synchronized (this) {
             mWindowManager = wm;
             mStackSupervisor.setWindowManager(wm);
-            mActivityStarter.setWindowManager(wm);
             mLockTaskController.setWindowManager(wm);
         }
     }
@@ -2782,8 +2763,9 @@
         mIntentFirewall = new IntentFirewall(new IntentFirewallInterface(), mHandler);
         mTaskChangeNotificationController =
                 new TaskChangeNotificationController(this, mStackSupervisor, mHandler);
-        mActivityStarter = new ActivityStarter(this, mStackSupervisor);
+        mActivityStarter = new ActivityStarter(this);
         mRecentTasks = new RecentTasks(this, mStackSupervisor);
+        mStackSupervisor.setRecentTasks(mRecentTasks);
         mLockTaskController = new LockTaskController(mContext, mStackSupervisor, mHandler);
 
         mProcessCpuThread = new Thread("CpuTracker") {
@@ -3241,11 +3223,12 @@
         // stack implementation changes in the future, keep in mind that the use of the fullscreen
         // stack is a means to move the activity to the main display and a moveActivityToDisplay()
         // option would be a better choice here.
-        if (r.requestedVrComponent != null && r.getStackId() >= FIRST_DYNAMIC_STACK_ID) {
+        if (r.requestedVrComponent != null && r.getDisplayId() != DEFAULT_DISPLAY) {
             Slog.i(TAG, "Moving " + r.shortComponentName + " from stack " + r.getStackId()
                     + " to main stack for VR");
-            setTaskWindowingMode(r.getTask().taskId,
-                    WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY, true /* toTop */);
+            final ActivityStack stack = mStackSupervisor.getDefaultDisplay().getOrCreateStack(
+                    WINDOWING_MODE_FULLSCREEN, r.getActivityType(), true /* toTop */);
+            moveTaskToStack(r.getTask().taskId, stack.mStackId, true /* toTop */);
         }
         mHandler.sendMessage(
                 mHandler.obtainMessage(VR_MODE_CHANGE_MSG, 0, 0, r));
@@ -4894,7 +4877,7 @@
         final long origId = Binder.clearCallingIdentity();
         try {
             synchronized (this) {
-                return mStackSupervisor.startActivityFromRecentsInner(taskId, bOptions);
+                return mStackSupervisor.startActivityFromRecents(taskId, bOptions);
             }
         } finally {
             Binder.restoreCallingIdentity(origId);
@@ -5095,11 +5078,12 @@
         }
 
         synchronized(this) {
-            if (mHeavyWeightProcess == null) {
+            final ProcessRecord proc = mHeavyWeightProcess;
+            if (proc == null) {
                 return;
             }
 
-            ArrayList<ActivityRecord> activities = new ArrayList<>(mHeavyWeightProcess.activities);
+            ArrayList<ActivityRecord> activities = new ArrayList<>(proc.activities);
             for (int i = 0; i < activities.size(); i++) {
                 ActivityRecord r = activities.get(i);
                 if (!r.finishing && r.isInStackLocked()) {
@@ -5109,7 +5093,7 @@
             }
 
             mHandler.sendMessage(mHandler.obtainMessage(CANCEL_HEAVY_NOTIFICATION_MSG,
-                    mHeavyWeightProcess.userId, 0));
+                    proc.userId, 0));
             mHeavyWeightProcess = null;
         }
     }
@@ -5428,7 +5412,6 @@
             boolean doLowMem = app.instr == null;
             boolean doOomAdj = doLowMem;
             if (!app.killedByAm) {
-                maybeNotifyTopAppKilled(app);
                 Slog.i(TAG, "Process " + app.processName + " (pid " + pid + ") has died: "
                         + ProcessList.makeOomAdjString(app.setAdj)
                         + ProcessList.makeProcStateString(app.setProcState));
@@ -5462,23 +5445,6 @@
         }
     }
 
-    /** Show system error dialog when a top app is killed by LMK */
-    void maybeNotifyTopAppKilled(ProcessRecord app) {
-        if (!shouldNotifyTopAppKilled(app)) {
-            return;
-        }
-
-        Message msg = mHandler.obtainMessage(TOP_APP_KILLED_BY_LMK_MSG);
-        msg.obj = mContext.getPackageManager().getApplicationLabel(app.info);
-        mUiHandler.sendMessage(msg);
-    }
-
-    /** Only show notification when the top app is killed on low ram devices */
-    private boolean shouldNotifyTopAppKilled(ProcessRecord app) {
-        return app.curSchedGroup == ProcessList.SCHED_GROUP_TOP_APP &&
-            ActivityManager.isLowRamDeviceStatic();
-    }
-
     /**
      * If a stack trace dump file is configured, dump process stack traces.
      * @param clearTraces causes the dump file to be erased prior to the new
@@ -5963,16 +5929,7 @@
 
                 if (appInfo != null) {
                     forceStopPackageLocked(packageName, appInfo.uid, "clear data");
-                    // Remove all tasks match the cleared application package and user
-                    for (int i = mRecentTasks.size() - 1; i >= 0; i--) {
-                        final TaskRecord tr = mRecentTasks.get(i);
-                        final String taskPackageName =
-                                tr.getBaseIntent().getComponent().getPackageName();
-                        if (tr.userId != resolvedUserId) continue;
-                        if (!taskPackageName.equals(packageName)) continue;
-                        mStackSupervisor.removeTaskByIdLocked(tr.taskId, false,
-                                REMOVE_FROM_RECENTS);
-                    }
+                    mRecentTasks.removeTasksByPackageName(packageName, resolvedUserId);
                 }
             }
 
@@ -6553,7 +6510,7 @@
         }
 
         // Clean-up disabled tasks
-        cleanupDisabledPackageTasksLocked(packageName, disabledClasses, userId);
+        mRecentTasks.cleanupDisabledPackageTasksLocked(packageName, disabledClasses, userId);
 
         // Clean-up disabled services.
         mServices.bringDownDisabledPackageServicesLocked(
@@ -8084,8 +8041,8 @@
     }
 
     private boolean isInPictureInPictureMode(ActivityRecord r) {
-        if (r == null || r.getStack() == null || !r.getStack().isPinnedStack() ||
-                r.getStack().isInStackLocked(r) == null) {
+        if (r == null || r.getStack() == null || !r.inPinnedWindowingMode()
+                || r.getStack().isInStackLocked(r) == null) {
             return false;
         }
 
@@ -8179,7 +8136,7 @@
 
                 // Only update the saved args from the args that are set
                 r.pictureInPictureArgs.copyOnlySet(params);
-                if (r.getStack().getStackId() == PINNED_STACK_ID) {
+                if (r.inPinnedWindowingMode()) {
                     // If the activity is already in picture-in-picture, update the pinned stack now
                     // if it is not already expanding to fullscreen. Otherwise, the arguments will
                     // be used the next time the activity enters PiP
@@ -9800,35 +9757,12 @@
     public List<IBinder> getAppTasks(String callingPackage) {
         int callingUid = Binder.getCallingUid();
         long ident = Binder.clearCallingIdentity();
-
-        synchronized(this) {
-            ArrayList<IBinder> list = new ArrayList<IBinder>();
-            try {
-                if (DEBUG_ALL) Slog.v(TAG, "getAppTasks");
-
-                final int N = mRecentTasks.size();
-                for (int i = 0; i < N; i++) {
-                    TaskRecord tr = mRecentTasks.get(i);
-                    // Skip tasks that do not match the caller.  We don't need to verify
-                    // callingPackage, because we are also limiting to callingUid and know
-                    // that will limit to the correct security sandbox.
-                    if (tr.effectiveUid != callingUid) {
-                        continue;
-                    }
-                    Intent intent = tr.getBaseIntent();
-                    if (intent == null ||
-                            !callingPackage.equals(intent.getComponent().getPackageName())) {
-                        continue;
-                    }
-                    ActivityManager.RecentTaskInfo taskInfo =
-                            createRecentTaskInfoFromTaskRecord(tr);
-                    AppTaskImpl taskImpl = new AppTaskImpl(taskInfo.persistentId, callingUid);
-                    list.add(taskImpl.asBinder());
-                }
-            } finally {
-                Binder.restoreCallingIdentity(ident);
+        try {
+            synchronized(this) {
+                return mRecentTasks.getAppTasksList(callingUid, callingPackage);
             }
-            return list;
+        } finally {
+            Binder.restoreCallingIdentity(ident);
         }
     }
 
@@ -9851,58 +9785,6 @@
         return list;
     }
 
-    /**
-     * Creates a new RecentTaskInfo from a TaskRecord.
-     */
-    private ActivityManager.RecentTaskInfo createRecentTaskInfoFromTaskRecord(TaskRecord tr) {
-        // Update the task description to reflect any changes in the task stack
-        tr.updateTaskDescription();
-
-        // Compose the recent task info
-        ActivityManager.RecentTaskInfo rti = new ActivityManager.RecentTaskInfo();
-        rti.id = tr.getTopActivity() == null ? INVALID_TASK_ID : tr.taskId;
-        rti.persistentId = tr.taskId;
-        rti.baseIntent = new Intent(tr.getBaseIntent());
-        rti.origActivity = tr.origActivity;
-        rti.realActivity = tr.realActivity;
-        rti.description = tr.lastDescription;
-        rti.stackId = tr.getStackId();
-        rti.userId = tr.userId;
-        rti.taskDescription = new ActivityManager.TaskDescription(tr.lastTaskDescription);
-        rti.firstActiveTime = tr.firstActiveTime;
-        rti.lastActiveTime = tr.lastActiveTime;
-        rti.affiliatedTaskId = tr.mAffiliatedTaskId;
-        rti.affiliatedTaskColor = tr.mAffiliatedTaskColor;
-        rti.numActivities = 0;
-        if (tr.mBounds != null) {
-            rti.bounds = new Rect(tr.mBounds);
-        }
-        rti.supportsSplitScreenMultiWindow = tr.supportsSplitScreenWindowingMode();
-        rti.resizeMode = tr.mResizeMode;
-        rti.configuration.setTo(tr.getConfiguration());
-
-        ActivityRecord base = null;
-        ActivityRecord top = null;
-        ActivityRecord tmp;
-
-        for (int i = tr.mActivities.size() - 1; i >= 0; --i) {
-            tmp = tr.mActivities.get(i);
-            if (tmp.finishing) {
-                continue;
-            }
-            base = tmp;
-            if (top == null || (top.state == ActivityState.INITIALIZING)) {
-                top = base;
-            }
-            rti.numActivities++;
-        }
-
-        rti.baseActivity = (base != null) ? base.intent.getComponent() : null;
-        rti.topActivity = (top != null) ? top.intent.getComponent() : null;
-
-        return rti;
-    }
-
     private boolean isGetTasksAllowed(String caller, int callingPid, int callingUid) {
         boolean allowed = checkPermission(android.Manifest.permission.REAL_GET_TASKS,
                 callingPid, callingUid) == PackageManager.PERMISSION_GRANTED;
@@ -9936,118 +9818,15 @@
         final int callingUid = Binder.getCallingUid();
         userId = mUserController.handleIncomingUser(Binder.getCallingPid(), callingUid, userId,
                 false, ALLOW_FULL_ONLY, "getRecentTasks", null);
+        final boolean allowed = isGetTasksAllowed("getRecentTasks", Binder.getCallingPid(),
+                callingUid);
+        final boolean detailed = checkCallingPermission(
+                android.Manifest.permission.GET_DETAILED_TASKS)
+                        == PackageManager.PERMISSION_GRANTED;
 
-        final boolean includeProfiles = (flags & ActivityManager.RECENT_INCLUDE_PROFILES) != 0;
-        final boolean withExcluded = (flags&ActivityManager.RECENT_WITH_EXCLUDED) != 0;
         synchronized (this) {
-            final boolean allowed = isGetTasksAllowed("getRecentTasks", Binder.getCallingPid(),
+            return mRecentTasks.getRecentTasks(maxNum, flags, allowed, detailed, userId,
                     callingUid);
-            final boolean detailed = checkCallingPermission(
-                    android.Manifest.permission.GET_DETAILED_TASKS)
-                    == PackageManager.PERMISSION_GRANTED;
-
-            if (!isUserRunning(userId, ActivityManager.FLAG_AND_UNLOCKED)) {
-                Slog.i(TAG, "user " + userId + " is still locked. Cannot load recents");
-                return ParceledListSlice.emptyList();
-            }
-            mRecentTasks.loadUserRecentsLocked(userId);
-
-            final int recentsCount = mRecentTasks.size();
-            ArrayList<ActivityManager.RecentTaskInfo> res =
-                    new ArrayList<>(maxNum < recentsCount ? maxNum : recentsCount);
-
-            final Set<Integer> includedUsers;
-            if (includeProfiles) {
-                includedUsers = mUserController.getProfileIds(userId);
-            } else {
-                includedUsers = new HashSet<>();
-            }
-            includedUsers.add(Integer.valueOf(userId));
-
-            for (int i = 0; i < recentsCount && maxNum > 0; i++) {
-                TaskRecord tr = mRecentTasks.get(i);
-                // Only add calling user or related users recent tasks
-                if (!includedUsers.contains(Integer.valueOf(tr.userId))) {
-                    if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "Skipping, not user: " + tr);
-                    continue;
-                }
-
-                if (tr.realActivitySuspended) {
-                    if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "Skipping, activity suspended: " + tr);
-                    continue;
-                }
-
-                // Return the entry if desired by the caller.  We always return
-                // the first entry, because callers always expect this to be the
-                // foreground app.  We may filter others if the caller has
-                // not supplied RECENT_WITH_EXCLUDED and there is some reason
-                // we should exclude the entry.
-
-                if (i == 0
-                        || withExcluded
-                        || (tr.intent == null)
-                        || ((tr.intent.getFlags() & Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
-                                == 0)) {
-                    if (!allowed) {
-                        // If the caller doesn't have the GET_TASKS permission, then only
-                        // allow them to see a small subset of tasks -- their own and home.
-                        if (!tr.isActivityTypeHome() && tr.effectiveUid != callingUid) {
-                            if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "Skipping, not allowed: " + tr);
-                            continue;
-                        }
-                    }
-                    final ActivityStack stack = tr.getStack();
-                    if ((flags & ActivityManager.RECENT_IGNORE_HOME_AND_RECENTS_STACK_TASKS) != 0) {
-                        if (stack != null && stack.isHomeOrRecentsStack()) {
-                            if (DEBUG_RECENTS) Slog.d(TAG_RECENTS,
-                                    "Skipping, home or recents stack task: " + tr);
-                            continue;
-                        }
-                    }
-                    if ((flags & ActivityManager.RECENT_INGORE_DOCKED_STACK_TOP_TASK) != 0) {
-                        if (stack != null && stack.isDockedStack() && stack.topTask() == tr) {
-                            if (DEBUG_RECENTS) Slog.d(TAG_RECENTS,
-                                    "Skipping, top task in docked stack: " + tr);
-                            continue;
-                        }
-                    }
-                    if ((flags & ActivityManager.RECENT_INGORE_PINNED_STACK_TASKS) != 0) {
-                        if (stack != null && stack.isPinnedStack()) {
-                            if (DEBUG_RECENTS) Slog.d(TAG_RECENTS,
-                                    "Skipping, pinned stack task: " + tr);
-                            continue;
-                        }
-                    }
-                    if (tr.autoRemoveRecents && tr.getTopActivity() == null) {
-                        // Don't include auto remove tasks that are finished or finishing.
-                        if (DEBUG_RECENTS) Slog.d(TAG_RECENTS,
-                                "Skipping, auto-remove without activity: " + tr);
-                        continue;
-                    }
-                    if ((flags&ActivityManager.RECENT_IGNORE_UNAVAILABLE) != 0
-                            && !tr.isAvailable) {
-                        if (DEBUG_RECENTS) Slog.d(TAG_RECENTS,
-                                "Skipping, unavail real act: " + tr);
-                        continue;
-                    }
-
-                    if (!tr.mUserSetupComplete) {
-                        // Don't include task launched while user is not done setting-up.
-                        if (DEBUG_RECENTS) Slog.d(TAG_RECENTS,
-                                "Skipping, user setup not complete: " + tr);
-                        continue;
-                    }
-
-                    ActivityManager.RecentTaskInfo rti = createRecentTaskInfoFromTaskRecord(tr);
-                    if (!detailed) {
-                        rti.baseIntent.replaceExtras((Bundle)null);
-                    }
-
-                    res.add(rti);
-                    maxNum--;
-                }
-            }
-            return new ParceledListSlice<>(res);
         }
     }
 
@@ -10119,23 +9898,10 @@
                 TaskRecord task = new TaskRecord(this,
                         mStackSupervisor.getNextTaskIdForUserLocked(r.userId),
                         ainfo, intent, description);
-
-                int trimIdx = mRecentTasks.trimForTaskLocked(task, false);
-                if (trimIdx >= 0) {
-                    // If this would have caused a trim, then we'll abort because that
-                    // means it would be added at the end of the list but then just removed.
+                if (!mRecentTasks.addToBottom(task)) {
                     return INVALID_TASK_ID;
                 }
-
-                final int N = mRecentTasks.size();
-                if (N >= (ActivityManager.getMaxRecentTasksStatic()-1)) {
-                    final TaskRecord tr = mRecentTasks.remove(N - 1);
-                    tr.removedFromRecents();
-                }
-
-                task.inRecents = true;
-                mRecentTasks.add(task);
-                r.getStack().addTask(task, false, "addAppTask");
+                r.getStack().addTask(task, !ON_TOP, "addAppTask");
 
                 // TODO: Send the thumbnail to WM to store it.
 
@@ -10351,38 +10117,6 @@
         mWindowManager.executeAppTransition();
     }
 
-    private void removeTasksByPackageNameLocked(String packageName, int userId) {
-        // Remove all tasks with activities in the specified package from the list of recent tasks
-        for (int i = mRecentTasks.size() - 1; i >= 0; i--) {
-            TaskRecord tr = mRecentTasks.get(i);
-            if (tr.userId != userId) continue;
-
-            ComponentName cn = tr.intent.getComponent();
-            if (cn != null && cn.getPackageName().equals(packageName)) {
-                // If the package name matches, remove the task.
-                mStackSupervisor.removeTaskByIdLocked(tr.taskId, true, REMOVE_FROM_RECENTS);
-            }
-        }
-    }
-
-    private void cleanupDisabledPackageTasksLocked(String packageName, Set<String> filterByClasses,
-            int userId) {
-
-        for (int i = mRecentTasks.size() - 1; i >= 0; i--) {
-            TaskRecord tr = mRecentTasks.get(i);
-            if (userId != UserHandle.USER_ALL && tr.userId != userId) {
-                continue;
-            }
-
-            ComponentName cn = tr.intent.getComponent();
-            final boolean sameComponent = cn != null && cn.getPackageName().equals(packageName)
-                    && (filterByClasses == null || filterByClasses.contains(cn.getClassName()));
-            if (sameComponent) {
-                mStackSupervisor.removeTaskByIdLocked(tr.taskId, false, REMOVE_FROM_RECENTS);
-            }
-        }
-    }
-
     @Override
     public void removeStack(int stackId) {
         enforceCallingPermission(Manifest.permission.MANAGE_ACTIVITY_STACKS, "removeStack()");
@@ -10390,11 +10124,14 @@
             final long ident = Binder.clearCallingIdentity();
             try {
                 final ActivityStack stack = mStackSupervisor.getStack(stackId);
-                if (stack != null && !stack.isActivityTypeStandardOrUndefined()) {
+                if (stack == null) {
+                    return;
+                }
+                if (!stack.isActivityTypeStandardOrUndefined()) {
                     throw new IllegalArgumentException(
                             "Removing non-standard stack is not allowed.");
                 }
-                mStackSupervisor.removeStackLocked(stackId);
+                mStackSupervisor.removeStack(stack);
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
@@ -10609,7 +10346,7 @@
                 }
 
                 final ActivityStack stack = r.getStack();
-                if (stack == null || stack.mStackId != FREEFORM_WORKSPACE_STACK_ID) {
+                if (stack == null || !stack.inFreeformWindowingMode()) {
                     throw new IllegalStateException(
                             "exitFreeformMode: You can only go fullscreen from freeform.");
                 }
@@ -10677,27 +10414,20 @@
 
                 if (DEBUG_STACK) Slog.d(TAG_STACK, "moveTaskToStack: moving task=" + taskId
                         + " to stackId=" + stackId + " toTop=" + toTop);
-                if (stackId == DOCKED_STACK_ID) {
-                    mWindowManager.setDockedStackCreateState(DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT,
-                            null /* initialBounds */);
-                }
 
-                ActivityStack stack = mStackSupervisor.getStack(stackId);
+                final ActivityStack stack = mStackSupervisor.getStack(stackId);
                 if (stack == null) {
-                    if (!isStaticStack(stackId)) {
-                        throw new IllegalStateException(
-                                "moveTaskToStack: No stack for stackId=" + stackId);
-                    }
-                    final ActivityDisplay display = task.getStack().getDisplay();
-                    final int windowingMode =
-                            getWindowingModeForStackId(stackId, display.hasSplitScreenStack());
-                    stack = display.getOrCreateStack(windowingMode,
-                            task.getStack().getActivityType(), toTop);
+                    throw new IllegalStateException(
+                            "moveTaskToStack: No stack for stackId=" + stackId);
                 }
                 if (!stack.isActivityTypeStandardOrUndefined()) {
                     throw new IllegalArgumentException("moveTaskToStack: Attempt to move task "
                             + taskId + " to stack " + stackId);
                 }
+                if (stack.inSplitScreenPrimaryWindowingMode()) {
+                    mWindowManager.setDockedStackCreateState(
+                            DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT, null /* initialBounds */);
+                }
                 task.reparent(stack, toTop, REPARENT_KEEP_STACK_AT_FRONT, ANIMATE, !DEFER_RESUME,
                         "moveTaskToStack");
             } finally {
@@ -10767,9 +10497,9 @@
         try {
             synchronized (this) {
                 final ActivityStack stack =
-                        mStackSupervisor.getDefaultDisplay().getSplitScreenStack();
+                        mStackSupervisor.getDefaultDisplay().getSplitScreenPrimaryStack();
                 if (toTop) {
-                    mStackSupervisor.resizeStackLocked(stack.mStackId, null /* destBounds */,
+                    mStackSupervisor.resizeStackLocked(stack, null /* destBounds */,
                             null /* tempTaskBounds */, null /* tempTaskInsetBounds */,
                             true /* preserveWindows */, true /* allowResizeInDockedMode */,
                             !DEFER_RESUME);
@@ -10862,7 +10592,12 @@
                     stack.animateResizePinnedStack(null /* sourceHintBounds */, destBounds,
                             animationDuration, false /* fromFullscreen */);
                 } else {
-                    mStackSupervisor.resizeStackLocked(stackId, destBounds, null /* tempTaskBounds */,
+                    final ActivityStack stack = mStackSupervisor.getStack(stackId);
+                    if (stack == null) {
+                        Slog.w(TAG, "resizeStack: stackId " + stackId + " not found.");
+                        return;
+                    }
+                    mStackSupervisor.resizeStackLocked(stack, destBounds, null /* tempTaskBounds */,
                             null /* tempTaskInsetBounds */, preserveWindows,
                             allowResizeInDockedMode, !DEFER_RESUME);
                 }
@@ -12938,6 +12673,10 @@
                 throw new IllegalArgumentException("Provided bugreport type is not correct, value: "
                         + bugreportType);
         }
+        // Always log caller, even if it does not have permission to dump.
+        String type = extraOptions == null ? "bugreport" : extraOptions;
+        Slog.i(TAG, type + " requested by UID " + Binder.getCallingUid());
+
         enforceCallingPermission(android.Manifest.permission.DUMP, "requestBugReport");
         if (extraOptions != null) {
             SystemProperties.set("dumpstate.options", extraOptions);
@@ -14145,7 +13884,6 @@
 
             // Load resources only after the current configuration has been set.
             final Resources res = mContext.getResources();
-            mHasRecents = res.getBoolean(com.android.internal.R.bool.config_hasRecents);
             mThumbnailWidth = res.getDimensionPixelSize(
                     com.android.internal.R.dimen.thumbnail_width);
             mThumbnailHeight = res.getDimensionPixelSize(
@@ -15104,10 +14842,31 @@
         long origId = Binder.clearCallingIdentity();
 
         if (useProto) {
-            //TODO: Options when dumping proto
             final ProtoOutputStream proto = new ProtoOutputStream(fd);
-            synchronized (this) {
-                writeActivitiesToProtoLocked(proto);
+            String cmd = opti < args.length ? args[opti] : "";
+            opti++;
+
+            if ("activities".equals(cmd) || "a".equals(cmd)) {
+                // output proto is ActivityStackSupervisorProto
+                synchronized (this) {
+                    writeActivitiesToProtoLocked(proto);
+                }
+            } else if ("broadcasts".equals(cmd) || "b".equals(cmd)) {
+                // output proto is BroadcastProto
+                synchronized (this) {
+                    writeBroadcastsToProtoLocked(proto);
+                }
+            } else {
+                // default option, dump everything, output is ActivityManagerServiceProto
+                synchronized (this) {
+                    long activityToken = proto.start(ActivityManagerServiceProto.ACTIVITIES);
+                    writeActivitiesToProtoLocked(proto);
+                    proto.end(activityToken);
+
+                    long broadcastToken = proto.start(ActivityManagerServiceProto.BROADCASTS);
+                    writeBroadcastsToProtoLocked(proto);
+                    proto.end(broadcastToken);
+                }
             }
             proto.flush();
             Binder.restoreCallingIdentity(origId);
@@ -15131,9 +14890,15 @@
                 synchronized (this) {
                     dumpActivityStarterLocked(pw, dumpPackage);
                 }
+            } else if ("containers".equals(cmd)) {
+                synchronized (this) {
+                    dumpActivityContainersLocked(pw);
+                }
             } else if ("recents".equals(cmd) || "r".equals(cmd)) {
                 synchronized (this) {
-                    dumpRecentsLocked(fd, pw, args, opti, true, dumpPackage);
+                    if (mRecentTasks != null) {
+                        mRecentTasks.dump(pw, true /* dumpAll */, dumpPackage);
+                    }
                 }
             } else if ("broadcasts".equals(cmd) || "b".equals(cmd)) {
                 String[] newArgs;
@@ -15354,7 +15119,9 @@
                 if (dumpAll) {
                     pw.println("-------------------------------------------------------------------------------");
                 }
-                dumpRecentsLocked(fd, pw, args, opti, dumpAll, dumpPackage);
+                if (mRecentTasks != null) {
+                    mRecentTasks.dump(pw, dumpAll, dumpPackage);
+                }
                 pw.println();
                 if (dumpAll) {
                     pw.println("-------------------------------------------------------------------------------");
@@ -15369,6 +15136,11 @@
                 if (dumpAll) {
                     pw.println("-------------------------------------------------------------------------------");
                 }
+                dumpActivityContainersLocked(pw);
+                pw.println();
+                if (dumpAll) {
+                    pw.println("-------------------------------------------------------------------------------");
+                }
                 dumpActivitiesLocked(fd, pw, args, opti, dumpAll, dumpClient, dumpPackage);
                 if (mAssociations.size() > 0) {
                     pw.println();
@@ -15424,7 +15196,9 @@
                 if (dumpAll) {
                     pw.println("-------------------------------------------------------------------------------");
                 }
-                dumpRecentsLocked(fd, pw, args, opti, dumpAll, dumpPackage);
+                if (mRecentTasks != null) {
+                    mRecentTasks.dump(pw, dumpAll, dumpPackage);
+                }
                 pw.println();
                 if (dumpAll) {
                     pw.println("-------------------------------------------------------------------------------");
@@ -15439,6 +15213,11 @@
                 if (dumpAll) {
                     pw.println("-------------------------------------------------------------------------------");
                 }
+                dumpActivityContainersLocked(pw);
+                pw.println();
+                if (dumpAll) {
+                    pw.println("-------------------------------------------------------------------------------");
+                }
                 dumpActivitiesLocked(fd, pw, args, opti, dumpAll, dumpClient, dumpPackage);
                 if (mAssociations.size() > 0) {
                     pw.println();
@@ -15458,7 +15237,8 @@
     }
 
     private void writeActivitiesToProtoLocked(ProtoOutputStream proto) {
-        mStackSupervisor.writeToProto(proto, ACTIVITIES);
+        // The output proto of "activity --proto activities" is ActivityStackSupervisorProto
+        mStackSupervisor.writeToProto(proto);
     }
 
     private void dumpLastANRLocked(PrintWriter pw) {
@@ -15470,6 +15250,12 @@
         }
     }
 
+    private void dumpActivityContainersLocked(PrintWriter pw) {
+        pw.println("ACTIVITY MANAGER STARTER (dumpsys activity containers)");
+        mStackSupervisor.dumpChildrenNames(pw, " ");
+        pw.println(" ");
+    }
+
     private void dumpActivityStarterLocked(PrintWriter pw, String dumpPackage) {
         pw.println("ACTIVITY MANAGER STARTER (dumpsys activity starter)");
         mActivityStarter.dump(pw, "", dumpPackage);
@@ -15510,42 +15296,6 @@
         }
     }
 
-    void dumpRecentsLocked(FileDescriptor fd, PrintWriter pw, String[] args,
-            int opti, boolean dumpAll, String dumpPackage) {
-        pw.println("ACTIVITY MANAGER RECENT TASKS (dumpsys activity recents)");
-
-        boolean printedAnything = false;
-
-        if (mRecentTasks != null && mRecentTasks.size() > 0) {
-            boolean printedHeader = false;
-
-            final int N = mRecentTasks.size();
-            for (int i=0; i<N; i++) {
-                TaskRecord tr = mRecentTasks.get(i);
-                if (dumpPackage != null) {
-                    if (tr.realActivity == null ||
-                            !dumpPackage.equals(tr.realActivity.getPackageName())) {
-                        continue;
-                    }
-                }
-                if (!printedHeader) {
-                    pw.println("  Recent tasks:");
-                    printedHeader = true;
-                    printedAnything = true;
-                }
-                pw.print("  * Recent #"); pw.print(i); pw.print(": ");
-                        pw.println(tr);
-                if (dumpAll) {
-                    mRecentTasks.get(i).dump(pw, "    ");
-                }
-            }
-        }
-
-        if (!printedAnything) {
-            pw.println("  (nothing)");
-        }
-    }
-
     void dumpAssociationsLocked(FileDescriptor fd, PrintWriter pw, String[] args,
             int opti, boolean dumpAll, boolean dumpClient, String dumpPackage) {
         pw.println("ACTIVITY MANAGER ASSOCIATIONS (dumpsys activity associations)");
@@ -16372,6 +16122,40 @@
         }
     }
 
+    void writeBroadcastsToProtoLocked(ProtoOutputStream proto) {
+        if (mRegisteredReceivers.size() > 0) {
+            Iterator it = mRegisteredReceivers.values().iterator();
+            while (it.hasNext()) {
+                ReceiverList r = (ReceiverList)it.next();
+                r.writeToProto(proto, BroadcastProto.RECEIVER_LIST);
+            }
+        }
+        mReceiverResolver.writeToProto(proto, BroadcastProto.RECEIVER_RESOLVER);
+        for (BroadcastQueue q : mBroadcastQueues) {
+            q.writeToProto(proto, BroadcastProto.BROADCAST_QUEUE);
+        }
+        for (int user=0; user<mStickyBroadcasts.size(); user++) {
+            long token = proto.start(BroadcastProto.STICKY_BROADCASTS);
+            proto.write(StickyBroadcastProto.USER, mStickyBroadcasts.keyAt(user));
+            for (Map.Entry<String, ArrayList<Intent>> ent
+                    : mStickyBroadcasts.valueAt(user).entrySet()) {
+                long actionToken = proto.start(StickyBroadcastProto.ACTIONS);
+                proto.write(StickyBroadcastProto.StickyAction.NAME, ent.getKey());
+                for (Intent intent : ent.getValue()) {
+                    intent.writeToProto(proto, StickyBroadcastProto.StickyAction.INTENTS,
+                            false, true, true, false);
+                }
+                proto.end(actionToken);
+            }
+            proto.end(token);
+        }
+
+        long handlerToken = proto.start(BroadcastProto.HANDLER);
+        proto.write(BroadcastProto.MainHandler.HANDLER, mHandler.toString());
+        mHandler.getLooper().writeToProto(proto, BroadcastProto.MainHandler.LOOPER);
+        proto.end(handlerToken);
+    }
+
     void dumpBroadcastsLocked(FileDescriptor fd, PrintWriter pw, String[] args,
             int opti, boolean dumpAll, String dumpPackage) {
         boolean needSep = false;
@@ -19360,7 +19144,7 @@
                                         // Remove all permissions granted from/to this package
                                         removeUriPermissionsForPackageLocked(ssp, userId, true);
 
-                                        removeTasksByPackageNameLocked(ssp, userId);
+                                        mRecentTasks.removeTasksByPackageName(ssp, userId);
 
                                         mServices.forceStopPackageLocked(ssp, userId);
 
@@ -20697,9 +20481,10 @@
     /** Helper method that requests bounds from WM and applies them to stack. */
     private void resizeStackWithBoundsFromWindowManager(int stackId, boolean deferResume) {
         final Rect newStackBounds = new Rect();
-        mStackSupervisor.getStack(stackId).getBoundsForNewConfiguration(newStackBounds);
+        final ActivityStack stack = mStackSupervisor.getStack(stackId);
+        stack.getBoundsForNewConfiguration(newStackBounds);
         mStackSupervisor.resizeStackLocked(
-                stackId, !newStackBounds.isEmpty() ? newStackBounds : null /* bounds */,
+                stack, !newStackBounds.isEmpty() ? newStackBounds : null /* bounds */,
                 null /* tempTaskBounds */, null /* tempTaskInsetBounds */,
                 false /* preserveWindows */, false /* allowResizeInDockedMode */, deferResume);
     }
@@ -24367,125 +24152,6 @@
     }
 
     /**
-     * An implementation of IAppTask, that allows an app to manage its own tasks via
-     * {@link android.app.ActivityManager.AppTask}.  We keep track of the callingUid to ensure that
-     * only the process that calls getAppTasks() can call the AppTask methods.
-     */
-    class AppTaskImpl extends IAppTask.Stub {
-        private int mTaskId;
-        private int mCallingUid;
-
-        public AppTaskImpl(int taskId, int callingUid) {
-            mTaskId = taskId;
-            mCallingUid = callingUid;
-        }
-
-        private void checkCaller() {
-            if (mCallingUid != Binder.getCallingUid()) {
-                throw new SecurityException("Caller " + mCallingUid
-                        + " does not match caller of getAppTasks(): " + Binder.getCallingUid());
-            }
-        }
-
-        @Override
-        public void finishAndRemoveTask() {
-            checkCaller();
-
-            synchronized (ActivityManagerService.this) {
-                long origId = Binder.clearCallingIdentity();
-                try {
-                    // We remove the task from recents to preserve backwards
-                    if (!mStackSupervisor.removeTaskByIdLocked(mTaskId, false,
-                            REMOVE_FROM_RECENTS)) {
-                        throw new IllegalArgumentException("Unable to find task ID " + mTaskId);
-                    }
-                } finally {
-                    Binder.restoreCallingIdentity(origId);
-                }
-            }
-        }
-
-        @Override
-        public ActivityManager.RecentTaskInfo getTaskInfo() {
-            checkCaller();
-
-            synchronized (ActivityManagerService.this) {
-                long origId = Binder.clearCallingIdentity();
-                try {
-                    TaskRecord tr = mStackSupervisor.anyTaskForIdLocked(mTaskId);
-                    if (tr == null) {
-                        throw new IllegalArgumentException("Unable to find task ID " + mTaskId);
-                    }
-                    return createRecentTaskInfoFromTaskRecord(tr);
-                } finally {
-                    Binder.restoreCallingIdentity(origId);
-                }
-            }
-        }
-
-        @Override
-        public void moveToFront() {
-            checkCaller();
-            // Will bring task to front if it already has a root activity.
-            final long origId = Binder.clearCallingIdentity();
-            try {
-                synchronized (this) {
-                    mStackSupervisor.startActivityFromRecentsInner(mTaskId, null);
-                }
-            } finally {
-                Binder.restoreCallingIdentity(origId);
-            }
-        }
-
-        @Override
-        public int startActivity(IBinder whoThread, String callingPackage,
-                Intent intent, String resolvedType, Bundle bOptions) {
-            checkCaller();
-
-            int callingUser = UserHandle.getCallingUserId();
-            TaskRecord tr;
-            IApplicationThread appThread;
-            synchronized (ActivityManagerService.this) {
-                tr = mStackSupervisor.anyTaskForIdLocked(mTaskId);
-                if (tr == null) {
-                    throw new IllegalArgumentException("Unable to find task ID " + mTaskId);
-                }
-                appThread = IApplicationThread.Stub.asInterface(whoThread);
-                if (appThread == null) {
-                    throw new IllegalArgumentException("Bad app thread " + appThread);
-                }
-            }
-            return mActivityStarter.startActivityMayWait(appThread, -1, callingPackage, intent,
-                    resolvedType, null, null, null, null, 0, 0, null, null,
-                    null, bOptions, false, callingUser, tr, "AppTaskImpl");
-        }
-
-        @Override
-        public void setExcludeFromRecents(boolean exclude) {
-            checkCaller();
-
-            synchronized (ActivityManagerService.this) {
-                long origId = Binder.clearCallingIdentity();
-                try {
-                    TaskRecord tr = mStackSupervisor.anyTaskForIdLocked(mTaskId);
-                    if (tr == null) {
-                        throw new IllegalArgumentException("Unable to find task ID " + mTaskId);
-                    }
-                    Intent intent = tr.getBaseIntent();
-                    if (exclude) {
-                        intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
-                    } else {
-                        intent.setFlags(intent.getFlags()
-                                & ~Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
-                    }
-                } finally {
-                    Binder.restoreCallingIdentity(origId);
-                }
-            }
-        }
-    }
-
-    /**
      * Kill processes for the user with id userId and that depend on the package named packageName
      */
     @Override
diff --git a/com/android/server/am/ActivityManagerShellCommand.java b/com/android/server/am/ActivityManagerShellCommand.java
index 4c93423..f03d2d5 100644
--- a/com/android/server/am/ActivityManagerShellCommand.java
+++ b/com/android/server/am/ActivityManagerShellCommand.java
@@ -73,10 +73,8 @@
 
 import static android.app.ActivityManager.RESIZE_MODE_SYSTEM;
 import static android.app.ActivityManager.RESIZE_MODE_USER;
-import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
 import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.view.Display.INVALID_DISPLAY;
 
@@ -86,15 +84,6 @@
     public static final String NO_CLASS_ERROR_CODE = "Error type 3";
     private static final String SHELL_PACKAGE_NAME = "com.android.shell";
 
-    // Is the object moving in a positive direction?
-    private static final boolean MOVING_FORWARD = true;
-    // Is the object moving in the horizontal plan?
-    private static final boolean MOVING_HORIZONTALLY = true;
-    // Is the object current point great then its target point?
-    private static final boolean GREATER_THAN_TARGET = true;
-    // Amount we reduce the stack size by when testing a task re-size.
-    private static final int STACK_BOUNDS_INSET = 10;
-
     // IPC interface to activity manager -- don't need to do additional security checks.
     final IActivityManager mInterface;
 
@@ -1944,8 +1933,6 @@
                 return runStackInfo(pw);
             case "move-top-activity-to-pinned-stack":
                 return runMoveTopActivityToPinnedStack(pw);
-            case "size-docked-stack-test":
-                return runStackSizeDockedStackTest(pw);
             case "remove":
                 return runStackRemove(pw);
             default:
@@ -2143,89 +2130,6 @@
         return 0;
     }
 
-    int runStackSizeDockedStackTest(PrintWriter pw) throws RemoteException {
-        final PrintWriter err = getErrPrintWriter();
-        final int stepSize = Integer.parseInt(getNextArgRequired());
-        final String side = getNextArgRequired();
-        final String delayStr = getNextArg();
-        final int delayMs = (delayStr != null) ? Integer.parseInt(delayStr) : 0;
-
-        ActivityManager.StackInfo info = mInterface.getStackInfo(
-                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_UNDEFINED);
-        if (info == null) {
-            err.println("Docked stack doesn't exist");
-            return -1;
-        }
-        if (info.bounds == null) {
-            err.println("Docked stack doesn't have a bounds");
-            return -1;
-        }
-        Rect bounds = info.bounds;
-
-        final boolean horizontalGrowth = "l".equals(side) || "r".equals(side);
-        final int changeSize = (horizontalGrowth ? bounds.width() : bounds.height()) / 2;
-        int currentPoint;
-        switch (side) {
-            case "l":
-                currentPoint = bounds.left;
-                break;
-            case "r":
-                currentPoint = bounds.right;
-                break;
-            case "t":
-                currentPoint = bounds.top;
-                break;
-            case "b":
-                currentPoint = bounds.bottom;
-                break;
-            default:
-                err.println("Unknown growth side: " + side);
-                return -1;
-        }
-
-        final int startPoint = currentPoint;
-        final int minPoint = currentPoint - changeSize;
-        final int maxPoint = currentPoint + changeSize;
-
-        int maxChange;
-        pw.println("Shrinking docked stack side=" + side);
-        pw.flush();
-        while (currentPoint > minPoint) {
-            maxChange = Math.min(stepSize, currentPoint - minPoint);
-            currentPoint -= maxChange;
-            setBoundsSide(bounds, side, currentPoint);
-            int res = resizeStack(DOCKED_STACK_ID, bounds, delayMs);
-            if (res < 0) {
-                return res;
-            }
-        }
-
-        pw.println("Growing docked stack side=" + side);
-        pw.flush();
-        while (currentPoint < maxPoint) {
-            maxChange = Math.min(stepSize, maxPoint - currentPoint);
-            currentPoint += maxChange;
-            setBoundsSide(bounds, side, currentPoint);
-            int res = resizeStack(DOCKED_STACK_ID, bounds, delayMs);
-            if (res < 0) {
-                return res;
-            }
-        }
-
-        pw.println("Back to Original size side=" + side);
-        pw.flush();
-        while (currentPoint > startPoint) {
-            maxChange = Math.min(stepSize, currentPoint - startPoint);
-            currentPoint -= maxChange;
-            setBoundsSide(bounds, side, currentPoint);
-            int res = resizeStack(DOCKED_STACK_ID, bounds, delayMs);
-            if (res < 0) {
-                return res;
-            }
-        }
-        return 0;
-    }
-
     void setBoundsSide(Rect bounds, String side, int value) {
         switch (side) {
             case "l":
@@ -2687,10 +2591,6 @@
             pw.println("           Change docked stack to <LEFT,TOP,RIGHT,BOTTOM>");
             pw.println("           and supplying temporary different task bounds indicated by");
             pw.println("           <TASK_LEFT,TOP,RIGHT,BOTTOM>");
-            pw.println("       size-docked-stack-test: <STEP_SIZE> <l|t|r|b> [DELAY_MS]");
-            pw.println("           Test command for sizing docked stack by");
-            pw.println("           <STEP_SIZE> increments from the side <l>eft, <t>op, <r>ight, or <b>ottom");
-            pw.println("           applying the optional [DELAY_MS] between each step.");
             pw.println("       move-top-activity-to-pinned-stack: <STACK_ID> <LEFT,TOP,RIGHT,BOTTOM>");
             pw.println("           Moves the top activity from");
             pw.println("           <STACK_ID> to the pinned stack using <LEFT,TOP,RIGHT,BOTTOM> for the");
diff --git a/com/android/server/am/ActivityMetricsLogger.java b/com/android/server/am/ActivityMetricsLogger.java
index fdcb8c6..93c0f77 100644
--- a/com/android/server/am/ActivityMetricsLogger.java
+++ b/com/android/server/am/ActivityMetricsLogger.java
@@ -5,6 +5,7 @@
 import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
 import static android.app.ActivityManagerInternal.APP_TRANSITION_TIMEOUT;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
@@ -127,7 +128,7 @@
             case WINDOWING_MODE_SPLIT_SCREEN_SECONDARY:
                 mWindowState = WINDOW_STATE_SIDE_BY_SIDE;
                 break;
-            case WINDOW_STATE_FREEFORM:
+            case WINDOWING_MODE_FREEFORM:
                 mWindowState = WINDOW_STATE_FREEFORM;
                 break;
             default:
diff --git a/com/android/server/am/ActivityRecord.java b/com/android/server/am/ActivityRecord.java
index 7b0b942..2c72a4d 100644
--- a/com/android/server/am/ActivityRecord.java
+++ b/com/android/server/am/ActivityRecord.java
@@ -17,9 +17,7 @@
 package com.android.server.am;
 
 import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
-import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
 import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
-import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
 import static android.app.ActivityManager.TaskDescription.ATTR_TASKDESCRIPTION_PREFIX;
 import static android.app.ActivityOptions.ANIM_CLIP_REVEAL;
 import static android.app.ActivityOptions.ANIM_CUSTOM;
@@ -60,6 +58,10 @@
 import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE;
 import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TASK;
 import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TOP;
+import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_ALWAYS;
+import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_DEFAULT;
+import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_IF_WHITELISTED;
+import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_NEVER;
 import static android.content.pm.ActivityInfo.PERSIST_ACROSS_REBOOTS;
 import static android.content.pm.ActivityInfo.PERSIST_ROOT_ONLY;
 import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZEABLE;
@@ -284,6 +286,7 @@
     int configChangeFlags;  // which config values have changed
     private boolean keysPaused;     // has key dispatching been paused for it?
     int launchMode;         // the launch mode activity attribute.
+    int lockTaskLaunchMode; // the lockTaskMode manifest attribute, subject to override
     boolean visible;        // does this activity's window need to be shown?
     boolean visibleIgnoringKeyguard; // is this activity visible, ignoring the fact that Keyguard
                                      // might hide this activity?
@@ -420,9 +423,13 @@
             if (iconFilename != null || taskDescription.getLabel() != null ||
                     taskDescription.getPrimaryColor() != 0) {
                 pw.print(prefix); pw.print("taskDescription:");
-                        pw.print(" iconFilename="); pw.print(taskDescription.getIconFilename());
                         pw.print(" label=\""); pw.print(taskDescription.getLabel());
                                 pw.print("\"");
+                        pw.print(" icon="); pw.print(taskDescription.getInMemoryIcon() != null
+                                ? taskDescription.getInMemoryIcon().getByteCount() + " bytes"
+                                : "null");
+                        pw.print(" iconResource="); pw.print(taskDescription.getIconResource());
+                        pw.print(" iconFilename="); pw.print(taskDescription.getIconFilename());
                         pw.print(" primaryColor=");
                         pw.println(Integer.toHexString(taskDescription.getPrimaryColor()));
                         pw.print(prefix + " backgroundColor=");
@@ -432,9 +439,6 @@
                         pw.print(prefix + " navigationBarColor=");
                         pw.println(Integer.toHexString(taskDescription.getNavigationBarColor()));
             }
-            if (iconFilename == null && taskDescription.getIcon() != null) {
-                pw.print(prefix); pw.println("taskDescription contains Bitmap");
-            }
         }
         if (results != null) {
             pw.print(prefix); pw.print("results="); pw.println(results);
@@ -661,8 +665,7 @@
             return;
         }
 
-        final boolean inPictureInPictureMode = (task.getStackId() == PINNED_STACK_ID) &&
-                (targetStackBounds != null);
+        final boolean inPictureInPictureMode = inPinnedWindowingMode() && targetStackBounds != null;
         if (inPictureInPictureMode != mLastReportedPictureInPictureMode || forceUpdate) {
             // Picture-in-picture mode changes also trigger a multi-window mode change as well, so
             // update that here in order
@@ -684,10 +687,6 @@
         }
     }
 
-    boolean isFreeform() {
-        return task != null && task.getStackId() == FREEFORM_WORKSPACE_STACK_ID;
-    }
-
     @Override
     protected int getChildCount() {
         // {@link ActivityRecord} is a leaf node and has no children.
@@ -831,23 +830,6 @@
         hasBeenLaunched = false;
         mStackSupervisor = supervisor;
 
-        mRotationAnimationHint = aInfo.rotationAnimation;
-
-        if (options != null) {
-            pendingOptions = options;
-            mLaunchTaskBehind = pendingOptions.getLaunchTaskBehind();
-
-            final int rotationAnimation = pendingOptions.getRotationAnimationHint();
-            // Only override manifest supplied option if set.
-            if (rotationAnimation >= 0) {
-                mRotationAnimationHint = rotationAnimation;
-            }
-            PendingIntent usageReport = pendingOptions.getUsageTimeReport();
-            if (usageReport != null) {
-                appTimeTracker = new AppTimeTracker(usageReport);
-            }
-        }
-
         // This starts out true, since the initial state of an activity is that we have everything,
         // and we shouldn't never consider it lacking in state to be removed if it dies.
         haveState = true;
@@ -914,6 +896,32 @@
 
         mShowWhenLocked = (aInfo.flags & FLAG_SHOW_WHEN_LOCKED) != 0;
         mTurnScreenOn = (aInfo.flags & FLAG_TURN_SCREEN_ON) != 0;
+
+        mRotationAnimationHint = aInfo.rotationAnimation;
+        lockTaskLaunchMode = aInfo.lockTaskLaunchMode;
+        if (appInfo.isPrivilegedApp() && (lockTaskLaunchMode == LOCK_TASK_LAUNCH_MODE_ALWAYS
+                || lockTaskLaunchMode == LOCK_TASK_LAUNCH_MODE_NEVER)) {
+            lockTaskLaunchMode = LOCK_TASK_LAUNCH_MODE_DEFAULT;
+        }
+
+        if (options != null) {
+            pendingOptions = options;
+            mLaunchTaskBehind = options.getLaunchTaskBehind();
+
+            final int rotationAnimation = pendingOptions.getRotationAnimationHint();
+            // Only override manifest supplied option if set.
+            if (rotationAnimation >= 0) {
+                mRotationAnimationHint = rotationAnimation;
+            }
+            final PendingIntent usageReport = pendingOptions.getUsageTimeReport();
+            if (usageReport != null) {
+                appTimeTracker = new AppTimeTracker(usageReport);
+            }
+            final boolean useLockTask = pendingOptions.getLockTaskMode();
+            if (useLockTask && lockTaskLaunchMode == LOCK_TASK_LAUNCH_MODE_DEFAULT) {
+                lockTaskLaunchMode = LOCK_TASK_LAUNCH_MODE_IF_WHITELISTED;
+            }
+        }
     }
 
     AppWindowContainerController getWindowContainerController() {
@@ -948,7 +956,7 @@
         // update the initial multi-window modes so that the callbacks are scheduled correctly when
         // the user leaves that mode.
         mLastReportedMultiWindowMode = !task.mFullscreen;
-        mLastReportedPictureInPictureMode = (task.getStackId() == PINNED_STACK_ID);
+        mLastReportedPictureInPictureMode = inPinnedWindowingMode();
     }
 
     void removeWindowContainer() {
@@ -1551,7 +1559,7 @@
             // On devices that support leanback only (Android TV), Recents activity can only be
             // visible if the home stack is the focused stack or we are in split-screen mode.
             final ActivityDisplay display = getDisplay();
-            boolean hasSplitScreenStack = display != null && display.hasSplitScreenStack();
+            boolean hasSplitScreenStack = display != null && display.hasSplitScreenPrimaryStack();
             isVisible = hasSplitScreenStack || mStackSupervisor.isFocusedStack(getStack());
         }
 
@@ -2739,6 +2747,8 @@
 
     void setShowWhenLocked(boolean showWhenLocked) {
         mShowWhenLocked = showWhenLocked;
+        mStackSupervisor.ensureActivitiesVisibleLocked(null, 0 /* configChanges */,
+                false /* preserveWindows */);
     }
 
     /**
diff --git a/com/android/server/am/ActivityStack.java b/com/android/server/am/ActivityStack.java
index 1940ca2..941c371 100644
--- a/com/android/server/am/ActivityStack.java
+++ b/com/android/server/am/ActivityStack.java
@@ -16,27 +16,25 @@
 
 package com.android.server.am;
 
-import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
-import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
-import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.app.WindowConfiguration.activityTypeToString;
+import static android.app.WindowConfiguration.windowingModeToString;
 import static android.content.pm.ActivityInfo.CONFIG_SCREEN_LAYOUT;
 import static android.content.pm.ActivityInfo.FLAG_RESUME_WHILE_PAUSING;
 import static android.content.pm.ActivityInfo.FLAG_SHOW_FOR_ALL_USERS;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD;
-
 import static android.view.Display.INVALID_DISPLAY;
+
 import static com.android.server.am.ActivityDisplay.POSITION_BOTTOM;
 import static com.android.server.am.ActivityDisplay.POSITION_TOP;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ADD_REMOVE;
@@ -100,7 +98,6 @@
 import android.app.Activity;
 import android.app.ActivityManager;
 import android.app.ActivityManager.RunningTaskInfo;
-import android.app.ActivityManager.StackId;
 import android.app.ActivityOptions;
 import android.app.AppGlobals;
 import android.app.IActivityController;
@@ -110,6 +107,7 @@
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.res.Configuration;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.net.Uri;
 import android.os.Binder;
@@ -192,10 +190,6 @@
     // finished destroying itself.
     private static final int DESTROY_TIMEOUT = 10 * 1000;
 
-    // How long until we reset a task when the user returns to it.  Currently
-    // disabled.
-    private static final long ACTIVITY_INACTIVE_RESET_TIME = 0;
-
     // Set to false to disable the preview that is shown while a new activity
     // is being started.
     private static final boolean SHOW_APP_STARTING_PREVIEW = true;
@@ -352,12 +346,11 @@
     private final SparseArray<Rect> mTmpBounds = new SparseArray<>();
     private final SparseArray<Rect> mTmpInsetBounds = new SparseArray<>();
     private final Rect mTmpRect2 = new Rect();
+    private final Point mTmpSize = new Point();
 
     /** Run all ActivityStacks through this */
     protected final ActivityStackSupervisor mStackSupervisor;
 
-    private final LaunchingTaskPositioner mTaskPositioner;
-
     private boolean mTopActivityOccludesKeyguard;
     private ActivityRecord mTopDismissingKeyguardActivity;
 
@@ -460,8 +453,6 @@
         mWindowManager = mService.mWindowManager;
         mStackId = stackId;
         mCurrentUser = mService.mUserController.getCurrentUserId();
-        mTaskPositioner = mStackId == FREEFORM_WORKSPACE_STACK_ID
-                ? new LaunchingTaskPositioner() : null;
         mTmpRect2.setEmpty();
         setWindowingMode(windowingMode);
         setActivityType(activityType);
@@ -479,6 +470,39 @@
         return mWindowContainerController;
     }
 
+    @Override
+    public void onConfigurationChanged(Configuration newParentConfig) {
+        final int prevWindowingMode = getWindowingMode();
+        super.onConfigurationChanged(newParentConfig);
+        final ActivityDisplay display = getDisplay();
+        if (display != null && prevWindowingMode != getWindowingMode()) {
+            display.onStackWindowingModeChanged(this);
+        }
+    }
+
+    @Override
+    public boolean isCompatible(int windowingMode, int activityType) {
+        // TODO: Should we just move this to ConfigurationContainer?
+        if (activityType == ACTIVITY_TYPE_UNDEFINED) {
+            // Undefined activity types end up in a standard stack once the stack is created on a
+            // display, so they should be considered compatible.
+            activityType = ACTIVITY_TYPE_STANDARD;
+        }
+        final ActivityDisplay display = getDisplay();
+        if (display != null) {
+            if (activityType == ACTIVITY_TYPE_STANDARD
+                    && windowingMode == WINDOWING_MODE_UNDEFINED) {
+                // Standard activity types will mostly take on the windowing mode of the display if
+                // one isn't specified, so look-up a compatible stack based on the display's
+                // windowing mode.
+                windowingMode = display.getWindowingMode();
+            }
+            windowingMode =
+                    display.updateWindowingModeForSplitScreenIfNeeded(windowingMode, activityType);
+        }
+        return super.isCompatible(windowingMode, activityType);
+    }
+
     /** Adds the stack to specified display and calls WindowManager to do the same. */
     void reparent(ActivityDisplay activityDisplay, boolean onTop) {
         removeFromDisplay();
@@ -502,14 +526,11 @@
         mDisplayId = activityDisplay.mDisplayId;
         mBounds = bounds != null ? new Rect(bounds) : null;
         mFullscreen = mBounds == null;
-        if (mTaskPositioner != null) {
-            mTaskPositioner.setDisplay(activityDisplay.mDisplay);
-            mTaskPositioner.configure(mBounds);
-        }
+
         onParentChanged();
 
         activityDisplay.addChild(this, onTop ? POSITION_TOP : POSITION_BOTTOM);
-        if (mStackId == DOCKED_STACK_ID) {
+        if (inSplitScreenPrimaryWindowingMode()) {
             // If we created a docked stack we want to resize it so it resizes all other stacks
             // in the system.
             mStackSupervisor.resizeDockedStackLocked(
@@ -533,9 +554,6 @@
             display.removeChild(this);
         }
         mDisplayId = INVALID_DISPLAY;
-        if (mTaskPositioner != null) {
-            mTaskPositioner.reset();
-        }
     }
 
     /** Removes the stack completely. Also calls WindowManager to do the same on its side. */
@@ -639,9 +657,6 @@
 
     void setBounds(Rect bounds) {
         mBounds = mFullscreen ? null : new Rect(bounds);
-        if (mTaskPositioner != null) {
-            mTaskPositioner.configure(bounds);
-        }
     }
 
     ActivityRecord topRunningActivityLocked() {
@@ -818,14 +833,6 @@
         return isActivityTypeHome() || isActivityTypeRecents();
     }
 
-    final boolean isDockedStack() {
-        return mStackId == DOCKED_STACK_ID;
-    }
-
-    final boolean isPinnedStack() {
-        return mStackId == PINNED_STACK_ID;
-    }
-
     final boolean isOnHomeDisplay() {
         return mDisplayId == DEFAULT_DISPLAY;
     }
@@ -1505,9 +1512,9 @@
      * needed. A stack is considered translucent if it don't contain a visible or
      * starting (about to be visible) activity that is fullscreen (opaque).
      * @param starting The currently starting activity or null if there is none.
-     * @param stackBehindId The id of the stack directly behind this one.
+     * @param stackBehind The stack directly behind this one.
      */
-    private boolean isStackTranslucent(ActivityRecord starting, int stackBehindId) {
+    private boolean isStackTranslucent(ActivityRecord starting, ActivityStack stackBehind) {
         for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
             final TaskRecord task = mTaskHistory.get(taskNdx);
             final ArrayList<ActivityRecord> activities = task.mActivities;
@@ -1532,7 +1539,6 @@
                     return false;
                 }
 
-                final ActivityStack stackBehind = mStackSupervisor.getStack(stackBehindId);
                 final boolean stackBehindHomeOrRecent = stackBehind != null
                         && stackBehind.isHomeOrRecentsStack();
                 if (!isHomeOrRecentsStack() && r.frontOfTask && task.isOverHomeStack()
@@ -1553,6 +1559,10 @@
                 && !mForceHidden;
     }
 
+    boolean isTopStackOnDisplay() {
+        return getDisplay().isTopStack(this);
+    }
+
     /**
      * Returns true if the stack should be visible.
      *
@@ -1563,23 +1573,15 @@
             return false;
         }
 
-        if (mStackSupervisor.isFrontStackOnDisplay(this) || mStackSupervisor.isFocusedStack(this)) {
+        final ActivityDisplay display = getDisplay();
+        if (isTopStackOnDisplay() || mStackSupervisor.isFocusedStack(this)) {
             return true;
         }
 
-        final ActivityDisplay display = getDisplay();
-        final ArrayList<ActivityStack> displayStacks = display.mStacks;
-        final int stackIndex = displayStacks.indexOf(this);
-
-        if (stackIndex == displayStacks.size() - 1) {
-            Slog.wtf(TAG,
-                    "Stack=" + this + " isn't front stack but is at the top of the stack list");
-            return false;
-        }
+        final int stackIndex = display.getIndexOf(this);
 
         // Check position and visibility of this stack relative to the front stack on its display.
-        final ActivityStack topStack = getTopStackOnDisplay();
-        final int topStackId = topStack.mStackId;
+        final ActivityStack topStack = getDisplay().getTopStack();
         final int windowingMode = getWindowingMode();
         final int activityType = getActivityType();
 
@@ -1587,7 +1589,7 @@
             // If the assistant stack is focused and translucent, then the docked stack is always
             // visible
             if (topStack.isActivityTypeAssistant()) {
-                return topStack.isStackTranslucent(starting, DOCKED_STACK_ID);
+                return topStack.isStackTranslucent(starting, this);
             }
             return true;
         }
@@ -1596,34 +1598,31 @@
         // A case would be if recents stack exists but has no tasks and is below the docked stack
         // and home stack is below recents
         if (activityType == ACTIVITY_TYPE_HOME) {
-            final ActivityStack splitScreenStack = display.getStack(
-                    WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_UNDEFINED);
-            int dockedStackIndex = displayStacks.indexOf(splitScreenStack);
+            final ActivityStack splitScreenStack = display.getSplitScreenPrimaryStack();
+            int dockedStackIndex = display.getIndexOf(splitScreenStack);
             if (dockedStackIndex > stackIndex && stackIndex != dockedStackIndex - 1) {
                 return false;
             }
         }
 
         // Find the first stack behind front stack that actually got something visible.
-        int stackBehindTopIndex = displayStacks.indexOf(topStack) - 1;
+        int stackBehindTopIndex = display.getIndexOf(topStack) - 1;
         while (stackBehindTopIndex >= 0 &&
-                displayStacks.get(stackBehindTopIndex).topRunningActivityLocked() == null) {
+                display.getChildAt(stackBehindTopIndex).topRunningActivityLocked() == null) {
             stackBehindTopIndex--;
         }
         final ActivityStack stackBehindTop = (stackBehindTopIndex >= 0)
-                ? displayStacks.get(stackBehindTopIndex) : null;
-        int stackBehindTopId = INVALID_STACK_ID;
+                ? display.getChildAt(stackBehindTopIndex) : null;
         int stackBehindTopWindowingMode = WINDOWING_MODE_UNDEFINED;
         int stackBehindTopActivityType = ACTIVITY_TYPE_UNDEFINED;
         if (stackBehindTop != null) {
-            stackBehindTopId = stackBehindTop.mStackId;
             stackBehindTopWindowingMode = stackBehindTop.getWindowingMode();
             stackBehindTopActivityType = stackBehindTop.getActivityType();
         }
 
         final boolean alwaysOnTop = topStack.getWindowConfiguration().isAlwaysOnTop();
         if (topStack.getWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY || alwaysOnTop) {
-            if (stackIndex == stackBehindTopIndex) {
+            if (this == stackBehindTop) {
                 // Stacks directly behind the docked or pinned stack are always visible.
                 return true;
             } else if (alwaysOnTop && stackIndex == stackBehindTopIndex - 1) {
@@ -1632,14 +1631,13 @@
                 if (stackBehindTopWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
                     return true;
                 } else if (stackBehindTopActivityType == ACTIVITY_TYPE_ASSISTANT) {
-                    return displayStacks.get(stackBehindTopIndex).isStackTranslucent(
-                            starting, mStackId);
+                    return stackBehindTop.isStackTranslucent(starting, this);
                 }
             }
         }
 
         if (topStack.isBackdropToTranslucentActivity()
-                && topStack.isStackTranslucent(starting, stackBehindTopId)) {
+                && topStack.isStackTranslucent(starting, stackBehindTop)) {
             // Stacks behind the fullscreen or assistant stack with a translucent activity are
             // always visible so they can act as a backdrop to the translucent activity.
             // For example, dialog activities
@@ -1657,14 +1655,15 @@
             }
         }
 
-        if (StackId.isStaticStack(mStackId)
-                || isHomeOrRecentsStack() || isActivityTypeAssistant()) {
-            // Visibility of any static stack should have been determined by the conditions above.
+        if (isOnHomeDisplay()) {
+            // Visibility of any stack on default display should have been determined by the
+            // conditions above.
             return false;
         }
 
-        for (int i = stackIndex + 1; i < displayStacks.size(); i++) {
-            final ActivityStack stack = displayStacks.get(i);
+        final int stackCount = display.getChildCount();
+        for (int i = stackIndex + 1; i < stackCount; i++) {
+            final ActivityStack stack = display.getChildAt(i);
 
             if (!stack.mFullscreen && !stack.hasFullscreenTask()) {
                 continue;
@@ -1675,7 +1674,7 @@
                 return false;
             }
 
-            if (!stack.isStackTranslucent(starting, INVALID_STACK_ID)) {
+            if (!stack.isStackTranslucent(starting, null /* stackBehind */)) {
                 return false;
             }
         }
@@ -1801,7 +1800,8 @@
                         makeInvisible(r);
                     }
                 }
-                if (mStackId == FREEFORM_WORKSPACE_STACK_ID) {
+                final int windowingMode = getWindowingMode();
+                if (windowingMode == WINDOWING_MODE_FREEFORM) {
                     // The visibility of tasks and the activities they contain in freeform stack are
                     // determined individually unlike other stacks where the visibility or fullscreen
                     // status of an activity in a previous task affects other.
@@ -1816,7 +1816,8 @@
                     // show activities in the next application stack behind them vs. another
                     // task in the home stack like recents.
                     behindFullscreenActivity = true;
-                } else if (mStackId == FULLSCREEN_WORKSPACE_STACK_ID) {
+                } else if (windowingMode == WINDOWING_MODE_FULLSCREEN
+                        || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) {
                     if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Skipping after task=" + task
                             + " returning to non-application type=" + task.getTaskToReturnTo());
                     // Once we reach a fullscreen stack task that has a running activity and should
@@ -1852,6 +1853,32 @@
     }
 
     /**
+     * Returns true if this stack should be resized to match the bounds specified by
+     * {@link ActivityOptions#setLaunchBounds} when launching an activity into the stack.
+     */
+    boolean resizeStackWithLaunchBounds() {
+        return inPinnedWindowingMode();
+    }
+
+    /**
+     * Returns true if we try to maintain focus in the current stack when the top activity finishes.
+     */
+    private boolean keepFocusInStackIfPossible() {
+        final int windowingMode = getWindowingMode();
+        return windowingMode == WINDOWING_MODE_FREEFORM
+                || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
+                || windowingMode == WINDOWING_MODE_PINNED;
+    }
+
+    /**
+     * Returns true if the top task in the task is allowed to return home when finished and
+     * there are other tasks in the stack.
+     */
+    boolean allowTopTaskToReturnHome() {
+        return !inPinnedWindowingMode();
+    }
+
+    /**
      * @return the top most visible activity that wants to dismiss Keyguard
      */
     ActivityRecord getTopDismissingKeyguardActivity() {
@@ -1867,7 +1894,7 @@
      */
     boolean checkKeyguardVisibility(ActivityRecord r, boolean shouldBeVisible,
             boolean isTop) {
-        final boolean isInPinnedStack = r.getStack().getStackId() == PINNED_STACK_ID;
+        final boolean isInPinnedStack = r.inPinnedWindowingMode();
         final boolean keyguardShowing = mStackSupervisor.mKeyguardController.isKeyguardShowing(
                 mDisplayId != INVALID_DISPLAY ? mDisplayId : DEFAULT_DISPLAY);
         final boolean keyguardLocked = mStackSupervisor.mKeyguardController.isKeyguardLocked();
@@ -2165,7 +2192,7 @@
         mResumedActivity = r;
         r.state = ActivityState.RESUMED;
         mService.setResumedActivityUncheckLocked(r, reason);
-        mStackSupervisor.addRecentActivity(r);
+        mStackSupervisor.mRecentTasks.add(r.getTask());
     }
 
     private boolean resumeTopActivityInnerLocked(ActivityRecord prev, ActivityOptions options) {
@@ -2572,8 +2599,8 @@
                     Slog.i(TAG, "Restarting because process died: " + next);
                     if (!next.hasBeenLaunched) {
                         next.hasBeenLaunched = true;
-                    } else  if (SHOW_APP_STARTING_PREVIEW && lastStack != null &&
-                            mStackSupervisor.isFrontStackOnDisplay(lastStack)) {
+                    } else  if (SHOW_APP_STARTING_PREVIEW && lastStack != null
+                            && lastStack.isTopStackOnDisplay()) {
                         next.showStartingWindow(null /* prev */, false /* newTask */,
                                 false /* taskSwitch */);
                     }
@@ -2617,7 +2644,7 @@
 
     private boolean resumeTopActivityInNextFocusableStack(ActivityRecord prev,
             ActivityOptions options, String reason) {
-        if ((!mFullscreen || !isOnHomeDisplay()) && adjustFocusToNextFocusableStackLocked(reason)) {
+        if (adjustFocusToNextFocusableStackLocked(reason)) {
             // Try to move focus to the next visible stack with a running activity if this
             // stack is not covering the entire screen or is on a secondary display (with no home
             // stack).
@@ -2746,9 +2773,8 @@
             // make underlying task focused when this one will be finished.
             int returnToType = isLastTaskOverHome
                     ? task.getTaskToReturnTo() : ACTIVITY_TYPE_STANDARD;
-            if (fromHomeOrRecents && StackId.allowTopTaskToReturnHome(mStackId)) {
-                returnToType = topTask == null
-                        ? ACTIVITY_TYPE_HOME : topTask.getActivityType();
+            if (fromHomeOrRecents && allowTopTaskToReturnHome()) {
+                returnToType = topTask == null ? ACTIVITY_TYPE_HOME : topTask.getActivityType();
             }
             task.setTaskToReturnTo(returnToType);
         }
@@ -2901,7 +2927,7 @@
             // Ensure the caller has requested not to trigger auto-enter PiP
             return false;
         }
-        if (pipCandidate == null || pipCandidate.getStackId() == PINNED_STACK_ID) {
+        if (pipCandidate == null || pipCandidate.inPinnedWindowingMode()) {
             // Ensure that we do not trigger entering PiP an activity on the pinned stack
             return false;
         }
@@ -3186,15 +3212,8 @@
 
     final ActivityRecord resetTaskIfNeededLocked(ActivityRecord taskTop,
             ActivityRecord newActivity) {
-        boolean forceReset =
+        final boolean forceReset =
                 (newActivity.info.flags & ActivityInfo.FLAG_CLEAR_TASK_ON_LAUNCH) != 0;
-        if (ACTIVITY_INACTIVE_RESET_TIME > 0
-                && taskTop.getTask().getInactiveDuration() > ACTIVITY_INACTIVE_RESET_TIME) {
-            if ((newActivity.info.flags & ActivityInfo.FLAG_ALWAYS_RETAIN_TASK_STATE) == 0) {
-                forceReset = true;
-            }
-        }
-
         final TaskRecord task = taskTop.getTask();
 
         /** False until we evaluate the TaskRecord associated with taskTop. Switches to true
@@ -3291,7 +3310,7 @@
         final String myReason = reason + " adjustFocus";
 
         if (next != r) {
-            if (next != null && StackId.keepFocusInStackIfPossible(mStackId) && isFocusable()) {
+            if (next != null && keepFocusInStackIfPossible() && isFocusable()) {
                 // For freeform, docked, and pinned stacks we always keep the focus within the
                 // stack as long as there is a running activity.
                 return;
@@ -3757,7 +3776,7 @@
 
         if (mode == FINISH_IMMEDIATELY
                 || (prevState == ActivityState.PAUSED
-                    && (mode == FINISH_AFTER_PAUSE || mStackId == PINNED_STACK_ID))
+                    && (mode == FINISH_AFTER_PAUSE || inPinnedWindowingMode()))
                 || finishingActivityInNonFocusedStack
                 || prevState == STOPPING
                 || prevState == STOPPED
@@ -4436,7 +4455,7 @@
             AppTimeTracker timeTracker, String reason) {
         if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, "moveTaskToFront: " + tr);
 
-        final ActivityStack topStack = getTopStackOnDisplay();
+        final ActivityStack topStack = getDisplay().getTopStack();
         final ActivityRecord topActivity = topStack != null ? topStack.topActivity() : null;
         final int numTasks = mTaskHistory.size();
         final int index = mTaskHistory.indexOf(tr);
@@ -4464,7 +4483,9 @@
         // Don't refocus if invisible to current user
         final ActivityRecord top = tr.getTopActivity();
         if (top == null || !top.okToShowLocked()) {
-            mStackSupervisor.addRecentActivity(top);
+            if (top != null) {
+                mStackSupervisor.mRecentTasks.add(top.getTask());
+            }
             ActivityOptions.abort(options);
             return;
         }
@@ -4524,7 +4545,7 @@
         // If we have a watcher, preflight the move before committing to it.  First check
         // for *other* available tasks, but if none are available, then try again allowing the
         // current task to be selected.
-        if (mStackSupervisor.isFrontStackOnDisplay(this) && mService.mController != null) {
+        if (isTopStackOnDisplay() && mService.mController != null) {
             ActivityRecord next = topRunningActivityLocked(null, taskId);
             if (next == null) {
                 next = topRunningActivityLocked(null, 0);
@@ -4571,8 +4592,8 @@
             mWindowContainerController.positionChildAtBottom(tr.getWindowContainerController());
         }
 
-        if (mStackId == PINNED_STACK_ID) {
-            mStackSupervisor.removeStackLocked(PINNED_STACK_ID);
+        if (inPinnedWindowingMode()) {
+            mStackSupervisor.removeStack(this);
             return true;
         }
 
@@ -4604,15 +4625,6 @@
         return true;
     }
 
-    /**
-     * Get the topmost stack on the current display. It may be different from focused stack, because
-     * focus may be on another display.
-     */
-    private ActivityStack getTopStackOnDisplay() {
-        final ArrayList<ActivityStack> stacks = getDisplay().mStacks;
-        return stacks.isEmpty() ? null : stacks.get(stacks.size() - 1);
-    }
-
     static void logStartActivity(int tag, ActivityRecord r, TaskRecord task) {
         final Uri data = r.intent.getData();
         final String strData = data != null ? data.toSafeString() : null;
@@ -4687,7 +4699,7 @@
         for (int i = mTaskHistory.size() - 1; i >= 0; i--) {
             final TaskRecord task = mTaskHistory.get(i);
             if (task.isResizeable()) {
-                if (mStackId == FREEFORM_WORKSPACE_STACK_ID) {
+                if (inFreeformWindowingMode()) {
                     // For freeform stack we don't adjust the size of the tasks to match that
                     // of the stack, but we do try to make sure the tasks are still contained
                     // with the bounds of the stack.
@@ -4889,7 +4901,7 @@
             if (focusedStack && topTask) {
                 // Give the latest time to ensure foreground task can be sorted
                 // at the first, because lastActiveTime of creating task is 0.
-                ci.lastActiveTime = System.currentTimeMillis();
+                ci.lastActiveTime = SystemClock.elapsedRealtime();
                 topTask = false;
             }
 
@@ -5069,7 +5081,7 @@
             if (task.autoRemoveFromRecents() || isVoiceSession) {
                 // Task creator asked to remove this when done, or this task was a voice
                 // interaction, so it should not remain on the recent tasks list.
-                mStackSupervisor.removeTaskFromRecents(task);
+                mStackSupervisor.mRecentTasks.remove(task);
             }
 
             task.removeWindowContainer();
@@ -5097,7 +5109,7 @@
         task.setStack(null);
 
         // Notify if a task from the pinned stack is being removed (or moved depending on the mode)
-        if (mStackId == PINNED_STACK_ID) {
+        if (inPinnedWindowingMode()) {
             mService.mTaskChangeNotificationController.notifyActivityUnpinned();
         }
     }
@@ -5120,10 +5132,12 @@
     }
 
     boolean layoutTaskInStack(TaskRecord task, ActivityInfo.WindowLayout windowLayout) {
-        if (mTaskPositioner == null) {
+        if (!task.inFreeformWindowingMode()) {
             return false;
         }
-        mTaskPositioner.updateDefaultBounds(task, mTaskHistory, windowLayout);
+        mStackSupervisor.getLaunchingTaskPositioner()
+                .updateDefaultBounds(task, mTaskHistory, windowLayout);
+
         return true;
     }
 
@@ -5248,10 +5262,12 @@
     @Override
     public String toString() {
         return "ActivityStack{" + Integer.toHexString(System.identityHashCode(this))
-                + " stackId=" + mStackId + ", " + mTaskHistory.size() + " tasks}";
+                + " stackId=" + mStackId + " type=" + activityTypeToString(getActivityType())
+                + " mode=" + windowingModeToString(getWindowingMode()) + ", "
+                + mTaskHistory.size() + " tasks}";
     }
 
-    void onLockTaskPackagesUpdatedLocked() {
+    void onLockTaskPackagesUpdated() {
         for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
             mTaskHistory.get(taskNdx).setLockTaskAuth();
         }
diff --git a/com/android/server/am/ActivityStackSupervisor.java b/com/android/server/am/ActivityStackSupervisor.java
index da2827a..c15b5e2 100644
--- a/com/android/server/am/ActivityStackSupervisor.java
+++ b/com/android/server/am/ActivityStackSupervisor.java
@@ -21,11 +21,7 @@
 import static android.Manifest.permission.START_ANY_ACTIVITY;
 import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
 import static android.app.ActivityManager.START_TASK_TO_FRONT;
-import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
-import static android.app.ActivityManager.StackId.FIRST_DYNAMIC_STACK_ID;
-import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
 import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
-import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
 import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SECONDARY_DISPLAY;
 import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SPLIT_SCREEN;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
@@ -35,10 +31,13 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.app.WindowConfiguration.activityTypeToString;
+import static android.app.WindowConfiguration.windowingModeToString;
 import static android.content.pm.PackageManager.PERMISSION_DENIED;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.os.PowerManager.PARTIAL_WAKE_LOCK;
@@ -47,6 +46,7 @@
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.INVALID_DISPLAY;
 import static android.view.Display.TYPE_VIRTUAL;
+
 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_PICTURE_IN_PICTURE_EXPANDED_TO_FULLSCREEN;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ALL;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_FOCUS;
@@ -86,12 +86,13 @@
 import static com.android.server.am.TaskRecord.REPARENT_KEEP_STACK_AT_FRONT;
 import static com.android.server.am.TaskRecord.REPARENT_LEAVE_STACK_IN_PLACE;
 import static com.android.server.am.TaskRecord.REPARENT_MOVE_STACK_TO_FRONT;
+import static com.android.server.am.proto.ActivityStackSupervisorProto.CONFIGURATION_CONTAINER;
 import static com.android.server.am.proto.ActivityStackSupervisorProto.DISPLAYS;
 import static com.android.server.am.proto.ActivityStackSupervisorProto.FOCUSED_STACK_ID;
 import static com.android.server.am.proto.ActivityStackSupervisorProto.KEYGUARD_CONTROLLER;
 import static com.android.server.am.proto.ActivityStackSupervisorProto.RESUMED_ACTIVITY;
-import static com.android.server.am.proto.ActivityStackSupervisorProto.CONFIGURATION_CONTAINER;
 import static com.android.server.wm.AppTransition.TRANSIT_DOCK_TASK_FROM_RECENTS;
+
 import static java.lang.Integer.MAX_VALUE;
 
 import android.Manifest;
@@ -102,7 +103,6 @@
 import android.app.Activity;
 import android.app.ActivityManager;
 import android.app.ActivityManager.RunningTaskInfo;
-import android.app.ActivityManager.StackId;
 import android.app.ActivityManager.StackInfo;
 import android.app.ActivityManagerInternal.SleepToken;
 import android.app.ActivityOptions;
@@ -176,7 +176,8 @@
 import java.util.List;
 import java.util.Set;
 
-public class ActivityStackSupervisor extends ConfigurationContainer implements DisplayListener {
+public class ActivityStackSupervisor extends ConfigurationContainer implements DisplayListener,
+        RecentTasks.Callbacks {
     private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityStackSupervisor" : TAG_AM;
     private static final String TAG_FOCUS = TAG + POSTFIX_FOCUS;
     private static final String TAG_IDLE = TAG + POSTFIX_IDLE;
@@ -286,7 +287,7 @@
 
     final ActivityManagerService mService;
 
-    private RecentTasks mRecentTasks;
+    RecentTasks mRecentTasks;
 
     final ActivityStackSupervisorHandler mHandler;
 
@@ -294,8 +295,10 @@
     WindowManagerService mWindowManager;
     DisplayManager mDisplayManager;
 
+    LaunchingTaskPositioner mTaskPositioner = new LaunchingTaskPositioner();
+
     /** Counter for next free stack ID to use for dynamic activity stacks. */
-    private int mNextFreeStackId = FIRST_DYNAMIC_STACK_ID;
+    private int mNextFreeStackId = 0;
 
     /**
      * Maps the task identifier that activities are currently being started in to the userId of the
@@ -576,6 +579,7 @@
 
     void setRecentTasks(RecentTasks recentTasks) {
         mRecentTasks = recentTasks;
+        mRecentTasks.registerCallback(this);
     }
 
     /**
@@ -627,15 +631,6 @@
         return stack != null && stack == mFocusedStack;
     }
 
-    /** The top most stack on its display. */
-    boolean isFrontStackOnDisplay(ActivityStack stack) {
-        return isFrontOfStackList(stack, stack.getDisplay().mStacks);
-    }
-
-    private boolean isFrontOfStackList(ActivityStack stack, List<ActivityStack> stackList) {
-        return stack == stackList.get((stackList.size() - 1));
-    }
-
     /** NOTE: Should only be called from {@link ActivityStack#moveToFront} */
     void setFocusStackUnchecked(String reason, ActivityStack focusCandidate) {
         if (!focusCandidate.isFocusable()) {
@@ -711,7 +706,7 @@
     }
 
     TaskRecord anyTaskForIdLocked(int id, @AnyTaskForIdMatchTaskMode int matchMode) {
-        return anyTaskForIdLocked(id, matchMode, null);
+        return anyTaskForIdLocked(id, matchMode, null, !ON_TOP);
     }
 
     /**
@@ -719,11 +714,11 @@
      * @param id Id of the task we would like returned.
      * @param matchMode The mode to match the given task id in.
      * @param aOptions The activity options to use for restoration. Can be null.
+     * @param onTop If the stack for the task should be the topmost on the display.
      */
     TaskRecord anyTaskForIdLocked(int id, @AnyTaskForIdMatchTaskMode int matchMode,
-            @Nullable ActivityOptions aOptions) {
-        // If there is a stack id set, ensure that we are attempting to actually restore a task
-        // TODO: Don't really know if this is needed...
+            @Nullable ActivityOptions aOptions, boolean onTop) {
+        // If options are set, ensure that we are attempting to actually restore a task
         if (matchMode != MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE && aOptions != null) {
             throw new IllegalArgumentException("Should not specify activity options for non-restore"
                     + " lookup");
@@ -731,13 +726,25 @@
 
         int numDisplays = mActivityDisplays.size();
         for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
-            ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
-            for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
-                ActivityStack stack = stacks.get(stackNdx);
+            final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+            for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+                final ActivityStack stack = display.getChildAt(stackNdx);
                 final TaskRecord task = stack.taskForIdLocked(id);
-                if (task != null) {
-                    return task;
+                if (task == null) {
+                    continue;
                 }
+                if (aOptions != null) {
+                    // Resolve the stack the task should be placed in now based on options
+                    // and reparent if needed.
+                    final ActivityStack launchStack = getLaunchStack(null, aOptions, task, onTop);
+                    if (launchStack != null && stack != launchStack) {
+                        final int reparentMode = onTop
+                                ? REPARENT_MOVE_STACK_TO_FRONT : REPARENT_LEAVE_STACK_IN_PLACE;
+                        task.reparent(launchStack, onTop, reparentMode, ANIMATE, DEFER_RESUME,
+                                "anyTaskForIdLocked");
+                    }
+                }
+                return task;
             }
         }
 
@@ -749,7 +756,7 @@
         // Otherwise, check the recent tasks and return if we find it there and we are not restoring
         // the task from recents
         if (DEBUG_RECENTS) Slog.v(TAG_RECENTS, "Looking for task id=" + id + " in recents");
-        final TaskRecord task = mRecentTasks.taskForIdLocked(id);
+        final TaskRecord task = mRecentTasks.getTask(id);
 
         if (task == null) {
             if (DEBUG_RECENTS) {
@@ -764,7 +771,7 @@
         }
 
         // Implicitly, this case is MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE
-        if (!restoreRecentTaskLocked(task, aOptions)) {
+        if (!restoreRecentTaskLocked(task, aOptions, onTop)) {
             if (DEBUG_RECENTS) Slog.w(TAG_RECENTS,
                     "Couldn't restore task id=" + id + " found in recents");
             return null;
@@ -776,9 +783,10 @@
     ActivityRecord isInAnyStackLocked(IBinder token) {
         int numDisplays = mActivityDisplays.size();
         for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
-            ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
-            for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
-                final ActivityRecord r = stacks.get(stackNdx).isInStackLocked(token);
+            final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+            for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+                final ActivityStack stack = display.getChildAt(stackNdx);
+                final ActivityRecord r = stack.isInStackLocked(token);
                 if (r != null) {
                     return r;
                 }
@@ -816,18 +824,21 @@
     void lockAllProfileTasks(@UserIdInt int userId) {
         mWindowManager.deferSurfaceLayout();
         try {
-            final List<ActivityStack> stacks = getStacks();
-            for (int stackNdx = stacks.size() - 1; stackNdx >= 0; stackNdx--) {
-                final List<TaskRecord> tasks = stacks.get(stackNdx).getAllTasks();
-                for (int taskNdx = tasks.size() - 1; taskNdx >= 0; taskNdx--) {
-                    final TaskRecord task = tasks.get(taskNdx);
+            for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
+                final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+                for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+                    final ActivityStack stack = display.getChildAt(stackNdx);
+                    final List<TaskRecord> tasks = stack.getAllTasks();
+                    for (int taskNdx = tasks.size() - 1; taskNdx >= 0; taskNdx--) {
+                        final TaskRecord task = tasks.get(taskNdx);
 
-                    // Check the task for a top activity belonging to userId, or returning a result
-                    // to an activity belonging to userId. Example case: a document picker for
-                    // personal files, opened by a work app, should still get locked.
-                    if (taskTopActivityIsUser(task, userId)) {
-                        mService.mTaskChangeNotificationController.notifyTaskProfileLocked(
-                                task.taskId, userId);
+                        // Check the task for a top activity belonging to userId, or returning a
+                        // result to an activity belonging to userId. Example case: a document
+                        // picker for personal files, opened by a work app, should still get locked.
+                        if (taskTopActivityIsUser(task, userId)) {
+                            mService.mTaskChangeNotificationController.notifyTaskProfileLocked(
+                                    task.taskId, userId);
+                        }
                     }
                 }
             }
@@ -858,7 +869,7 @@
         // [u*MAX_TASK_IDS_PER_USER, (u+1)*MAX_TASK_IDS_PER_USER-1], so if MAX_TASK_IDS_PER_USER
         // was 10, user 0 could only have taskIds 0 to 9, user 1: 10 to 19, user 2: 20 to 29, so on.
         int candidateTaskId = nextTaskIdForUser(currentTaskId, userId);
-        while (mRecentTasks.taskIdTakenForUserLocked(candidateTaskId, userId)
+        while (mRecentTasks.containsTaskId(candidateTaskId, userId)
                 || anyTaskForIdLocked(
                         candidateTaskId, MATCH_TASK_IN_STACKS_OR_RECENT_TASKS) != null) {
             candidateTaskId = nextTaskIdForUser(candidateTaskId, userId);
@@ -893,9 +904,9 @@
         final String processName = app.processName;
         boolean didSomething = false;
         for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
-            ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
-            for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
-                final ActivityStack stack = stacks.get(stackNdx);
+            final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+            for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+                final ActivityStack stack = display.getChildAt(stackNdx);
                 if (!isFocusedStack(stack)) {
                     continue;
                 }
@@ -928,9 +939,9 @@
 
     boolean allResumedActivitiesIdle() {
         for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
-            ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
-            for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
-                final ActivityStack stack = stacks.get(stackNdx);
+            final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+            for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+                final ActivityStack stack = display.getChildAt(stackNdx);
                 if (!isFocusedStack(stack) || stack.numActivities() == 0) {
                     continue;
                 }
@@ -949,9 +960,9 @@
 
     boolean allResumedActivitiesComplete() {
         for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
-            ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
-            for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
-                final ActivityStack stack = stacks.get(stackNdx);
+            final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+            for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+                final ActivityStack stack = display.getChildAt(stackNdx);
                 if (isFocusedStack(stack)) {
                     final ActivityRecord r = stack.mResumedActivity;
                     if (r != null && r.state != RESUMED) {
@@ -968,12 +979,12 @@
         return true;
     }
 
-    boolean allResumedActivitiesVisible() {
+    private boolean allResumedActivitiesVisible() {
         boolean foundResumed = false;
         for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
-            ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
-            for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
-                final ActivityStack stack = stacks.get(stackNdx);
+            final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+            for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+                final ActivityStack stack = display.getChildAt(stackNdx);
                 final ActivityRecord r = stack.mResumedActivity;
                 if (r != null) {
                     if (!r.nowVisible || mActivitiesWaitingForVisibleActivity.contains(r)) {
@@ -997,9 +1008,9 @@
     boolean pauseBackStacks(boolean userLeaving, ActivityRecord resuming, boolean dontWait) {
         boolean someActivityPaused = false;
         for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
-            ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
-            for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
-                final ActivityStack stack = stacks.get(stackNdx);
+            final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+            for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+                final ActivityStack stack = display.getChildAt(stackNdx);
                 if (!isFocusedStack(stack) && stack.mResumedActivity != null) {
                     if (DEBUG_STATES) Slog.d(TAG_STATES, "pauseBackStacks: stack=" + stack +
                             " mResumedActivity=" + stack.mResumedActivity);
@@ -1014,9 +1025,9 @@
     boolean allPausedActivitiesComplete() {
         boolean pausing = true;
         for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
-            ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
-            for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
-                final ActivityStack stack = stacks.get(stackNdx);
+            final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+            for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+                final ActivityStack stack = display.getChildAt(stackNdx);
                 final ActivityRecord r = stack.mPausingActivity;
                 if (r != null && r.state != PAUSED && r.state != STOPPED && r.state != STOPPING) {
                     if (DEBUG_STATES) {
@@ -1034,9 +1045,10 @@
 
     void cancelInitializingActivities() {
         for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
-            ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
-            for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
-                stacks.get(stackNdx).cancelInitializingActivities();
+            final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+            for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+                final ActivityStack stack = display.getChildAt(stackNdx);
+                stack.cancelInitializingActivities();
             }
         }
     }
@@ -1134,13 +1146,10 @@
 
         for (int i = mTmpOrderedDisplayIds.size() - 1; i >= 0; --i) {
             final int displayId = mTmpOrderedDisplayIds.get(i);
-            final List<ActivityStack> stacks = mActivityDisplays.get(displayId).mStacks;
-            if (stacks == null) {
-                continue;
-            }
-            for (int j = stacks.size() - 1; j >= 0; --j) {
-                final ActivityStack stack = stacks.get(j);
-                if (stack != focusedStack && isFrontStackOnDisplay(stack) && stack.isFocusable()) {
+            final ActivityDisplay display = mActivityDisplays.get(displayId);
+            for (int j = display.getChildCount() - 1; j >= 0; --j) {
+                final ActivityStack stack = display.getChildAt(j);
+                if (stack != focusedStack && stack.isTopStackOnDisplay() && stack.isFocusable()) {
                     r = stack.topRunningActivityLocked();
                     if (r != null) {
                         return r;
@@ -1156,9 +1165,9 @@
         ArrayList<ArrayList<RunningTaskInfo>> runningTaskLists = new ArrayList<>();
         final int numDisplays = mActivityDisplays.size();
         for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
-            ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
-            for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
-                final ActivityStack stack = stacks.get(stackNdx);
+            final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+            for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+                final ActivityStack stack = display.getChildAt(stackNdx);
                 ArrayList<RunningTaskInfo> stackTaskList = new ArrayList<>();
                 runningTaskLists.add(stackTaskList);
                 stack.getTasksLocked(stackTaskList, callingUid, allowed);
@@ -1607,6 +1616,16 @@
                 Slog.w(TAG, msg);
                 throw new SecurityException(msg);
             }
+            // Check if someone tries to launch an unwhitelisted activity into LockTask mode.
+            final boolean lockTaskMode = options.getLockTaskMode();
+            if (lockTaskMode && !mService.mLockTaskController.isPackageWhitelisted(
+                    UserHandle.getUserId(callingUid), aInfo.packageName)) {
+                final String msg = "Permission Denial: starting " + intent.toString()
+                        + " from " + callerApp + " (pid=" + callingPid
+                        + ", uid=" + callingUid + ") with lockTaskMode=true";
+                Slog.w(TAG, msg);
+                throw new SecurityException(msg);
+            }
         }
 
         return true;
@@ -1928,9 +1947,10 @@
     boolean handleAppDiedLocked(ProcessRecord app) {
         boolean hasVisibleActivities = false;
         for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
-            final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
-            for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
-                hasVisibleActivities |= stacks.get(stackNdx).handleAppDiedLocked(app);
+            final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+            for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+                final ActivityStack stack = display.getChildAt(stackNdx);
+                hasVisibleActivities |= stack.handleAppDiedLocked(app);
             }
         }
         return hasVisibleActivities;
@@ -1938,9 +1958,10 @@
 
     void closeSystemDialogsLocked() {
         for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
-            final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
-            for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
-                stacks.get(stackNdx).closeSystemDialogsLocked();
+            final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+            for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+                final ActivityStack stack = display.getChildAt(stackNdx);
+                stack.closeSystemDialogsLocked();
             }
         }
     }
@@ -1966,9 +1987,9 @@
             boolean doit, boolean evenPersistent, int userId) {
         boolean didSomething = false;
         for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
-            final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
-            for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
-                final ActivityStack stack = stacks.get(stackNdx);
+            final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+            for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+                final ActivityStack stack = display.getChildAt(stackNdx);
                 if (stack.finishDisabledPackageActivitiesLocked(
                         packageName, filterByClasses, doit, evenPersistent, userId)) {
                     didSomething = true;
@@ -1988,9 +2009,9 @@
         // hosted by the process that is actually still the foreground.
         ProcessRecord fgApp = null;
         for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
-            final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
-            for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
-                final ActivityStack stack = stacks.get(stackNdx);
+            final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+            for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+                final ActivityStack stack = display.getChildAt(stackNdx);
                 if (isFocusedStack(stack)) {
                     if (stack.mResumedActivity != null) {
                         fgApp = stack.mResumedActivity.app;
@@ -2040,9 +2061,10 @@
 
     void updateActivityApplicationInfoLocked(ApplicationInfo aInfo) {
         for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
-            final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
-            for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
-                stacks.get(stackNdx).updateActivityApplicationInfoLocked(aInfo);
+            final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+            for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+                final ActivityStack stack = display.getChildAt(stackNdx);
+                stack.updateActivityApplicationInfoLocked(aInfo);
             }
         }
     }
@@ -2051,10 +2073,10 @@
         TaskRecord finishedTask = null;
         ActivityStack focusedStack = getFocusedStack();
         for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
-            final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
-            final int numStacks = stacks.size();
+            final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+            final int numStacks = display.getChildCount();
             for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) {
-                final ActivityStack stack = stacks.get(stackNdx);
+                final ActivityStack stack = display.getChildAt(stackNdx);
                 TaskRecord t = stack.finishTopRunningActivityLocked(app, reason);
                 if (stack == focusedStack || finishedTask == null) {
                     finishedTask = t;
@@ -2066,10 +2088,10 @@
 
     void finishVoiceTask(IVoiceInteractionSession session) {
         for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
-            final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
-            final int numStacks = stacks.size();
+            final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+            final int numStacks = display.getChildCount();
             for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) {
-                final ActivityStack stack = stacks.get(stackNdx);
+                final ActivityStack stack = display.getChildAt(stackNdx);
                 stack.finishVoiceTask(session);
             }
         }
@@ -2105,8 +2127,8 @@
                 // moveTaskToStackUncheckedLocked() should already placed the task on top,
                 // still need moveTaskToFrontLocked() below for any transition settings.
             }
-            if (StackId.resizeStackWithLaunchBounds(stack.mStackId)) {
-                resizeStackLocked(stack.mStackId, bounds, null /* tempTaskBounds */,
+            if (stack.resizeStackWithLaunchBounds()) {
+                resizeStackLocked(stack, bounds, null /* tempTaskBounds */,
                         null /* tempTaskInsetBounds */, !PRESERVE_WINDOWS,
                         true /* allowResizeInDockedMode */, !DEFER_RESUME);
             } else {
@@ -2125,7 +2147,7 @@
                 "findTaskToMoveToFront: moved to front of stack=" + currentStack);
 
         handleNonResizableTaskIfNeeded(task, WINDOWING_MODE_UNDEFINED, DEFAULT_DISPLAY,
-                currentStack.mStackId, forceNonResizeable);
+                currentStack, forceNonResizeable);
     }
 
     boolean canUseActivityOptionsLaunchBounds(ActivityOptions options) {
@@ -2139,6 +2161,10 @@
                 || mService.mSupportsFreeformWindowManagement;
     }
 
+    LaunchingTaskPositioner getLaunchingTaskPositioner() {
+        return mTaskPositioner;
+    }
+
     protected <T extends ActivityStack> T getStack(int stackId) {
         for (int i = mActivityDisplays.size() - 1; i >= 0; --i) {
             final T stack = mActivityDisplays.valueAt(i).getStack(stackId);
@@ -2234,11 +2260,13 @@
             }
         }
 
-        if (isWindowingModeSupported(windowingMode, supportsMultiWindow, supportsSplitScreen,
+        if (windowingMode != WINDOWING_MODE_UNDEFINED
+                && isWindowingModeSupported(windowingMode, supportsMultiWindow, supportsSplitScreen,
                 supportsFreeform, supportsPip, activityType)) {
             return windowingMode;
         }
-        return WINDOWING_MODE_FULLSCREEN;
+        // Return root/systems windowing mode
+        return getWindowingMode();
     }
 
     int resolveActivityType(@Nullable ActivityRecord r, @Nullable ActivityOptions options,
@@ -2252,7 +2280,10 @@
         if (activityType != ACTIVITY_TYPE_UNDEFINED) {
             return activityType;
         }
-        return options != null ? options.getLaunchActivityType() : ACTIVITY_TYPE_UNDEFINED;
+        if (options != null) {
+            activityType = options.getLaunchActivityType();
+        }
+        return activityType != ACTIVITY_TYPE_UNDEFINED ? activityType : ACTIVITY_TYPE_STANDARD;
     }
 
     <T extends ActivityStack> T getLaunchStack(@Nullable ActivityRecord r,
@@ -2290,7 +2321,7 @@
             // Temporarily set the task id to invalid in case in re-entry.
             options.setLaunchTaskId(INVALID_TASK_ID);
             final TaskRecord task = anyTaskForIdLocked(taskId,
-                    MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE, options);
+                    MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE, options, onTop);
             options.setLaunchTaskId(taskId);
             if (task != null) {
                 return task.getStack();
@@ -2316,13 +2347,10 @@
             }
             final ActivityDisplay display = getActivityDisplayOrCreateLocked(displayId);
             if (display != null) {
-                for (int i = display.mStacks.size() - 1; i >= 0; --i) {
-                    stack = (T) display.mStacks.get(i);
-                    if (stack.isCompatible(windowingMode, activityType)) {
-                        return stack;
-                    }
+                stack = display.getOrCreateStack(windowingMode, activityType, onTop);
+                if (stack != null) {
+                    return stack;
                 }
-                // TODO: We should create the stack we want on the display at this point.
             }
         }
 
@@ -2351,18 +2379,6 @@
             display = getDefaultDisplay();
         }
 
-        stack = display.getOrCreateStack(windowingMode, activityType, onTop);
-        if (stack != null) {
-            return stack;
-        }
-
-        // Whatever...return some default for now.
-        if (candidateTask != null && candidateTask.mBounds != null
-                && mService.mSupportsFreeformWindowManagement) {
-            windowingMode = WINDOWING_MODE_FREEFORM;
-        } else {
-            windowingMode = WINDOWING_MODE_FULLSCREEN;
-        }
         return display.getOrCreateStack(windowingMode, activityType, onTop);
     }
 
@@ -2385,8 +2401,8 @@
         }
 
         // Return the topmost valid stack on the display.
-        for (int i = activityDisplay.mStacks.size() - 1; i >= 0; --i) {
-            final ActivityStack stack = activityDisplay.mStacks.get(i);
+        for (int i = activityDisplay.getChildCount() - 1; i >= 0; --i) {
+            final ActivityStack stack = activityDisplay.getChildAt(i);
             if (isValidLaunchStack(stack, displayId, r)) {
                 return stack;
             }
@@ -2417,25 +2433,13 @@
             case WINDOWING_MODE_SPLIT_SCREEN_SECONDARY: return r.supportsSplitScreenWindowingMode();
         }
 
-        if (StackId.isDynamicStack(stack.mStackId)) {
+        if (!stack.isOnHomeDisplay()) {
             return r.canBeLaunchedOnDisplay(displayId);
         }
         Slog.e(TAG, "isValidLaunchStack: Unexpected stack=" + stack);
         return false;
     }
 
-    ArrayList<ActivityStack> getStacks() {
-        ArrayList<ActivityStack> allStacks = new ArrayList<>();
-        for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
-            allStacks.addAll(mActivityDisplays.valueAt(displayNdx).mStacks);
-        }
-        return allStacks;
-    }
-
-    ArrayList<ActivityStack> getStacksOnDefaultDisplay() {
-        return mActivityDisplays.valueAt(DEFAULT_DISPLAY).mStacks;
-    }
-
     /**
      * Get next focusable stack in the system. This will search across displays and stacks
      * in last-focused order for a focusable and visible stack, different from the target stack.
@@ -2450,10 +2454,9 @@
         for (int i = mTmpOrderedDisplayIds.size() - 1; i >= 0; --i) {
             final int displayId = mTmpOrderedDisplayIds.get(i);
             // If a display is registered in WM, it must also be available in AM.
-            @SuppressWarnings("ConstantConditions")
-            final List<ActivityStack> stacks = getActivityDisplayOrCreateLocked(displayId).mStacks;
-            for (int j = stacks.size() - 1; j >= 0; --j) {
-                final ActivityStack stack = stacks.get(j);
+            final ActivityDisplay display = getActivityDisplayOrCreateLocked(displayId);
+            for (int j = display.getChildCount() - 1; j >= 0; --j) {
+                final ActivityStack stack = display.getChildAt(j);
                 if (stack != currentFocus && stack.isFocusable()
                         && stack.shouldBeVisible(null)) {
                     return stack;
@@ -2511,20 +2514,17 @@
         return null;
     }
 
-    void resizeStackLocked(int stackId, Rect bounds, Rect tempTaskBounds, Rect tempTaskInsetBounds,
-            boolean preserveWindows, boolean allowResizeInDockedMode, boolean deferResume) {
-        if (stackId == DOCKED_STACK_ID) {
+    void resizeStackLocked(ActivityStack stack, Rect bounds, Rect tempTaskBounds,
+            Rect tempTaskInsetBounds, boolean preserveWindows, boolean allowResizeInDockedMode,
+            boolean deferResume) {
+
+        if (stack.inSplitScreenPrimaryWindowingMode()) {
             resizeDockedStackLocked(bounds, tempTaskBounds, tempTaskInsetBounds, null, null,
                     preserveWindows, deferResume);
             return;
         }
-        final ActivityStack stack = getStack(stackId);
-        if (stack == null) {
-            Slog.w(TAG, "resizeStack: stackId " + stackId + " not found.");
-            return;
-        }
 
-        final boolean splitScreenActive = getDefaultDisplay().hasSplitScreenStack();
+        final boolean splitScreenActive = getDefaultDisplay().hasSplitScreenPrimaryStack();
         if (!allowResizeInDockedMode
                 && !stack.getWindowConfiguration().tasksAreFloating() && splitScreenActive) {
             // If the docked stack exists, don't resize non-floating stacks independently of the
@@ -2532,7 +2532,7 @@
             return;
         }
 
-        Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "am.resizeStack_" + stackId);
+        Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "am.resizeStack_" + stack.mStackId);
         mWindowManager.deferSurfaceLayout();
         try {
             if (stack.supportsSplitScreenWindowingMode()) {
@@ -2584,8 +2584,7 @@
 
     /**
      * TODO: This should just change the windowing mode and resize vs. actually moving task around.
-     * Can do that once we are no longer using static stack ids. Specially when
-     * {@link ActivityManager.StackId#FULLSCREEN_WORKSPACE_STACK_ID} is removed.
+     * Can do that once we are no longer using static stack ids.
      */
     private void moveTasksToFullscreenStackInSurfaceTransaction(ActivityStack fromStack,
             int toDisplayId, boolean onTop) {
@@ -2600,13 +2599,12 @@
                 // We are moving all tasks from the docked stack to the fullscreen stack,
                 // which is dismissing the docked stack, so resize all other stacks to
                 // fullscreen here already so we don't end up with resize trashing.
-                final ArrayList<ActivityStack> displayStacks = toDisplay.mStacks;
-                for (int i = displayStacks.size() - 1; i >= 0; --i) {
-                    final ActivityStack otherStack = displayStacks.get(i);
+                for (int i = toDisplay.getChildCount() - 1; i >= 0; --i) {
+                    final ActivityStack otherStack = toDisplay.getChildAt(i);
                     if (!otherStack.inSplitScreenSecondaryWindowingMode()) {
                         continue;
                     }
-                    resizeStackLocked(otherStack.mStackId, null, null, null, PRESERVE_WINDOWS,
+                    resizeStackLocked(otherStack, null, null, null, PRESERVE_WINDOWS,
                             true /* allowResizeInDockedMode */, DEFER_RESUME);
                 }
 
@@ -2697,8 +2695,7 @@
             return;
         }
 
-        final ActivityStack stack = getDefaultDisplay().getStack(
-                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_UNDEFINED);
+        final ActivityStack stack = getDefaultDisplay().getSplitScreenPrimaryStack();
         if (stack == null) {
             Slog.w(TAG, "resizeDockedStackLocked: docked stack not found");
             return;
@@ -2727,10 +2724,10 @@
                 // static stacks need to be adjusted so they don't overlap with the docked stack.
                 // We get the bounds to use from window manager which has been adjusted for any
                 // screen controls and is also the same for all stacks.
-                final ArrayList<ActivityStack> stacks = getStacksOnDefaultDisplay();
+                final ActivityDisplay display = getDefaultDisplay();
                 final Rect otherTaskRect = new Rect();
-                for (int i = stacks.size() - 1; i >= 0; --i) {
-                    final ActivityStack current = stacks.get(i);
+                for (int i = display.getChildCount() - 1; i >= 0; --i) {
+                    final ActivityStack current = display.getChildAt(i);
                     if (current.getWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
                         continue;
                     }
@@ -2744,7 +2741,7 @@
                             tempRect /* outStackBounds */,
                             otherTaskRect /* outTempTaskBounds */, true /* ignoreVisibility */);
 
-                    resizeStackLocked(current.mStackId, !tempRect.isEmpty() ? tempRect : null,
+                    resizeStackLocked(current, !tempRect.isEmpty() ? tempRect : null,
                             !otherTaskRect.isEmpty() ? otherTaskRect : tempOtherTaskBounds,
                             tempOtherTaskInsetBounds, preserveWindows,
                             true /* allowResizeInDockedMode */, deferResume);
@@ -2762,8 +2759,7 @@
 
     void resizePinnedStackLocked(Rect pinnedBounds, Rect tempPinnedTaskBounds) {
         // TODO(multi-display): Pinned stack display should be passed in.
-        final PinnedActivityStack stack = getDefaultDisplay().getStack(
-                WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED);
+        final PinnedActivityStack stack = getDefaultDisplay().getPinnedStack();
         if (stack == null) {
             Slog.w(TAG, "resizePinnedStackLocked: pinned stack not found");
             return;
@@ -2801,12 +2797,7 @@
         }
     }
 
-    private void removeStackInSurfaceTransaction(int stackId) {
-        final ActivityStack stack = getStack(stackId);
-        if (stack == null) {
-            return;
-        }
-
+    private void removeStackInSurfaceTransaction(ActivityStack stack) {
         final ArrayList<TaskRecord> tasks = stack.getAllTasks();
         if (stack.getWindowingMode() == WINDOWING_MODE_PINNED) {
             /**
@@ -2836,12 +2827,12 @@
     }
 
     /**
-     * Removes the stack associated with the given {@param stackId}.  If the {@param stackId} is the
+     * Removes the stack associated with the given {@param stack}. If the {@param stack} is the
      * pinned stack, then its tasks are not explicitly removed when the stack is destroyed, but
      * instead moved back onto the fullscreen stack.
      */
-    void removeStackLocked(int stackId) {
-        mWindowManager.inSurfaceTransaction(() -> removeStackInSurfaceTransaction(stackId));
+    void removeStack(ActivityStack stack) {
+        mWindowManager.inSurfaceTransaction(() -> removeStackInSurfaceTransaction(stack));
     }
 
     /**
@@ -2892,23 +2883,9 @@
         return false;
     }
 
-    void addRecentActivity(ActivityRecord r) {
-        if (r == null) {
-            return;
-        }
-        final TaskRecord task = r.getTask();
-        mRecentTasks.addLocked(task);
-        task.touchActiveTime();
-    }
-
-    void removeTaskFromRecents(TaskRecord task) {
-        mRecentTasks.remove(task);
-        task.removedFromRecents();
-    }
-
     void cleanUpRemovedTaskLocked(TaskRecord tr, boolean killProcess, boolean removeFromRecents) {
         if (removeFromRecents) {
-            removeTaskFromRecents(tr);
+            mRecentTasks.remove(tr);
         }
         ComponentName component = tr.getBaseIntent().getComponent();
         if (component == null) {
@@ -2979,8 +2956,7 @@
 
     int getNextStackId() {
         while (true) {
-            if (mNextFreeStackId >= FIRST_DYNAMIC_STACK_ID
-                    && getStack(mNextFreeStackId) == null) {
+            if (getStack(mNextFreeStackId) == null) {
                 break;
             }
             mNextFreeStackId++;
@@ -2989,13 +2965,15 @@
     }
 
     /**
-     * Restores a recent task to a stack
+     * Called to restore the state of the task into the stack that it's supposed to go into.
+     *
      * @param task The recent task to be restored.
      * @param aOptions The activity options to use for restoration.
+     * @param onTop If the stack for the task should be the topmost on the display.
      * @return true if the task has been restored successfully.
      */
-    boolean restoreRecentTaskLocked(TaskRecord task, ActivityOptions aOptions) {
-        final ActivityStack stack = getLaunchStack(null, aOptions, task, !ON_TOP);
+    boolean restoreRecentTaskLocked(TaskRecord task, ActivityOptions aOptions, boolean onTop) {
+        final ActivityStack stack = getLaunchStack(null, aOptions, task, onTop);
         final ActivityStack currentStack = task.getStack();
         if (currentStack != null) {
             // Task has already been restored once. See if we need to do anything more
@@ -3008,9 +2986,9 @@
             currentStack.removeTask(task, "restoreRecentTaskLocked", REMOVE_TASK_MODE_MOVING);
         }
 
-        stack.addTask(task, !ON_TOP, "restoreRecentTask");
+        stack.addTask(task, onTop, "restoreRecentTask");
         // TODO: move call for creation here and other place into Stack.addTask()
-        task.createWindowContainer(!ON_TOP, true /* showForAllUsers */);
+        task.createWindowContainer(onTop, true /* showForAllUsers */);
         if (DEBUG_RECENTS) Slog.v(TAG_RECENTS,
                 "Added restored task=" + task + " to stack=" + stack);
         final ArrayList<ActivityRecord> activities = task.mActivities;
@@ -3020,6 +2998,17 @@
         return true;
     }
 
+    @Override
+    public void onRecentTaskAdded(TaskRecord task) {
+        task.touchActiveTime();
+    }
+
+    @Override
+    public void onRecentTaskRemoved(TaskRecord task, boolean wasTrimmed) {
+        // TODO: Trim active task once b/68045330 is fixed
+        task.removedFromRecents();
+    }
+
     /**
      * Move stack with all its existing content to specified display.
      * @param stackId Id of stack to move.
@@ -3160,7 +3149,7 @@
             // Resize the pinned stack to match the current size of the task the activity we are
             // going to be moving is currently contained in. We do this to have the right starting
             // animation bounds for the pinned stack to the desired bounds the caller wants.
-            resizeStackLocked(PINNED_STACK_ID, task.mBounds, null /* tempTaskBounds */,
+            resizeStackLocked(stack, task.mBounds, null /* tempTaskBounds */,
                     null /* tempTaskInsetBounds */, !PRESERVE_WINDOWS,
                     true /* allowResizeInDockedMode */, !DEFER_RESUME);
 
@@ -3257,9 +3246,9 @@
         ActivityRecord affinityMatch = null;
         if (DEBUG_TASKS) Slog.d(TAG_TASKS, "Looking for task of " + r);
         for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
-            final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
-            for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
-                final ActivityStack stack = stacks.get(stackNdx);
+            final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+            for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+                final ActivityStack stack = display.getChildAt(stackNdx);
                 if (!r.hasCompatibleActivityType(stack)) {
                     if (DEBUG_TASKS) Slog.d(TAG_TASKS, "Skipping stack: (mismatch activity/stack) "
                             + stack);
@@ -3292,12 +3281,13 @@
     }
 
     ActivityRecord findActivityLocked(Intent intent, ActivityInfo info,
-                                      boolean compareIntentFilters) {
+            boolean compareIntentFilters) {
         for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
-            final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
-            for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
-                final ActivityRecord ar = stacks.get(stackNdx)
-                        .findActivityLocked(intent, info, compareIntentFilters);
+            final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+            for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+                final ActivityStack stack = display.getChildAt(stackNdx);
+                final ActivityRecord ar = stack.findActivityLocked(
+                        intent, info, compareIntentFilters);
                 if (ar != null) {
                     return ar;
                 }
@@ -3391,9 +3381,8 @@
             }
 
             // Set the sleeping state of the stacks on the display.
-            final ArrayList<ActivityStack> stacks = display.mStacks;
-            for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
-                final ActivityStack stack = stacks.get(stackNdx);
+            for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+                final ActivityStack stack = display.getChildAt(stackNdx);
                 if (displayShouldSleep) {
                     stack.goToSleepIfPossible(false /* shuttingDown */);
                 } else {
@@ -3455,12 +3444,13 @@
     private boolean putStacksToSleepLocked(boolean allowDelay, boolean shuttingDown) {
         boolean allSleep = true;
         for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
-            final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
-            for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
+            final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+            for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+                final ActivityStack stack = display.getChildAt(stackNdx);
                 if (allowDelay) {
-                    allSleep &= stacks.get(stackNdx).goToSleepIfPossible(shuttingDown);
+                    allSleep &= stack.goToSleepIfPossible(shuttingDown);
                 } else {
-                    stacks.get(stackNdx).goToSleep();
+                    stack.goToSleep();
                 }
             }
         }
@@ -3485,11 +3475,10 @@
 
     void handleAppCrashLocked(ProcessRecord app) {
         for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
-            final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
-            int stackNdx = stacks.size() - 1;
-            while (stackNdx >= 0) {
-                stacks.get(stackNdx).handleAppCrashLocked(app);
-                stackNdx--;
+            final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+            for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+                final ActivityStack stack = display.getChildAt(stackNdx);
+                stack.handleAppCrashLocked(app);
             }
         }
     }
@@ -3500,7 +3489,7 @@
         final ActivityStack stack = task.getStack();
 
         r.mLaunchTaskBehind = false;
-        mRecentTasks.addLocked(task);
+        mRecentTasks.add(task);
         mService.mTaskChangeNotificationController.notifyTaskStackChanged();
         r.setVisibility(false);
 
@@ -3522,10 +3511,9 @@
         try {
             // First the front stacks. In case any are not fullscreen and are in front of home.
             for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
-                final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
-                final int topStackNdx = stacks.size() - 1;
-                for (int stackNdx = topStackNdx; stackNdx >= 0; --stackNdx) {
-                    final ActivityStack stack = stacks.get(stackNdx);
+                final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+                for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+                    final ActivityStack stack = display.getChildAt(stackNdx);
                     stack.ensureActivitiesVisibleLocked(starting, configChanges, preserveWindows);
                 }
             }
@@ -3536,10 +3524,9 @@
 
     void addStartingWindowsForVisibleActivities(boolean taskSwitch) {
         for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
-            final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
-            final int topStackNdx = stacks.size() - 1;
-            for (int stackNdx = topStackNdx; stackNdx >= 0; --stackNdx) {
-                final ActivityStack stack = stacks.get(stackNdx);
+            final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+            for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+                final ActivityStack stack = display.getChildAt(stackNdx);
                 stack.addStartingWindowsForVisibleActivities(taskSwitch);
             }
         }
@@ -3555,20 +3542,20 @@
         }
         mTaskLayersChanged = false;
         for (int displayNdx = 0; displayNdx < mActivityDisplays.size(); displayNdx++) {
-            final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
+            final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
             int baseLayer = 0;
-            for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
-                baseLayer += stacks.get(stackNdx).rankTaskLayers(baseLayer);
+            for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+                final ActivityStack stack = display.getChildAt(stackNdx);
+                baseLayer += stack.rankTaskLayers(baseLayer);
             }
         }
     }
 
     void clearOtherAppTimeTrackers(AppTimeTracker except) {
         for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
-            final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
-            final int topStackNdx = stacks.size() - 1;
-            for (int stackNdx = topStackNdx; stackNdx >= 0; --stackNdx) {
-                final ActivityStack stack = stacks.get(stackNdx);
+            final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+            for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+                final ActivityStack stack = display.getChildAt(stackNdx);
                 stack.clearOtherAppTimeTrackers(except);
             }
         }
@@ -3576,10 +3563,9 @@
 
     void scheduleDestroyAllActivities(ProcessRecord app, String reason) {
         for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
-            final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
-            final int numStacks = stacks.size();
-            for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) {
-                final ActivityStack stack = stacks.get(stackNdx);
+            final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+            for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+                final ActivityStack stack = display.getChildAt(stackNdx);
                 stack.scheduleDestroyActivities(app, reason);
             }
         }
@@ -3631,10 +3617,11 @@
         // let's iterate through the tasks and release the oldest one.
         final int numDisplays = mActivityDisplays.size();
         for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
-            final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
+            final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+            final int stackCount = display.getChildCount();
             // Step through all stacks starting from behind, to hit the oldest things first.
-            for (int stackNdx = 0; stackNdx < stacks.size(); stackNdx++) {
-                final ActivityStack stack = stacks.get(stackNdx);
+            for (int stackNdx = 0; stackNdx < stackCount; stackNdx++) {
+                final ActivityStack stack = display.getChildAt(stackNdx);
                 // Try to release activities in this stack; if we manage to, we are done.
                 if (stack.releaseSomeActivitiesLocked(app, tasks, reason) > 0) {
                     return;
@@ -3646,14 +3633,14 @@
     boolean switchUserLocked(int userId, UserState uss) {
         final int focusStackId = mFocusedStack.getStackId();
         // We dismiss the docked stack whenever we switch users.
-        final ActivityStack dockedStack = getDefaultDisplay().getSplitScreenStack();
+        final ActivityStack dockedStack = getDefaultDisplay().getSplitScreenPrimaryStack();
         if (dockedStack != null) {
             moveTasksToFullscreenStackLocked(dockedStack, mFocusedStack == dockedStack);
         }
         // Also dismiss the pinned stack whenever we switch users. Removing the pinned stack will
         // also cause all tasks to be moved to the fullscreen stack at a position that is
         // appropriate.
-        removeStackLocked(PINNED_STACK_ID);
+        removeStacksInWindowingModes(WINDOWING_MODE_PINNED);
 
         mUserStackInFront.put(mCurrentUser, focusStackId);
         final int restoreStackId = mUserStackInFront.get(userId, mHomeStack.mStackId);
@@ -3661,9 +3648,9 @@
 
         mStartingUsers.add(uss);
         for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
-            final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
-            for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
-                final ActivityStack stack = stacks.get(stackNdx);
+            final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+            for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+                final ActivityStack stack = display.getChildAt(stackNdx);
                 stack.switchUserLocked(userId);
                 TaskRecord task = stack.topTask();
                 if (task != null) {
@@ -3761,9 +3748,9 @@
 
     void validateTopActivitiesLocked() {
         for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
-            final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
-            for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
-                final ActivityStack stack = stacks.get(stackNdx);
+            final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+            for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+                final ActivityStack stack = display.getChildAt(stackNdx);
                 final ActivityRecord r = stack.topRunningActivityLocked();
                 final ActivityState state = r == null ? DESTROYED : r.state;
                 if (isFocusedStack(stack)) {
@@ -3798,7 +3785,7 @@
         pw.print(prefix); pw.println("mUserStackInFront=" + mUserStackInFront);
         for (int i = mActivityDisplays.size() - 1; i >= 0; --i) {
             final ActivityDisplay display = mActivityDisplays.valueAt(i);
-            pw.println(prefix + "displayId=" + display.mDisplayId + " mStacks=" + display.mStacks);
+            display.dump(pw, prefix);
         }
         if (!mWaitingForActivityVisible.isEmpty()) {
             pw.print(prefix); pw.println("mWaitingForActivityVisible=");
@@ -3811,8 +3798,7 @@
         mService.mLockTaskController.dump(pw, prefix);
     }
 
-    public void writeToProto(ProtoOutputStream proto, long fieldId) {
-        final long token = proto.start(fieldId);
+    public void writeToProto(ProtoOutputStream proto) {
         super.writeToProto(proto, CONFIGURATION_CONTAINER);
         for (int displayNdx = 0; displayNdx < mActivityDisplays.size(); ++displayNdx) {
             ActivityDisplay activityDisplay = mActivityDisplays.valueAt(displayNdx);
@@ -3828,7 +3814,6 @@
         } else {
             proto.write(FOCUSED_STACK_ID, INVALID_STACK_ID);
         }
-        proto.end(token);
     }
 
     /**
@@ -3857,9 +3842,9 @@
             ArrayList<ActivityRecord> activities = new ArrayList<>();
             int numDisplays = mActivityDisplays.size();
             for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
-                ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
-                for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
-                    ActivityStack stack = stacks.get(stackNdx);
+                final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+                for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+                    final ActivityStack stack = display.getChildAt(stackNdx);
                     if (!dumpVisibleStacksOnly || stack.shouldBeVisible(null)) {
                         activities.addAll(stack.getDumpActivitiesLocked(name));
                     }
@@ -3892,11 +3877,13 @@
             ActivityDisplay activityDisplay = mActivityDisplays.valueAt(displayNdx);
             pw.print("Display #"); pw.print(activityDisplay.mDisplayId);
                     pw.println(" (activities from top to bottom):");
-            ArrayList<ActivityStack> stacks = activityDisplay.mStacks;
-            for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
-                final ActivityStack stack = stacks.get(stackNdx);
+            final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+            for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+                final ActivityStack stack = display.getChildAt(stackNdx);
                 pw.println();
-                pw.println("  Stack #" + stack.mStackId + ":");
+                pw.println("  Stack #" + stack.mStackId
+                        + ": type=" + activityTypeToString(stack.getActivityType())
+                        + " mode=" + windowingModeToString(stack.getWindowingMode()));
                 pw.println("  mFullscreen=" + stack.mFullscreen);
                 pw.println("  isSleeping=" + stack.shouldSleepActivities());
                 pw.println("  mBounds=" + stack.mBounds);
@@ -4140,30 +4127,29 @@
         }
 
         synchronized (mService) {
-            ActivityDisplay activityDisplay = mActivityDisplays.get(displayId);
-            if (activityDisplay != null) {
-                final boolean destroyContentOnRemoval
-                        = activityDisplay.shouldDestroyContentOnRemove();
-                final ArrayList<ActivityStack> stacks = activityDisplay.mStacks;
-                while (!stacks.isEmpty()) {
-                    final ActivityStack stack = stacks.get(0);
-                    if (destroyContentOnRemoval) {
-                        moveStackToDisplayLocked(stack.mStackId, DEFAULT_DISPLAY,
-                                false /* onTop */);
-                        stack.finishAllActivitiesLocked(true /* immediately */);
-                    } else {
-                        // Moving all tasks to fullscreen stack, because it's guaranteed to be
-                        // a valid launch stack for all activities. This way the task history from
-                        // external display will be preserved on primary after move.
-                        moveTasksToFullscreenStackLocked(stack, true /* onTop */);
-                    }
-                }
-
-                releaseSleepTokens(activityDisplay);
-
-                mActivityDisplays.remove(displayId);
-                mWindowManager.onDisplayRemoved(displayId);
+            final ActivityDisplay activityDisplay = mActivityDisplays.get(displayId);
+            if (activityDisplay == null) {
+                return;
             }
+            final boolean destroyContentOnRemoval
+                    = activityDisplay.shouldDestroyContentOnRemove();
+            while (activityDisplay.getChildCount() > 0) {
+                final ActivityStack stack = activityDisplay.getChildAt(0);
+                if (destroyContentOnRemoval) {
+                    moveStackToDisplayLocked(stack.mStackId, DEFAULT_DISPLAY, false /* onTop */);
+                    stack.finishAllActivitiesLocked(true /* immediately */);
+                } else {
+                    // Moving all tasks to fullscreen stack, because it's guaranteed to be
+                    // a valid launch stack for all activities. This way the task history from
+                    // external display will be preserved on primary after move.
+                    moveTasksToFullscreenStackLocked(stack, true /* onTop */);
+                }
+            }
+
+            releaseSleepTokens(activityDisplay);
+
+            mActivityDisplays.remove(displayId);
+            mWindowManager.onDisplayRemoved(displayId);
         }
     }
 
@@ -4235,7 +4221,7 @@
         info.userId = stack.mCurrentUser;
         info.visible = stack.shouldBeVisible(null);
         // A stack might be not attached to a display.
-        info.position = display != null ? display.mStacks.indexOf(stack) : 0;
+        info.position = display != null ? display.getIndexOf(stack) : 0;
         info.configuration.setTo(stack.getConfiguration());
 
         ArrayList<TaskRecord> tasks = stack.getAllTasks();
@@ -4281,25 +4267,25 @@
     ArrayList<StackInfo> getAllStackInfosLocked() {
         ArrayList<StackInfo> list = new ArrayList<>();
         for (int displayNdx = 0; displayNdx < mActivityDisplays.size(); ++displayNdx) {
-            ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
-            for (int ndx = stacks.size() - 1; ndx >= 0; --ndx) {
-                list.add(getStackInfo(stacks.get(ndx)));
+            final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+            for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+                final ActivityStack stack = display.getChildAt(stackNdx);
+                list.add(getStackInfo(stack));
             }
         }
         return list;
     }
 
     void handleNonResizableTaskIfNeeded(TaskRecord task, int preferredWindowingMode,
-            int preferredDisplayId, int actualStackId) {
+            int preferredDisplayId, ActivityStack actualStack) {
         handleNonResizableTaskIfNeeded(task, preferredWindowingMode, preferredDisplayId,
-                actualStackId, false /* forceNonResizable */);
+                actualStack, false /* forceNonResizable */);
     }
 
     void handleNonResizableTaskIfNeeded(TaskRecord task, int preferredWindowingMode,
-            int preferredDisplayId, int actualStackId, boolean forceNonResizable) {
+            int preferredDisplayId, ActivityStack actualStack, boolean forceNonResizable) {
         final boolean isSecondaryDisplayPreferred =
                 (preferredDisplayId != DEFAULT_DISPLAY && preferredDisplayId != INVALID_DISPLAY);
-        final ActivityStack actualStack = getStack(actualStackId);
         final boolean inSplitScreenMode = actualStack != null
                 && actualStack.inSplitScreenWindowingMode();
         if (((!inSplitScreenMode && preferredWindowingMode != WINDOWING_MODE_SPLIT_SCREEN_PRIMARY)
@@ -4315,8 +4301,8 @@
                 // The task landed on an inappropriate display somehow, move it to the default
                 // display.
                 // TODO(multi-display): Find proper stack for the task on the default display.
-                mService.moveTaskToStack(task.taskId, FULLSCREEN_WORKSPACE_STACK_ID,
-                        true /* toTop */);
+                mService.setTaskWindowingMode(task.taskId,
+                        WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY, true /* toTop */);
                 launchOnSecondaryDisplayFailed = true;
             } else {
                 // The task might have landed on a display different from requested.
@@ -4346,10 +4332,9 @@
             // Dismiss docked stack. If task appeared to be in docked stack but is not resizable -
             // we need to move it to top of fullscreen stack, otherwise it will be covered.
 
-            final ActivityStack dockedStack = task.getStack().getDisplay().getSplitScreenStack();
+            final ActivityStack dockedStack = task.getStack().getDisplay().getSplitScreenPrimaryStack();
             if (dockedStack != null) {
-                moveTasksToFullscreenStackLocked(dockedStack,
-                        actualStackId == dockedStack.getStackId());
+                moveTasksToFullscreenStackLocked(dockedStack, actualStack == dockedStack);
             }
         } else if (topActivity != null && topActivity.isNonResizableOrForcedResizable()
                 && !topActivity.noDisplay) {
@@ -4402,7 +4387,7 @@
     void scheduleUpdatePictureInPictureModeIfNeeded(TaskRecord task, ActivityStack prevStack) {
         final ActivityStack stack = task.getStack();
         if (prevStack == null || prevStack == stack
-                || (prevStack.mStackId != PINNED_STACK_ID && stack.mStackId != PINNED_STACK_ID)) {
+                || (!prevStack.inPinnedWindowingMode() && !stack.inPinnedWindowingMode())) {
             return;
         }
 
@@ -4561,14 +4546,13 @@
         if (display == null) {
             return null;
         }
-        final ArrayList<ActivityStack> stacks = display.mStacks;
-        for (int i = stacks.size() - 1; i >= 0; i--) {
-            if (stacks.get(i) == stack && i > 0) {
-                return stacks.get(i - 1);
+        for (int i = display.getChildCount() - 1; i >= 0; i--) {
+            if (display.getChildAt(i) == stack && i > 0) {
+                return display.getChildAt(i - 1);
             }
         }
         throw new IllegalStateException("Failed to find a stack behind stack=" + stack
-                + " in=" + stacks);
+                + " in=" + display);
     }
 
     /**
@@ -4581,7 +4565,7 @@
         task.setTaskDockedResizing(true);
     }
 
-    final int startActivityFromRecentsInner(int taskId, Bundle bOptions) {
+    int startActivityFromRecents(int taskId, Bundle bOptions) {
         final TaskRecord task;
         final int callingUid;
         final String callingPackage;
@@ -4596,7 +4580,7 @@
             windowingMode = activityOptions.getLaunchWindowingMode();
         }
         if (activityType == ACTIVITY_TYPE_HOME || activityType == ACTIVITY_TYPE_RECENTS) {
-            throw new IllegalArgumentException("startActivityFromRecentsInner: Task "
+            throw new IllegalArgumentException("startActivityFromRecents: Task "
                     + taskId + " can't be launch in the home/recents stack.");
         }
 
@@ -4614,21 +4598,12 @@
             }
 
             task = anyTaskForIdLocked(taskId, MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE,
-                    activityOptions);
+                    activityOptions, ON_TOP);
             if (task == null) {
                 continueUpdateBounds(ACTIVITY_TYPE_RECENTS);
                 mWindowManager.executeAppTransition();
                 throw new IllegalArgumentException(
-                        "startActivityFromRecentsInner: Task " + taskId + " not found.");
-            }
-
-            // Since we don't have an actual source record here, we assume that the currently
-            // focused activity was the source.
-            final ActivityStack stack = getLaunchStack(null, activityOptions, task, ON_TOP);
-
-            if (stack != null && task.getStack() != stack) {
-                task.reparent(stack, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT, ANIMATE, DEFER_RESUME,
-                        "startActivityFromRecents");
+                        "startActivityFromRecents: Task " + taskId + " not found.");
             }
 
             // If the user must confirm credentials (e.g. when first launching a work app and the
@@ -4681,8 +4656,8 @@
         for (int i = mActivityDisplays.size() - 1; i >= 0; i--) {
             final ActivityDisplay display = mActivityDisplays.valueAt(i);
             // Traverse all stacks on a display.
-            for (int j = display.mStacks.size() - 1; j >= 0; j--) {
-                final ActivityStack stack = display.mStacks.get(j);
+            for (int j = display.getChildCount() - 1; j >= 0; --j) {
+                final ActivityStack stack = display.getChildAt(j);
                 // Get top activity from a visible stack and add it to the list.
                 if (stack.shouldBeVisible(null /* starting */)) {
                     final ActivityRecord top = stack.topActivity();
diff --git a/com/android/server/am/ActivityStarter.java b/com/android/server/am/ActivityStarter.java
index d444c66..6f74d85 100644
--- a/com/android/server/am/ActivityStarter.java
+++ b/com/android/server/am/ActivityStarter.java
@@ -26,15 +26,9 @@
 import static android.app.ActivityManager.START_RETURN_LOCK_TASK_MODE_VIOLATION;
 import static android.app.ActivityManager.START_SUCCESS;
 import static android.app.ActivityManager.START_TASK_TO_FRONT;
-import static android.app.ActivityManager.StackId;
-import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
-import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
-import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
-import static android.app.ActivityManager.StackId.isDynamicStack;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
@@ -121,7 +115,6 @@
 import com.android.internal.app.IVoiceInteractor;
 import com.android.server.am.ActivityStackSupervisor.PendingActivityLaunch;
 import com.android.server.pm.InstantAppResolver;
-import com.android.server.wm.WindowManagerService;
 
 import java.io.PrintWriter;
 import java.text.DateFormat;
@@ -140,11 +133,11 @@
     private static final String TAG_FOCUS = TAG + POSTFIX_FOCUS;
     private static final String TAG_CONFIGURATION = TAG + POSTFIX_CONFIGURATION;
     private static final String TAG_USER_LEAVING = TAG + POSTFIX_USER_LEAVING;
+    private static final int INVALID_LAUNCH_MODE = -1;
 
     private final ActivityManagerService mService;
     private final ActivityStackSupervisor mSupervisor;
     private final ActivityStartInterceptor mInterceptor;
-    private WindowManagerService mWindowManager;
 
     final ArrayList<PendingActivityLaunch> mPendingActivityLaunches = new ArrayList<>();
 
@@ -154,9 +147,7 @@
     private int mCallingUid;
     private ActivityOptions mOptions;
 
-    private boolean mLaunchSingleTop;
-    private boolean mLaunchSingleInstance;
-    private boolean mLaunchSingleTask;
+    private int mLaunchMode;
     private boolean mLaunchTaskBehind;
     private int mLaunchFlags;
 
@@ -166,6 +157,7 @@
     private boolean mDoResume;
     private int mStartFlags;
     private ActivityRecord mSourceRecord;
+
     // The display to launch the activity onto, barring any strong reason to do otherwise.
     private int mPreferredDisplayId;
 
@@ -195,8 +187,6 @@
     private IVoiceInteractionSession mVoiceSession;
     private IVoiceInteractor mVoiceInteractor;
 
-    private boolean mUsingVr2dDisplay;
-
     // Last home activity record we attempted to start
     private final ActivityRecord[] mLastHomeActivityStartRecord = new ActivityRecord[1];
     // The result of the last home activity we attempted to start.
@@ -216,11 +206,9 @@
         mCallingUid = -1;
         mOptions = null;
 
-        mLaunchSingleTop = false;
-        mLaunchSingleInstance = false;
-        mLaunchSingleTask = false;
         mLaunchTaskBehind = false;
         mLaunchFlags = 0;
+        mLaunchMode = INVALID_LAUNCH_MODE;
 
         mLaunchBounds = null;
 
@@ -248,16 +236,13 @@
         mVoiceSession = null;
         mVoiceInteractor = null;
 
-        mUsingVr2dDisplay = false;
-
         mIntentDelivered = false;
     }
 
-    ActivityStarter(ActivityManagerService service, ActivityStackSupervisor supervisor) {
+    ActivityStarter(ActivityManagerService service) {
         mService = service;
-        mSupervisor = supervisor;
+        mSupervisor = mService.mStackSupervisor;
         mInterceptor = new ActivityStartInterceptor(mService, mSupervisor);
-        mUsingVr2dDisplay = false;
     }
 
     int startActivityLocked(IApplicationThread caller, Intent intent, Intent ephemeralIntent,
@@ -610,38 +595,41 @@
             mSupervisor.reportTaskToFrontNoLaunch(mStartActivity);
         }
 
-        int startedActivityStackId = INVALID_STACK_ID;
+        ActivityStack startedActivityStack = null;
         final ActivityStack currentStack = r.getStack();
         if (currentStack != null) {
-            startedActivityStackId = currentStack.mStackId;
+            startedActivityStack = currentStack;
         } else if (mTargetStack != null) {
-            startedActivityStackId = targetStack.mStackId;
+            startedActivityStack = targetStack;
         }
 
-        if (startedActivityStackId == DOCKED_STACK_ID) {
-            final ActivityStack homeStack = mSupervisor.getDefaultDisplay().getStack(
-                            WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME);
+        if (startedActivityStack == null) {
+            return;
+        }
+
+        if (startedActivityStack.inSplitScreenPrimaryWindowingMode()) {
+            final ActivityStack homeStack = mSupervisor.mHomeStack;
             final boolean homeStackVisible = homeStack != null && homeStack.isVisible();
             if (homeStackVisible) {
                 // We launch an activity while being in home stack, which means either launcher or
                 // recents into docked stack. We don't want the launched activity to be alone in a
                 // docked stack, so we want to immediately launch recents too.
                 if (DEBUG_RECENTS) Slog.d(TAG, "Scheduling recents launch.");
-                mWindowManager.showRecentApps(true /* fromHome */);
+                mService.mWindowManager.showRecentApps(true /* fromHome */);
             }
             return;
         }
 
         boolean clearedTask = (mLaunchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK))
                 == (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK) && (mReuseTask != null);
-        if (startedActivityStackId == PINNED_STACK_ID && (result == START_TASK_TO_FRONT
-                || result == START_DELIVERED_TO_TOP || clearedTask)) {
+        if (startedActivityStack.inPinnedWindowingMode()
+                && (result == START_TASK_TO_FRONT || result == START_DELIVERED_TO_TOP
+                || clearedTask)) {
             // The activity was already running in the pinned stack so it wasn't started, but either
             // brought to the front or the new intent was delivered to it since it was already in
             // front. Notify anyone interested in this piece of information.
             mService.mTaskChangeNotificationController.notifyPinnedActivityRestartAttempt(
                     clearedTask);
-            return;
         }
     }
 
@@ -1062,7 +1050,7 @@
             // operations.
             if ((mLaunchFlags & FLAG_ACTIVITY_CLEAR_TOP) != 0
                     || isDocumentLaunchesIntoExisting(mLaunchFlags)
-                    || mLaunchSingleInstance || mLaunchSingleTask) {
+                    || isLaunchModeOneOf(LAUNCH_SINGLE_INSTANCE, LAUNCH_SINGLE_TASK)) {
                 final TaskRecord task = reusedActivity.getTask();
 
                 // In this situation we want to remove all activities from the task up to the one
@@ -1145,7 +1133,7 @@
                 && top.userId == mStartActivity.userId
                 && top.app != null && top.app.thread != null
                 && ((mLaunchFlags & FLAG_ACTIVITY_SINGLE_TOP) != 0
-                || mLaunchSingleTop || mLaunchSingleTask);
+                || isLaunchModeOneOf(LAUNCH_SINGLE_TOP, LAUNCH_SINGLE_TASK));
         if (dontStart) {
             // For paranoia, make sure we have correctly resumed the top activity.
             topStack.mLastPausedActivity = null;
@@ -1164,7 +1152,7 @@
             // Don't use mStartActivity.task to show the toast. We're not starting a new activity
             // but reusing 'top'. Fields in mStartActivity may not be fully initialized.
             mSupervisor.handleNonResizableTaskIfNeeded(top.getTask(), preferredWindowingMode,
-                    preferredLaunchDisplayId, topStack.mStackId);
+                    preferredLaunchDisplayId, topStack);
 
             return START_DELIVERED_TO_TOP;
         }
@@ -1227,7 +1215,7 @@
                 mTargetStack.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
                 // Go ahead and tell window manager to execute app transition for this activity
                 // since the app transition will not be triggered through the resume channel.
-                mWindowManager.executeAppTransition();
+                mService.mWindowManager.executeAppTransition();
             } else {
                 // If the target stack was not previously focusable (previous top running activity
                 // on that stack was not visible) then any prior calls to move the stack to the
@@ -1240,13 +1228,13 @@
                 mSupervisor.resumeFocusedStackTopActivityLocked(mTargetStack, mStartActivity,
                         mOptions);
             }
-        } else {
-            mSupervisor.addRecentActivity(mStartActivity);
+        } else if (mStartActivity != null) {
+            mSupervisor.mRecentTasks.add(mStartActivity.getTask());
         }
         mSupervisor.updateUserStackLocked(mStartActivity.userId, mTargetStack);
 
         mSupervisor.handleNonResizableTaskIfNeeded(mStartActivity.getTask(), preferredWindowingMode,
-                preferredLaunchDisplayId, mTargetStack.mStackId);
+                preferredLaunchDisplayId, mTargetStack);
 
         return START_SUCCESS;
     }
@@ -1268,13 +1256,13 @@
 
         mLaunchBounds = getOverrideBounds(r, options, inTask);
 
-        mLaunchSingleTop = r.launchMode == LAUNCH_SINGLE_TOP;
-        mLaunchSingleInstance = r.launchMode == LAUNCH_SINGLE_INSTANCE;
-        mLaunchSingleTask = r.launchMode == LAUNCH_SINGLE_TASK;
+        mLaunchMode = r.launchMode;
+
         mLaunchFlags = adjustLaunchFlagsToDocumentMode(
-                r, mLaunchSingleInstance, mLaunchSingleTask, mIntent.getFlags());
+                r, LAUNCH_SINGLE_INSTANCE == mLaunchMode,
+                LAUNCH_SINGLE_TASK == mLaunchMode, mIntent.getFlags());
         mLaunchTaskBehind = r.mLaunchTaskBehind
-                && !mLaunchSingleTask && !mLaunchSingleInstance
+                && !isLaunchModeOneOf(LAUNCH_SINGLE_TASK, LAUNCH_SINGLE_INSTANCE)
                 && (mLaunchFlags & FLAG_ACTIVITY_NEW_DOCUMENT) != 0;
 
         sendNewTaskResultRequestIfNeeded();
@@ -1384,7 +1372,7 @@
 
             // If this task is empty, then we are adding the first activity -- it
             // determines the root, and must be launching as a NEW_TASK.
-            if (mLaunchSingleInstance || mLaunchSingleTask) {
+            if (isLaunchModeOneOf(LAUNCH_SINGLE_INSTANCE, LAUNCH_SINGLE_TASK)) {
                 if (!baseIntent.getComponent().equals(mStartActivity.intent.getComponent())) {
                     ActivityOptions.abort(mOptions);
                     throw new IllegalArgumentException("Trying to launch singleInstance/Task "
@@ -1427,7 +1415,7 @@
             // in any task/stack, however it could launch other activities like ResolverActivity,
             // and we want those to stay in the original task.
             if ((mStartActivity.isResolverActivity() || mStartActivity.noDisplay) && mSourceRecord != null
-                    && mSourceRecord.isFreeform())  {
+                    && mSourceRecord.inFreeformWindowingMode())  {
                 mAddingToTask = true;
             }
         }
@@ -1446,7 +1434,7 @@
                 // instance...  this new activity it is starting must go on its
                 // own task.
                 mLaunchFlags |= FLAG_ACTIVITY_NEW_TASK;
-            } else if (mLaunchSingleInstance || mLaunchSingleTask) {
+            } else if (isLaunchModeOneOf(LAUNCH_SINGLE_INSTANCE, LAUNCH_SINGLE_TASK)) {
                 // The activity being started is a single instance...  it always
                 // gets launched into its own task.
                 mLaunchFlags |= FLAG_ACTIVITY_NEW_TASK;
@@ -1497,7 +1485,7 @@
         // launch this as a new task behind the current one.
         boolean putIntoExistingTask = ((mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) != 0 &&
                 (mLaunchFlags & FLAG_ACTIVITY_MULTIPLE_TASK) == 0)
-                || mLaunchSingleInstance || mLaunchSingleTask;
+                || isLaunchModeOneOf(LAUNCH_SINGLE_INSTANCE, LAUNCH_SINGLE_TASK);
         // If bring to front is requested, and no result is requested and we have not been given
         // an explicit task to launch in to, and we can find a task that was started with this
         // same component, then instead of launching bring that one to the front.
@@ -1507,7 +1495,7 @@
             final TaskRecord task = mSupervisor.anyTaskForIdLocked(mOptions.getLaunchTaskId());
             intentActivity = task != null ? task.getTopActivity() : null;
         } else if (putIntoExistingTask) {
-            if (mLaunchSingleInstance) {
+            if (LAUNCH_SINGLE_INSTANCE == mLaunchMode) {
                 // There can be one and only one instance of single instance activity in the
                 // history, and it is always in its own unique task, so we do a special search.
                intentActivity = mSupervisor.findActivityLocked(mIntent, mStartActivity.info,
@@ -1516,7 +1504,7 @@
                 // For the launch adjacent case we only want to put the activity in an existing
                 // task if the activity already exists in the history.
                 intentActivity = mSupervisor.findActivityLocked(mIntent, mStartActivity.info,
-                        !mLaunchSingleTask);
+                        !(LAUNCH_SINGLE_TASK == mLaunchMode));
             } else {
                 // Otherwise find the best task to put the activity in.
                 intentActivity = mSupervisor.findTaskLocked(mStartActivity, mPreferredDisplayId);
@@ -1545,7 +1533,6 @@
             if (DEBUG_STACK) {
                 Slog.d(TAG, "getSourceDisplayId :" + displayId);
             }
-            mUsingVr2dDisplay = true;
             return displayId;
         }
 
@@ -1667,7 +1654,7 @@
         }
 
         mSupervisor.handleNonResizableTaskIfNeeded(intentActivity.getTask(),
-                WINDOWING_MODE_UNDEFINED, DEFAULT_DISPLAY, mTargetStack.mStackId);
+                WINDOWING_MODE_UNDEFINED, DEFAULT_DISPLAY, mTargetStack);
 
         // If the caller has requested that the target task be reset, then do so.
         if ((mLaunchFlags & FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) != 0) {
@@ -1719,7 +1706,7 @@
             // mTaskToReturnTo values and we don't want to overwrite them accidentally.
             mMovedOtherTask = true;
         } else if ((mLaunchFlags & FLAG_ACTIVITY_CLEAR_TOP) != 0
-                || mLaunchSingleInstance || mLaunchSingleTask) {
+                || isLaunchModeOneOf(LAUNCH_SINGLE_INSTANCE, LAUNCH_SINGLE_TASK)) {
             ActivityRecord top = intentActivity.getTask().performClearTaskLocked(mStartActivity,
                     mLaunchFlags);
             if (top == null) {
@@ -1748,7 +1735,8 @@
             // so we take that as a request to bring the task to the foreground. If the top
             // activity in the task is the root activity, deliver this new intent to it if it
             // desires.
-            if (((mLaunchFlags & FLAG_ACTIVITY_SINGLE_TOP) != 0 || mLaunchSingleTop)
+            if (((mLaunchFlags & FLAG_ACTIVITY_SINGLE_TOP) != 0
+                        || LAUNCH_SINGLE_TOP == mLaunchMode)
                     && intentActivity.realActivity.equals(mStartActivity.realActivity)) {
                 if (intentActivity.frontOfTask) {
                     intentActivity.getTask().setIntent(mStartActivity);
@@ -1952,7 +1940,7 @@
         if (top != null && top.realActivity.equals(mStartActivity.realActivity)
                 && top.userId == mStartActivity.userId) {
             if ((mLaunchFlags & FLAG_ACTIVITY_SINGLE_TOP) != 0
-                    || mLaunchSingleTop || mLaunchSingleTask) {
+                    || isLaunchModeOneOf(LAUNCH_SINGLE_TOP, LAUNCH_SINGLE_TASK)) {
                 mTargetStack.moveTaskToFrontLocked(mInTask, mNoAnimation, mOptions,
                         mStartActivity.appTimeTracker, "inTaskToFront");
                 if ((mStartFlags & START_FLAG_ONLY_IF_NEEDED) != 0) {
@@ -2001,9 +1989,9 @@
             return;
         }
 
-        final int stackId = task.getStackId();
-        if (StackId.resizeStackWithLaunchBounds(stackId)) {
-            mService.resizeStack(stackId, bounds, true, !PRESERVE_WINDOWS, ANIMATE, -1);
+        final ActivityStack stack = task.getStack();
+        if (stack != null && stack.resizeStackWithLaunchBounds()) {
+            mService.resizeStack(stack.mStackId, bounds, true, !PRESERVE_WINDOWS, ANIMATE, -1);
         } else {
             task.updateOverrideConfiguration(bounds);
         }
@@ -2115,11 +2103,10 @@
         }
         if (stack == null) {
             // We first try to put the task in the first dynamic stack on home display.
-            final ArrayList<ActivityStack> homeDisplayStacks =
-                    mSupervisor.getStacksOnDefaultDisplay();
-            for (int stackNdx = homeDisplayStacks.size() - 1; stackNdx >= 0; --stackNdx) {
-                stack = homeDisplayStacks.get(stackNdx);
-                if (isDynamicStack(stack.mStackId)) {
+            final ActivityDisplay display = mSupervisor.getDefaultDisplay();
+            for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+                stack = display.getChildAt(stackNdx);
+                if (!stack.isOnHomeDisplay()) {
                     if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS,
                             "computeStackFocus: Setting focused stack=" + stack);
                     return stack;
@@ -2138,7 +2125,6 @@
     private boolean canLaunchIntoFocusedStack(ActivityRecord r, boolean newTask) {
         final ActivityStack focusedStack = mSupervisor.mFocusedStack;
         final boolean canUseFocusedStack;
-        final int focusedStackId = mSupervisor.mFocusedStack.mStackId;
         if (focusedStack.isActivityTypeAssistant()) {
             canUseFocusedStack = r.isActivityTypeAssistant();
         } else {
@@ -2161,7 +2147,7 @@
                 default:
                     // Dynamic stacks behave similarly to the fullscreen stack and can contain any
                     // resizeable task.
-                    canUseFocusedStack = isDynamicStack(focusedStackId)
+                    canUseFocusedStack = !focusedStack.isOnHomeDisplay()
                             && r.canBeLaunchedOnDisplay(focusedStack.mDisplayId);
             }
         }
@@ -2177,7 +2163,8 @@
             return mReuseTask.getStack();
         }
 
-        final int vrDisplayId = mUsingVr2dDisplay ? mPreferredDisplayId : INVALID_DISPLAY;
+        final int vrDisplayId = mPreferredDisplayId == mService.mVr2dDisplayId
+                ? mPreferredDisplayId : INVALID_DISPLAY;
         final ActivityStack launchStack = mSupervisor.getLaunchStack(r, aOptions, task, ON_TOP,
                 vrDisplayId);
 
@@ -2205,7 +2192,7 @@
                 return mSupervisor.mFocusedStack;
             }
 
-            if (parentStack != null && parentStack.isDockedStack()) {
+            if (parentStack != null && parentStack.inSplitScreenPrimaryWindowingMode()) {
                 // If parent was in docked stack, the natural place to launch another activity
                 // will be fullscreen, so it can appear alongside the docked window.
                 final int activityType = mSupervisor.resolveActivityType(r, mOptions, task);
@@ -2215,8 +2202,8 @@
                 // If the parent is not in the docked stack, we check if there is docked window
                 // and if yes, we will launch into that stack. If not, we just put the new
                 // activity into parent's stack, because we can't find a better place.
-                final ActivityStack dockedStack = mSupervisor.getDefaultDisplay().getStack(
-                                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_UNDEFINED);
+                final ActivityStack dockedStack =
+                        mSupervisor.getDefaultDisplay().getSplitScreenPrimaryStack();
                 if (dockedStack != null && !dockedStack.shouldBeVisible(r)) {
                     // There is a docked stack, but it isn't visible, so we can't launch into that.
                     return null;
@@ -2236,8 +2223,8 @@
         return newBounds;
     }
 
-    void setWindowManager(WindowManagerService wm) {
-        mWindowManager = wm;
+    private boolean isLaunchModeOneOf(int mode1, int mode2) {
+        return mode1 == mLaunchMode || mode2 == mLaunchMode;
     }
 
     static boolean isDocumentLaunchesIntoExisting(int flags) {
@@ -2318,11 +2305,11 @@
         }
         pw.print(prefix);
         pw.print("mLaunchSingleTop=");
-        pw.print(mLaunchSingleTop);
+        pw.print(LAUNCH_SINGLE_TOP == mLaunchMode);
         pw.print(" mLaunchSingleInstance=");
-        pw.print(mLaunchSingleInstance);
+        pw.print(LAUNCH_SINGLE_INSTANCE == mLaunchMode);
         pw.print(" mLaunchSingleTask=");
-        pw.println(mLaunchSingleTask);
+        pw.println(LAUNCH_SINGLE_TASK == mLaunchMode);
         pw.print(prefix);
         pw.print("mLaunchFlags=0x");
         pw.print(Integer.toHexString(mLaunchFlags));
diff --git a/com/android/server/am/AppTaskImpl.java b/com/android/server/am/AppTaskImpl.java
new file mode 100644
index 0000000..38b3039
--- /dev/null
+++ b/com/android/server/am/AppTaskImpl.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2017 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.am;
+
+import static com.android.server.am.ActivityStackSupervisor.MATCH_TASK_IN_STACKS_OR_RECENT_TASKS;
+import static com.android.server.am.ActivityStackSupervisor.REMOVE_FROM_RECENTS;
+
+import android.app.ActivityManager;
+import android.app.IAppTask;
+import android.app.IApplicationThread;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.UserHandle;
+
+/**
+ * An implementation of IAppTask, that allows an app to manage its own tasks via
+ * {@link android.app.ActivityManager.AppTask}.  We keep track of the callingUid to ensure that
+ * only the process that calls getAppTasks() can call the AppTask methods.
+ */
+class AppTaskImpl extends IAppTask.Stub {
+    private ActivityManagerService mService;
+
+    private int mTaskId;
+    private int mCallingUid;
+
+    public AppTaskImpl(ActivityManagerService service, int taskId, int callingUid) {
+        mService = service;
+        mTaskId = taskId;
+        mCallingUid = callingUid;
+    }
+
+    private void checkCaller() {
+        if (mCallingUid != Binder.getCallingUid()) {
+            throw new SecurityException("Caller " + mCallingUid
+                    + " does not match caller of getAppTasks(): " + Binder.getCallingUid());
+        }
+    }
+
+    @Override
+    public void finishAndRemoveTask() {
+        checkCaller();
+
+        synchronized (mService) {
+            long origId = Binder.clearCallingIdentity();
+            try {
+                // We remove the task from recents to preserve backwards
+                if (!mService.mStackSupervisor.removeTaskByIdLocked(mTaskId, false,
+                        REMOVE_FROM_RECENTS)) {
+                    throw new IllegalArgumentException("Unable to find task ID " + mTaskId);
+                }
+            } finally {
+                Binder.restoreCallingIdentity(origId);
+            }
+        }
+    }
+
+    @Override
+    public ActivityManager.RecentTaskInfo getTaskInfo() {
+        checkCaller();
+
+        synchronized (mService) {
+            long origId = Binder.clearCallingIdentity();
+            try {
+                TaskRecord tr = mService.mStackSupervisor.anyTaskForIdLocked(mTaskId,
+                        MATCH_TASK_IN_STACKS_OR_RECENT_TASKS);
+                if (tr == null) {
+                    throw new IllegalArgumentException("Unable to find task ID " + mTaskId);
+                }
+                return RecentTasks.createRecentTaskInfo(tr);
+            } finally {
+                Binder.restoreCallingIdentity(origId);
+            }
+        }
+    }
+
+    @Override
+    public void moveToFront() {
+        checkCaller();
+        // Will bring task to front if it already has a root activity.
+        final long origId = Binder.clearCallingIdentity();
+        try {
+            synchronized (this) {
+                mService.mStackSupervisor.startActivityFromRecents(mTaskId, null);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+        }
+    }
+
+    @Override
+    public int startActivity(IBinder whoThread, String callingPackage,
+            Intent intent, String resolvedType, Bundle bOptions) {
+        checkCaller();
+
+        int callingUser = UserHandle.getCallingUserId();
+        TaskRecord tr;
+        IApplicationThread appThread;
+        synchronized (mService) {
+            tr = mService.mStackSupervisor.anyTaskForIdLocked(mTaskId,
+                    MATCH_TASK_IN_STACKS_OR_RECENT_TASKS);
+            if (tr == null) {
+                throw new IllegalArgumentException("Unable to find task ID " + mTaskId);
+            }
+            appThread = IApplicationThread.Stub.asInterface(whoThread);
+            if (appThread == null) {
+                throw new IllegalArgumentException("Bad app thread " + appThread);
+            }
+        }
+        return mService.mActivityStarter.startActivityMayWait(appThread, -1, callingPackage,
+                intent, resolvedType, null, null, null, null, 0, 0, null, null,
+                null, bOptions, false, callingUser, tr, "AppTaskImpl");
+    }
+
+    @Override
+    public void setExcludeFromRecents(boolean exclude) {
+        checkCaller();
+
+        synchronized (mService) {
+            long origId = Binder.clearCallingIdentity();
+            try {
+                TaskRecord tr = mService.mStackSupervisor.anyTaskForIdLocked(mTaskId,
+                        MATCH_TASK_IN_STACKS_OR_RECENT_TASKS);
+                if (tr == null) {
+                    throw new IllegalArgumentException("Unable to find task ID " + mTaskId);
+                }
+                Intent intent = tr.getBaseIntent();
+                if (exclude) {
+                    intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+                } else {
+                    intent.setFlags(intent.getFlags()
+                            & ~Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+                }
+            } finally {
+                Binder.restoreCallingIdentity(origId);
+            }
+        }
+    }
+}
diff --git a/com/android/server/am/BatteryStatsService.java b/com/android/server/am/BatteryStatsService.java
index 7c9cd00..db12ae2 100644
--- a/com/android/server/am/BatteryStatsService.java
+++ b/com/android/server/am/BatteryStatsService.java
@@ -297,43 +297,40 @@
     void noteProcessStart(String name, int uid) {
         synchronized (mStats) {
             mStats.noteProcessStartLocked(name, uid);
-
-            // TODO: remove this once we figure out properly where and how
-            // PROCESS_EVENT = 1112
-            // KEY_STATE = 1
-            // KEY_PACKAGE_NAME: 1002
-            // KEY_UID: 2
-            StatsLog.writeArray(1112, 1, 1, 1002, name, 2, uid);
+            // TODO: decide where this should be and use a constant instead of a literal.
+            StatsLog.write(StatsLog.PROCESS_LIFE_CYCLE_STATE_CHANGED, uid, name, 1);
         }
     }
 
     void noteProcessCrash(String name, int uid) {
         synchronized (mStats) {
             mStats.noteProcessCrashLocked(name, uid);
-
-            // TODO: remove this once we figure out properly where and how
-            // PROCESS_EVENT = 1112
-            // KEY_STATE = 1
-            // KEY_PACKAGE_NAME: 1002
-            // KEY_UID: 2
-            StatsLog.writeArray(1112, 1, 2, 1002, name, 2, uid);
+            // TODO: decide where this should be and use a constant instead of a literal.
+            StatsLog.write(StatsLog.PROCESS_LIFE_CYCLE_STATE_CHANGED, uid, name, 2);
         }
     }
 
     void noteProcessAnr(String name, int uid) {
         synchronized (mStats) {
             mStats.noteProcessAnrLocked(name, uid);
+            // TODO: decide where this should be and use a constant instead of a literal.
+            StatsLog.write(StatsLog.PROCESS_LIFE_CYCLE_STATE_CHANGED, uid, name, 3);
         }
     }
 
     void noteProcessFinish(String name, int uid) {
         synchronized (mStats) {
             mStats.noteProcessFinishLocked(name, uid);
+            // TODO: decide where this should be and use a constant instead of a literal.
+            StatsLog.write(StatsLog.PROCESS_LIFE_CYCLE_STATE_CHANGED, uid, name, 0);
         }
     }
 
     void noteUidProcessState(int uid, int state) {
         synchronized (mStats) {
+            // TODO: remove this once we figure out properly where and how
+            StatsLog.write(StatsLog.UID_PROCESS_STATE_CHANGED, uid, state);
+
             mStats.noteUidProcessStateLocked(uid, state);
         }
     }
@@ -548,12 +545,10 @@
         enforceCallingPermission();
         if (DBG) Slog.d(TAG, "begin noteScreenState");
         synchronized (mStats) {
-            mStats.noteScreenStateLocked(state);
             // TODO: remove this once we figure out properly where and how
-            // SCREEN_EVENT = 2
-            // KEY_STATE: 1
-            // State value: state. We can change this to our own def later.
-            StatsLog.writeArray(2, 1, state);
+            StatsLog.write(StatsLog.SCREEN_STATE_CHANGED, state);
+
+            mStats.noteScreenStateLocked(state);
         }
         if (DBG) Slog.d(TAG, "end noteScreenState");
     }
@@ -561,6 +556,7 @@
     public void noteScreenBrightness(int brightness) {
         enforceCallingPermission();
         synchronized (mStats) {
+            StatsLog.write(StatsLog.SCREEN_BRIGHTNESS_CHANGED, brightness);
             mStats.noteScreenBrightnessLocked(brightness);
         }
     }
@@ -922,6 +918,7 @@
         enforceCallingPermission();
         synchronized (mStats) {
             mStats.noteDeviceIdleModeLocked(mode, activeReason, activeUid);
+            StatsLog.write(StatsLog.DEVICE_IDLE_MODE_STATE_CHANGED, mode);
         }
     }
 
diff --git a/com/android/server/am/BroadcastFilter.java b/com/android/server/am/BroadcastFilter.java
index f96b06f..7ff227f 100644
--- a/com/android/server/am/BroadcastFilter.java
+++ b/com/android/server/am/BroadcastFilter.java
@@ -19,6 +19,9 @@
 import android.content.IntentFilter;
 import android.util.PrintWriterPrinter;
 import android.util.Printer;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.server.am.proto.BroadcastFilterProto;
 
 import java.io.PrintWriter;
 
@@ -44,27 +47,38 @@
         instantApp = _instantApp;
         visibleToInstantApp = _visibleToInstantApp;
     }
-    
+
+    public void writeToProto(ProtoOutputStream proto, long fieldId) {
+        long token = proto.start(fieldId);
+        super.writeToProto(proto, BroadcastFilterProto.INTENT_FILTER);
+        if (requiredPermission != null) {
+            proto.write(BroadcastFilterProto.REQUIRED_PERMISSION, requiredPermission);
+        }
+        proto.write(BroadcastFilterProto.HEX_HASH, Integer.toHexString(System.identityHashCode(this)));
+        proto.write(BroadcastFilterProto.OWNING_USER_ID, owningUserId);
+        proto.end(token);
+    }
+
     public void dump(PrintWriter pw, String prefix) {
         dumpInReceiverList(pw, new PrintWriterPrinter(pw), prefix);
         receiverList.dumpLocal(pw, prefix);
     }
-    
+
     public void dumpBrief(PrintWriter pw, String prefix) {
         dumpBroadcastFilterState(pw, prefix);
     }
-    
+
     public void dumpInReceiverList(PrintWriter pw, Printer pr, String prefix) {
         super.dump(pr, prefix);
         dumpBroadcastFilterState(pw, prefix);
     }
-    
+
     void dumpBroadcastFilterState(PrintWriter pw, String prefix) {
         if (requiredPermission != null) {
             pw.print(prefix); pw.print("requiredPermission="); pw.println(requiredPermission);
         }
     }
-    
+
     public String toString() {
         StringBuilder sb = new StringBuilder();
         sb.append("BroadcastFilter{");
diff --git a/com/android/server/am/BroadcastQueue.java b/com/android/server/am/BroadcastQueue.java
index d835454..c62cc38 100644
--- a/com/android/server/am/BroadcastQueue.java
+++ b/com/android/server/am/BroadcastQueue.java
@@ -51,9 +51,12 @@
 import android.util.EventLog;
 import android.util.Slog;
 import android.util.TimeUtils;
+import android.util.proto.ProtoOutputStream;
 
 import static com.android.server.am.ActivityManagerDebugConfig.*;
 
+import com.android.server.am.proto.BroadcastQueueProto;
+
 /**
  * BROADCASTS
  *
@@ -1585,6 +1588,55 @@
                 && (mPendingBroadcast == null);
     }
 
+    void writeToProto(ProtoOutputStream proto, long fieldId) {
+        long token = proto.start(fieldId);
+        proto.write(BroadcastQueueProto.QUEUE_NAME, mQueueName);
+        int N;
+        N = mParallelBroadcasts.size();
+        for (int i = N - 1; i >= 0; i--) {
+            mParallelBroadcasts.get(i).writeToProto(proto, BroadcastQueueProto.PARALLEL_BROADCASTS);
+        }
+        N = mOrderedBroadcasts.size();
+        for (int i = N - 1; i >= 0; i--) {
+            mOrderedBroadcasts.get(i).writeToProto(proto, BroadcastQueueProto.ORDERED_BROADCASTS);
+        }
+        if (mPendingBroadcast != null) {
+            mPendingBroadcast.writeToProto(proto, BroadcastQueueProto.PENDING_BROADCAST);
+        }
+
+        int lastIndex = mHistoryNext;
+        int ringIndex = lastIndex;
+        do {
+            // increasing index = more recent entry, and we want to print the most
+            // recent first and work backwards, so we roll through the ring backwards.
+            ringIndex = ringAdvance(ringIndex, -1, MAX_BROADCAST_HISTORY);
+            BroadcastRecord r = mBroadcastHistory[ringIndex];
+            if (r != null) {
+                r.writeToProto(proto, BroadcastQueueProto.HISTORICAL_BROADCASTS);
+            }
+        } while (ringIndex != lastIndex);
+
+        lastIndex = ringIndex = mSummaryHistoryNext;
+        do {
+            ringIndex = ringAdvance(ringIndex, -1, MAX_BROADCAST_SUMMARY_HISTORY);
+            Intent intent = mBroadcastSummaryHistory[ringIndex];
+            if (intent == null) {
+                continue;
+            }
+            long summaryToken = proto.start(BroadcastQueueProto.HISTORICAL_BROADCASTS_SUMMARY);
+            intent.writeToProto(proto, BroadcastQueueProto.BroadcastSummary.INTENT,
+                    false, true, true, false);
+            proto.write(BroadcastQueueProto.BroadcastSummary.ENQUEUE_CLOCK_TIME_MS,
+                    mSummaryHistoryEnqueueTime[ringIndex]);
+            proto.write(BroadcastQueueProto.BroadcastSummary.DISPATCH_CLOCK_TIME_MS,
+                    mSummaryHistoryDispatchTime[ringIndex]);
+            proto.write(BroadcastQueueProto.BroadcastSummary.FINISH_CLOCK_TIME_MS,
+                    mSummaryHistoryFinishTime[ringIndex]);
+            proto.end(summaryToken);
+        } while (ringIndex != lastIndex);
+        proto.end(token);
+    }
+
     final boolean dumpLocked(FileDescriptor fd, PrintWriter pw, String[] args,
             int opti, boolean dumpAll, String dumpPackage, boolean needSep) {
         SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
diff --git a/com/android/server/am/BroadcastRecord.java b/com/android/server/am/BroadcastRecord.java
index 6bc0744..5b3b2a8 100644
--- a/com/android/server/am/BroadcastRecord.java
+++ b/com/android/server/am/BroadcastRecord.java
@@ -30,6 +30,9 @@
 import android.os.UserHandle;
 import android.util.PrintWriterPrinter;
 import android.util.TimeUtils;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.server.am.proto.BroadcastRecordProto;
 
 import java.io.PrintWriter;
 import java.text.SimpleDateFormat;
@@ -331,9 +334,17 @@
         return didSomething;
     }
 
+    @Override
     public String toString() {
         return "BroadcastRecord{"
             + Integer.toHexString(System.identityHashCode(this))
             + " u" + userId + " " + intent.getAction() + "}";
     }
+
+    public void writeToProto(ProtoOutputStream proto, long fieldId) {
+        long token = proto.start(fieldId);
+        proto.write(BroadcastRecordProto.USER_ID, userId);
+        proto.write(BroadcastRecordProto.INTENT_ACTION, intent.getAction());
+        proto.end(token);
+    }
 }
diff --git a/com/android/server/am/KeyguardController.java b/com/android/server/am/KeyguardController.java
index 5c48f90..c3fed17 100644
--- a/com/android/server/am/KeyguardController.java
+++ b/com/android/server/am/KeyguardController.java
@@ -16,7 +16,6 @@
 
 package com.android.server.am;
 
-import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
 import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.INVALID_DISPLAY;
@@ -47,7 +46,6 @@
 import com.android.server.wm.WindowManagerService;
 
 import java.io.PrintWriter;
-import java.util.ArrayList;
 
 /**
  * Controls Keyguard occluding, dismissing and transitions depending on what kind of activities are
@@ -238,9 +236,9 @@
         final ActivityRecord lastDismissingKeyguardActivity = mDismissingKeyguardActivity;
         mOccluded = false;
         mDismissingKeyguardActivity = null;
-        final ArrayList<ActivityStack> stacks = mStackSupervisor.getStacksOnDefaultDisplay();
-        for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
-            final ActivityStack stack = stacks.get(stackNdx);
+        final ActivityDisplay display = mStackSupervisor.getDefaultDisplay();
+        for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+            final ActivityStack stack = display.getChildAt(stackNdx);
 
             // Only the focused stack top activity may control occluded state
             if (mStackSupervisor.isFocusedStack(stack)) {
@@ -342,7 +340,7 @@
             // show on top of the lock screen. In this can we want to dismiss the docked
             // stack since it will be complicated/risky to try to put the activity on top
             // of the lock screen in the right fullscreen configuration.
-            final ActivityStack stack = mStackSupervisor.getDefaultDisplay().getSplitScreenStack();
+            final ActivityStack stack = mStackSupervisor.getDefaultDisplay().getSplitScreenPrimaryStack();
             if (stack == null) {
                 return;
             }
diff --git a/com/android/server/am/LaunchingTaskPositioner.java b/com/android/server/am/LaunchingTaskPositioner.java
index d652341..0dc73e9 100644
--- a/com/android/server/am/LaunchingTaskPositioner.java
+++ b/com/android/server/am/LaunchingTaskPositioner.java
@@ -24,8 +24,8 @@
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.util.Slog;
-import android.view.Display;
 import android.view.Gravity;
+import com.android.internal.annotations.VisibleForTesting;
 
 import java.util.ArrayList;
 
@@ -64,43 +64,11 @@
     private static final int SHIFT_POLICY_HORIZONTAL_RIGHT = 2;
     private static final int SHIFT_POLICY_HORIZONTAL_LEFT = 3;
 
-    private boolean mDefaultStartBoundsConfigurationSet = false;
     private final Rect mAvailableRect = new Rect();
     private final Rect mTmpProposal = new Rect();
     private final Rect mTmpOriginal = new Rect();
 
-    private int mDefaultFreeformStartX;
-    private int mDefaultFreeformStartY;
-    private int mDefaultFreeformWidth;
-    private int mDefaultFreeformHeight;
-    private int mDefaultFreeformStepHorizontal;
-    private int mDefaultFreeformStepVertical;
-    private int mDisplayWidth;
-    private int mDisplayHeight;
-
-    void setDisplay(Display display) {
-        Point size = new Point();
-        display.getSize(size);
-        mDisplayWidth = size.x;
-        mDisplayHeight = size.y;
-    }
-
-    void configure(Rect stackBounds) {
-        if (stackBounds == null) {
-            mAvailableRect.set(0, 0, mDisplayWidth, mDisplayHeight);
-        } else {
-            mAvailableRect.set(stackBounds);
-        }
-        int width = mAvailableRect.width();
-        int height = mAvailableRect.height();
-        mDefaultFreeformStartX = mAvailableRect.left + width / MARGIN_SIZE_DENOMINATOR;
-        mDefaultFreeformStartY = mAvailableRect.top + height / MARGIN_SIZE_DENOMINATOR;
-        mDefaultFreeformWidth = width / WINDOW_SIZE_DENOMINATOR;
-        mDefaultFreeformHeight = height / WINDOW_SIZE_DENOMINATOR;
-        mDefaultFreeformStepHorizontal = Math.max(width / STEP_DENOMINATOR, MINIMAL_STEP);
-        mDefaultFreeformStepVertical = Math.max(height / STEP_DENOMINATOR, MINIMAL_STEP);
-        mDefaultStartBoundsConfigurationSet = true;
-    }
+    private final Point mDisplaySize = new Point();
 
     /**
      * Tries to set task's bound in a way that it won't collide with any other task. By colliding
@@ -114,104 +82,154 @@
      */
     void updateDefaultBounds(TaskRecord task, ArrayList<TaskRecord> tasks,
             @Nullable ActivityInfo.WindowLayout windowLayout) {
-        if (!mDefaultStartBoundsConfigurationSet) {
-            return;
-        }
+        updateAvailableRect(task, mAvailableRect);
+
         if (windowLayout == null) {
-            positionCenter(task, tasks, mDefaultFreeformWidth, mDefaultFreeformHeight);
+            positionCenter(task, tasks, mAvailableRect, getFreeformWidth(mAvailableRect),
+                    getFreeformHeight(mAvailableRect));
             return;
         }
-        int width = getFinalWidth(windowLayout);
-        int height = getFinalHeight(windowLayout);
+        int width = getFinalWidth(windowLayout, mAvailableRect);
+        int height = getFinalHeight(windowLayout, mAvailableRect);
         int verticalGravity = windowLayout.gravity & Gravity.VERTICAL_GRAVITY_MASK;
         int horizontalGravity = windowLayout.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
         if (verticalGravity == Gravity.TOP) {
             if (horizontalGravity == Gravity.RIGHT) {
-                positionTopRight(task, tasks, width, height);
+                positionTopRight(task, tasks, mAvailableRect, width, height);
             } else {
-                positionTopLeft(task, tasks, width, height);
+                positionTopLeft(task, tasks, mAvailableRect, width, height);
             }
         } else if (verticalGravity == Gravity.BOTTOM) {
             if (horizontalGravity == Gravity.RIGHT) {
-                positionBottomRight(task, tasks, width, height);
+                positionBottomRight(task, tasks, mAvailableRect, width, height);
             } else {
-                positionBottomLeft(task, tasks, width, height);
+                positionBottomLeft(task, tasks, mAvailableRect, width, height);
             }
         } else {
             // Some fancy gravity setting that we don't support yet. We just put the activity in the
             // center.
             Slog.w(TAG, "Received unsupported gravity: " + windowLayout.gravity
                     + ", positioning in the center instead.");
-            positionCenter(task, tasks, width, height);
+            positionCenter(task, tasks, mAvailableRect, width, height);
         }
     }
 
-    private int getFinalWidth(ActivityInfo.WindowLayout windowLayout) {
-        int width = mDefaultFreeformWidth;
+    private void updateAvailableRect(TaskRecord task, Rect availableRect) {
+        final Rect stackBounds = task.getStack().mBounds;
+
+        if (stackBounds != null) {
+            availableRect.set(stackBounds);
+        } else {
+            task.getStack().getDisplay().mDisplay.getSize(mDisplaySize);
+            availableRect.set(0, 0, mDisplaySize.x, mDisplaySize.y);
+        }
+    }
+
+    @VisibleForTesting
+    static int getFreeformStartLeft(Rect bounds) {
+        return bounds.left + bounds.width() / MARGIN_SIZE_DENOMINATOR;
+    }
+
+    @VisibleForTesting
+    static int getFreeformStartTop(Rect bounds) {
+        return bounds.top + bounds.height() / MARGIN_SIZE_DENOMINATOR;
+    }
+
+    @VisibleForTesting
+    static int getFreeformWidth(Rect bounds) {
+        return bounds.width() / WINDOW_SIZE_DENOMINATOR;
+    }
+
+    @VisibleForTesting
+    static int getFreeformHeight(Rect bounds) {
+        return bounds.height() / WINDOW_SIZE_DENOMINATOR;
+    }
+
+    @VisibleForTesting
+    static int getHorizontalStep(Rect bounds) {
+        return Math.max(bounds.width() / STEP_DENOMINATOR, MINIMAL_STEP);
+    }
+
+    @VisibleForTesting
+    static int getVerticalStep(Rect bounds) {
+        return Math.max(bounds.height() / STEP_DENOMINATOR, MINIMAL_STEP);
+    }
+
+
+
+    private int getFinalWidth(ActivityInfo.WindowLayout windowLayout, Rect availableRect) {
+        int width = getFreeformWidth(availableRect);
         if (windowLayout.width > 0) {
             width = windowLayout.width;
         }
         if (windowLayout.widthFraction > 0) {
-            width = (int) (mAvailableRect.width() * windowLayout.widthFraction);
+            width = (int) (availableRect.width() * windowLayout.widthFraction);
         }
         return width;
     }
 
-    private int getFinalHeight(ActivityInfo.WindowLayout windowLayout) {
-        int height = mDefaultFreeformHeight;
+    private int getFinalHeight(ActivityInfo.WindowLayout windowLayout, Rect availableRect) {
+        int height = getFreeformHeight(availableRect);
         if (windowLayout.height > 0) {
             height = windowLayout.height;
         }
         if (windowLayout.heightFraction > 0) {
-            height = (int) (mAvailableRect.height() * windowLayout.heightFraction);
+            height = (int) (availableRect.height() * windowLayout.heightFraction);
         }
         return height;
     }
 
-    private void positionBottomLeft(TaskRecord task, ArrayList<TaskRecord> tasks, int width,
-            int height) {
-        mTmpProposal.set(mAvailableRect.left, mAvailableRect.bottom - height,
-                mAvailableRect.left + width, mAvailableRect.bottom);
-        position(task, tasks, mTmpProposal, !ALLOW_RESTART, SHIFT_POLICY_HORIZONTAL_RIGHT);
+    private void positionBottomLeft(TaskRecord task, ArrayList<TaskRecord> tasks,
+            Rect availableRect, int width, int height) {
+        mTmpProposal.set(availableRect.left, availableRect.bottom - height,
+                availableRect.left + width, availableRect.bottom);
+        position(task, tasks, availableRect, mTmpProposal, !ALLOW_RESTART,
+                SHIFT_POLICY_HORIZONTAL_RIGHT);
     }
 
-    private void positionBottomRight(TaskRecord task, ArrayList<TaskRecord> tasks, int width,
-            int height) {
-        mTmpProposal.set(mAvailableRect.right - width, mAvailableRect.bottom - height,
-                mAvailableRect.right, mAvailableRect.bottom);
-        position(task, tasks, mTmpProposal, !ALLOW_RESTART, SHIFT_POLICY_HORIZONTAL_LEFT);
+    private void positionBottomRight(TaskRecord task, ArrayList<TaskRecord> tasks,
+            Rect availableRect, int width, int height) {
+        mTmpProposal.set(availableRect.right - width, availableRect.bottom - height,
+                availableRect.right, availableRect.bottom);
+        position(task, tasks, availableRect, mTmpProposal, !ALLOW_RESTART,
+                SHIFT_POLICY_HORIZONTAL_LEFT);
     }
 
-    private void positionTopLeft(TaskRecord task, ArrayList<TaskRecord> tasks, int width,
-            int height) {
-        mTmpProposal.set(mAvailableRect.left, mAvailableRect.top,
-                mAvailableRect.left + width, mAvailableRect.top + height);
-        position(task, tasks, mTmpProposal, !ALLOW_RESTART, SHIFT_POLICY_HORIZONTAL_RIGHT);
+    private void positionTopLeft(TaskRecord task, ArrayList<TaskRecord> tasks,
+            Rect availableRect, int width, int height) {
+        mTmpProposal.set(availableRect.left, availableRect.top,
+                availableRect.left + width, availableRect.top + height);
+        position(task, tasks, availableRect, mTmpProposal, !ALLOW_RESTART,
+                SHIFT_POLICY_HORIZONTAL_RIGHT);
     }
 
-    private void positionTopRight(TaskRecord task, ArrayList<TaskRecord> tasks, int width,
-            int height) {
-        mTmpProposal.set(mAvailableRect.right - width, mAvailableRect.top,
-                mAvailableRect.right, mAvailableRect.top + height);
-        position(task, tasks, mTmpProposal, !ALLOW_RESTART, SHIFT_POLICY_HORIZONTAL_LEFT);
+    private void positionTopRight(TaskRecord task, ArrayList<TaskRecord> tasks,
+            Rect availableRect, int width, int height) {
+        mTmpProposal.set(availableRect.right - width, availableRect.top,
+                availableRect.right, availableRect.top + height);
+        position(task, tasks, availableRect, mTmpProposal, !ALLOW_RESTART,
+                SHIFT_POLICY_HORIZONTAL_LEFT);
     }
 
-    private void positionCenter(TaskRecord task, ArrayList<TaskRecord> tasks, int width,
-            int height) {
-        mTmpProposal.set(mDefaultFreeformStartX, mDefaultFreeformStartY,
-                mDefaultFreeformStartX + width, mDefaultFreeformStartY + height);
-        position(task, tasks, mTmpProposal, ALLOW_RESTART, SHIFT_POLICY_DIAGONAL_DOWN);
+    private void positionCenter(TaskRecord task, ArrayList<TaskRecord> tasks,
+            Rect availableRect, int width, int height) {
+        final int defaultFreeformLeft = getFreeformStartLeft(availableRect);
+        final int defaultFreeformTop = getFreeformStartTop(availableRect);
+        mTmpProposal.set(defaultFreeformLeft, defaultFreeformTop,
+                defaultFreeformLeft + width, defaultFreeformTop + height);
+        position(task, tasks, availableRect, mTmpProposal, ALLOW_RESTART,
+                SHIFT_POLICY_DIAGONAL_DOWN);
     }
 
-    private void position(TaskRecord task, ArrayList<TaskRecord> tasks, Rect proposal,
-            boolean allowRestart, int shiftPolicy) {
+    private void position(TaskRecord task, ArrayList<TaskRecord> tasks, Rect availableRect,
+            Rect proposal, boolean allowRestart, int shiftPolicy) {
         mTmpOriginal.set(proposal);
         boolean restarted = false;
         while (boundsConflict(proposal, tasks)) {
             // Unfortunately there is already a task at that spot, so we need to look for some
             // other place.
-            shiftStartingPoint(proposal, shiftPolicy);
-            if (shiftedToFar(proposal, shiftPolicy)) {
+            shiftStartingPoint(proposal, availableRect, shiftPolicy);
+            if (shiftedTooFar(proposal, availableRect, shiftPolicy)) {
                 // We don't want the task to go outside of the stack, because it won't look
                 // nice. Depending on the starting point we either restart, or immediately give up.
                 if (!allowRestart) {
@@ -220,13 +238,13 @@
                 }
                 // We must have started not from the top. Let's restart from there because there
                 // might be some space there.
-                proposal.set(mAvailableRect.left, mAvailableRect.top,
-                        mAvailableRect.left + proposal.width(),
-                        mAvailableRect.top + proposal.height());
+                proposal.set(availableRect.left, availableRect.top,
+                        availableRect.left + proposal.width(),
+                        availableRect.top + proposal.height());
                 restarted = true;
             }
-            if (restarted && (proposal.left > mDefaultFreeformStartX
-                    || proposal.top > mDefaultFreeformStartY)) {
+            if (restarted && (proposal.left > getFreeformStartLeft(availableRect)
+                    || proposal.top > getFreeformStartTop(availableRect))) {
                 // If we restarted and crossed the initial position, let's not struggle anymore.
                 // The user already must have ton of tasks visible, we can just smack the new
                 // one in the center.
@@ -237,27 +255,30 @@
         task.updateOverrideConfiguration(proposal);
     }
 
-    private boolean shiftedToFar(Rect start, int shiftPolicy) {
+    private boolean shiftedTooFar(Rect start, Rect availableRect, int shiftPolicy) {
         switch (shiftPolicy) {
             case SHIFT_POLICY_HORIZONTAL_LEFT:
-                return start.left < mAvailableRect.left;
+                return start.left < availableRect.left;
             case SHIFT_POLICY_HORIZONTAL_RIGHT:
-                return start.right > mAvailableRect.right;
+                return start.right > availableRect.right;
             default: // SHIFT_POLICY_DIAGONAL_DOWN
-                return start.right > mAvailableRect.right || start.bottom > mAvailableRect.bottom;
+                return start.right > availableRect.right || start.bottom > availableRect.bottom;
         }
     }
 
-    private void shiftStartingPoint(Rect posposal, int shiftPolicy) {
+    private void shiftStartingPoint(Rect posposal, Rect availableRect, int shiftPolicy) {
+        final int defaultFreeformStepHorizontal = getHorizontalStep(availableRect);
+        final int defaultFreeformStepVertical = getVerticalStep(availableRect);
+
         switch (shiftPolicy) {
             case SHIFT_POLICY_HORIZONTAL_LEFT:
-                posposal.offset(-mDefaultFreeformStepHorizontal, 0);
+                posposal.offset(-defaultFreeformStepHorizontal, 0);
                 break;
             case SHIFT_POLICY_HORIZONTAL_RIGHT:
-                posposal.offset(mDefaultFreeformStepHorizontal, 0);
+                posposal.offset(defaultFreeformStepHorizontal, 0);
                 break;
             default: // SHIFT_POLICY_DIAGONAL_DOWN:
-                posposal.offset(mDefaultFreeformStepHorizontal, mDefaultFreeformStepVertical);
+                posposal.offset(defaultFreeformStepHorizontal, defaultFreeformStepVertical);
                 break;
         }
     }
@@ -296,8 +317,4 @@
         return Math.abs(first.right - second.right) < BOUNDS_CONFLICT_MIN_DISTANCE
                 && Math.abs(first.bottom - second.bottom) < BOUNDS_CONFLICT_MIN_DISTANCE;
     }
-
-    void reset() {
-        mDefaultStartBoundsConfigurationSet = false;
-    }
 }
diff --git a/com/android/server/am/LockTaskController.java b/com/android/server/am/LockTaskController.java
index 72b5de8..1c094c1 100644
--- a/com/android/server/am/LockTaskController.java
+++ b/com/android/server/am/LockTaskController.java
@@ -19,7 +19,6 @@
 import static android.app.ActivityManager.LOCK_TASK_MODE_LOCKED;
 import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
 import static android.app.ActivityManager.LOCK_TASK_MODE_PINNED;
-import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
 import static android.app.StatusBarManager.DISABLE_BACK;
 import static android.app.StatusBarManager.DISABLE_HOME;
 import static android.app.StatusBarManager.DISABLE_MASK;
@@ -59,7 +58,6 @@
 import android.util.Slog;
 import android.util.SparseArray;
 
-import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.widget.LockPatternUtils;
@@ -329,7 +327,9 @@
             if (getDevicePolicyManager() != null) {
                 getDevicePolicyManager().notifyLockTaskModeChanged(false, null, userId);
             }
-            getLockTaskNotify().show(false);
+            if (mLockTaskModeState == LOCK_TASK_MODE_PINNED) {
+                getLockTaskNotify().showPinningExitToast();
+            }
             try {
                 boolean shouldLockKeyguard = Settings.Secure.getIntForUser(
                         mContext.getContentResolver(),
@@ -351,10 +351,13 @@
     }
 
     /**
-     * Show the lock task violation toast.
+     * Show the lock task violation toast. Currently we only show toast for screen pinning mode, and
+     * no-op if the device is in locked mode.
      */
     void showLockTaskToast() {
-        mHandler.post(() -> getLockTaskNotify().showToast(mLockTaskModeState));
+        if (mLockTaskModeState == LOCK_TASK_MODE_PINNED) {
+            mHandler.post(() -> getLockTaskNotify().showEscapeToast());
+        }
     }
 
     // Starting lock task
@@ -433,7 +436,7 @@
             mWindowManager.executeAppTransition();
         } else if (lockTaskModeState != LOCK_TASK_MODE_NONE) {
             mSupervisor.handleNonResizableTaskIfNeeded(task, WINDOWING_MODE_UNDEFINED,
-                    DEFAULT_DISPLAY, task.getStackId(), true /* forceNonResizable */);
+                    DEFAULT_DISPLAY, task.getStack(), true /* forceNonResizable */);
         }
     }
 
@@ -441,7 +444,9 @@
     private void performStartLockTask(String packageName, int userId, int lockTaskModeState) {
         // When lock task starts, we disable the status bars.
         try {
-            getLockTaskNotify().show(true);
+            if (lockTaskModeState == LOCK_TASK_MODE_PINNED) {
+                getLockTaskNotify().showPinningStartToast();
+            }
             mLockTaskModeState = lockTaskModeState;
             if (getStatusBarService() != null) {
                 int flags = 0;
@@ -494,11 +499,7 @@
         }
 
         for (int displayNdx = mSupervisor.getChildCount() - 1; displayNdx >= 0; --displayNdx) {
-            ArrayList<ActivityStack> stacks = mSupervisor.getChildAt(displayNdx).mStacks;
-            for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
-                final ActivityStack stack = stacks.get(stackNdx);
-                stack.onLockTaskPackagesUpdatedLocked();
-            }
+            mSupervisor.getChildAt(displayNdx).onLockTaskPackagesUpdated();
         }
 
         final ActivityRecord r = mSupervisor.topRunningActivityLocked();
diff --git a/com/android/server/am/LockTaskNotify.java b/com/android/server/am/LockTaskNotify.java
index 0412db5..1dcb0ad 100644
--- a/com/android/server/am/LockTaskNotify.java
+++ b/com/android/server/am/LockTaskNotify.java
@@ -16,10 +16,7 @@
 
 package com.android.server.am;
 
-import android.app.ActivityManager;
 import android.content.Context;
-import android.os.Handler;
-import android.os.Message;
 import android.os.SystemClock;
 import android.util.Slog;
 import android.view.WindowManager;
@@ -28,37 +25,33 @@
 import com.android.internal.R;
 
 /**
- *  Helper to manage showing/hiding a image to notify them that they are entering
- *  or exiting lock-to-app mode.
+ *  Helper to manage showing/hiding a image to notify them that they are entering or exiting screen
+ *  pinning mode. All exposed methods should be called from a handler thread.
  */
 public class LockTaskNotify {
     private static final String TAG = "LockTaskNotify";
     private static final long SHOW_TOAST_MINIMUM_INTERVAL = 1000;
 
     private final Context mContext;
-    private final H mHandler;
     private Toast mLastToast;
     private long mLastShowToastTime;
 
     public LockTaskNotify(Context context) {
         mContext = context;
-        mHandler = new H();
     }
 
-    public void showToast(int lockTaskModeState) {
-        mHandler.obtainMessage(H.SHOW_TOAST, lockTaskModeState, 0 /* Not used */).sendToTarget();
+    /** Show "Screen pinned" toast. */
+    void showPinningStartToast() {
+        makeAllUserToastAndShow(R.string.lock_to_app_start);
     }
 
-    public void handleShowToast(int lockTaskModeState) {
-        String text = null;
-        if (lockTaskModeState == ActivityManager.LOCK_TASK_MODE_LOCKED) {
-            text = mContext.getString(R.string.lock_to_app_toast_locked);
-        } else if (lockTaskModeState == ActivityManager.LOCK_TASK_MODE_PINNED) {
-            text = mContext.getString(R.string.lock_to_app_toast);
-        }
-        if (text == null) {
-            return;
-        }
+    /** Show "Screen unpinned" toast. */
+    void showPinningExitToast() {
+        makeAllUserToastAndShow(R.string.lock_to_app_exit);
+    }
+
+    /** Show a toast that describes the gesture the user should use to escape pinned mode. */
+    void showEscapeToast() {
         long showToastTime = SystemClock.elapsedRealtime();
         if ((showToastTime - mLastShowToastTime) < SHOW_TOAST_MINIMUM_INTERVAL) {
             Slog.i(TAG, "Ignore toast since it is requested in very short interval.");
@@ -67,36 +60,15 @@
         if (mLastToast != null) {
             mLastToast.cancel();
         }
-        mLastToast = makeAllUserToastAndShow(text);
+        mLastToast = makeAllUserToastAndShow(R.string.lock_to_app_toast);
         mLastShowToastTime = showToastTime;
     }
 
-    public void show(boolean starting) {
-        int showString = R.string.lock_to_app_exit;
-        if (starting) {
-            showString = R.string.lock_to_app_start;
-        }
-        makeAllUserToastAndShow(mContext.getString(showString));
-    }
-
-    private Toast makeAllUserToastAndShow(String text) {
-        Toast toast = Toast.makeText(mContext, text, Toast.LENGTH_LONG);
+    private Toast makeAllUserToastAndShow(int resId) {
+        Toast toast = Toast.makeText(mContext, resId, Toast.LENGTH_LONG);
         toast.getWindowParams().privateFlags |=
                 WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
         toast.show();
         return toast;
     }
-
-    private final class H extends Handler {
-        private static final int SHOW_TOAST = 3;
-
-        @Override
-        public void handleMessage(Message msg) {
-            switch(msg.what) {
-                case SHOW_TOAST:
-                    handleShowToast(msg.arg1);
-                    break;
-            }
-        }
-    }
 }
diff --git a/com/android/server/am/ProcessRecord.java b/com/android/server/am/ProcessRecord.java
index 0e318d9..e847723 100644
--- a/com/android/server/am/ProcessRecord.java
+++ b/com/android/server/am/ProcessRecord.java
@@ -27,6 +27,7 @@
 import com.android.internal.app.procstats.ProcessStats;
 import com.android.internal.app.procstats.ProcessState;
 import com.android.internal.os.BatteryStatsImpl;
+import com.android.server.am.proto.ProcessRecordProto;
 
 import android.app.ActivityManager;
 import android.app.Dialog;
@@ -44,6 +45,7 @@
 import android.os.UserHandle;
 import android.util.ArrayMap;
 import android.util.TimeUtils;
+import android.util.proto.ProtoOutputStream;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -621,6 +623,22 @@
         }
     }
 
+    public void writeToProto(ProtoOutputStream proto, long fieldId) {
+        long token = proto.start(fieldId);
+        proto.write(ProcessRecordProto.PID, pid);
+        proto.write(ProcessRecordProto.PROCESS_NAME, processName);
+        if (info.uid < Process.FIRST_APPLICATION_UID) {
+            proto.write(ProcessRecordProto.UID, uid);
+        } else {
+            proto.write(ProcessRecordProto.USER_ID, userId);
+            proto.write(ProcessRecordProto.APP_ID, UserHandle.getAppId(info.uid));
+            if (uid != info.uid) {
+                proto.write(ProcessRecordProto.ISOLATED_APP_ID, UserHandle.getAppId(uid));
+            }
+        }
+        proto.end(token);
+    }
+
     public String toShortString() {
         if (shortStringName != null) {
             return shortStringName;
diff --git a/com/android/server/am/ReceiverList.java b/com/android/server/am/ReceiverList.java
index 6ade736..a989063 100644
--- a/com/android/server/am/ReceiverList.java
+++ b/com/android/server/am/ReceiverList.java
@@ -21,6 +21,8 @@
 import android.os.IBinder;
 import android.util.PrintWriterPrinter;
 import android.util.Printer;
+import android.util.proto.ProtoOutputStream;
+import com.android.server.am.proto.ReceiverListProto;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -41,7 +43,7 @@
     boolean linkedToDeath = false;
 
     String stringName;
-    
+
     ReceiverList(ActivityManagerService _owner, ProcessRecord _app,
             int _pid, int _uid, int _userId, IIntentReceiver _receiver) {
         owner = _owner;
@@ -59,12 +61,31 @@
     public int hashCode() {
         return System.identityHashCode(this);
     }
-    
+
     public void binderDied() {
         linkedToDeath = false;
         owner.unregisterReceiver(receiver);
     }
-    
+
+    void writeToProto(ProtoOutputStream proto, long fieldId) {
+        long token = proto.start(fieldId);
+        app.writeToProto(proto, ReceiverListProto.APP);
+        proto.write(ReceiverListProto.PID, pid);
+        proto.write(ReceiverListProto.UID, uid);
+        proto.write(ReceiverListProto.USER, userId);
+        if (curBroadcast != null) {
+            curBroadcast.writeToProto(proto, ReceiverListProto.CURRENT);
+        }
+        proto.write(ReceiverListProto.LINKED_TO_DEATH, linkedToDeath);
+        final int N = size();
+        for (int i=0; i<N; i++) {
+            BroadcastFilter bf = get(i);
+            bf.writeToProto(proto, ReceiverListProto.FILTERS);
+        }
+        proto.write(ReceiverListProto.HEX_HASH, Integer.toHexString(System.identityHashCode(this)));
+        proto.end(token);
+    }
+
     void dumpLocal(PrintWriter pw, String prefix) {
         pw.print(prefix); pw.print("app="); pw.print(app != null ? app.toShortString() : null);
             pw.print(" pid="); pw.print(pid); pw.print(" uid="); pw.print(uid);
@@ -74,7 +95,7 @@
                 pw.print(" linkedToDeath="); pw.println(linkedToDeath);
         }
     }
-    
+
     void dump(PrintWriter pw, String prefix) {
         Printer pr = new PrintWriterPrinter(pw);
         dumpLocal(pw, prefix);
@@ -89,7 +110,7 @@
             bf.dumpInReceiverList(pw, pr, p2);
         }
     }
-    
+
     public String toString() {
         if (stringName != null) {
             return stringName;
diff --git a/com/android/server/am/RecentTasks.java b/com/android/server/am/RecentTasks.java
index 365c5b1..ed3f503 100644
--- a/com/android/server/am/RecentTasks.java
+++ b/com/android/server/am/RecentTasks.java
@@ -16,15 +16,25 @@
 
 package com.android.server.am;
 
+import static android.app.ActivityManager.FLAG_AND_UNLOCKED;
+import static android.app.ActivityManager.RECENT_IGNORE_UNAVAILABLE;
+import static android.app.ActivityManager.RECENT_WITH_EXCLUDED;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
 import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_RECENTS;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_RECENTS_TRIM_TASKS;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_TASKS;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_RECENTS;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_TASKS;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.am.ActivityStackSupervisor.REMOVE_FROM_RECENTS;
 import static com.android.server.am.TaskRecord.INVALID_TASK_ID;
 
 import com.google.android.collect.Sets;
@@ -37,43 +47,87 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
+import android.content.pm.ParceledListSlice;
+import android.content.pm.UserInfo;
+import android.content.res.Resources;
 import android.graphics.Bitmap;
+import android.graphics.Rect;
+import android.os.Bundle;
 import android.os.Environment;
+import android.os.IBinder;
 import android.os.RemoteException;
+import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.util.ArraySet;
+import android.util.MutableBoolean;
+import android.util.MutableInt;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
 
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.am.ActivityStack.ActivityState;
+
 import java.io.File;
+import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Set;
+import java.util.concurrent.TimeUnit;
 
 /**
- * Class for managing the recent tasks list.
+ * Class for managing the recent tasks list. The list is ordered by most recent (index 0) to the
+ * least recent.
  */
-class RecentTasks extends ArrayList<TaskRecord> {
+class RecentTasks {
     private static final String TAG = TAG_WITH_CLASS_NAME ? "RecentTasks" : TAG_AM;
     private static final String TAG_RECENTS = TAG + POSTFIX_RECENTS;
     private static final String TAG_TASKS = TAG + POSTFIX_TASKS;
+    private static final boolean TRIMMED = true;
 
-    // Maximum number recent bitmaps to keep in memory.
-    private static final int MAX_RECENT_BITMAPS = 3;
     private static final int DEFAULT_INITIAL_CAPACITY = 5;
 
     // Whether or not to move all affiliated tasks to the front when one of the tasks is launched
     private static final boolean MOVE_AFFILIATED_TASKS_TO_FRONT = false;
 
+    // Comparator to sort by taskId
+    private static final Comparator<TaskRecord> TASK_ID_COMPARATOR =
+            (lhs, rhs) -> rhs.taskId - lhs.taskId;
+
+    // Placeholder variables to keep track of activities/apps that are no longer avialble while
+    // iterating through the recents list
+    private static final ActivityInfo NO_ACTIVITY_INFO_TOKEN = new ActivityInfo();
+    private static final ApplicationInfo NO_APPLICATION_INFO_TOKEN = new ApplicationInfo();
+
+    /**
+     * Callbacks made when manipulating the list.
+     */
+    interface Callbacks {
+        /**
+         * Called when a task is added to the recent tasks list.
+         */
+        void onRecentTaskAdded(TaskRecord task);
+
+        /**
+         * Called when a task is removed from the recent tasks list.
+         */
+        void onRecentTaskRemoved(TaskRecord task, boolean wasTrimmed);
+    }
+
     /**
      * Save recent tasks information across reboots.
      */
     private final TaskPersister mTaskPersister;
     private final ActivityManagerService mService;
+    private final UserController mUserController;
+
+    /**
+     * Mapping of user id -> whether recent tasks have been loaded for that user.
+     */
     private final SparseBooleanArray mUsersWithRecentsLoaded = new SparseBooleanArray(
             DEFAULT_INITIAL_CAPACITY);
 
@@ -81,21 +135,106 @@
      * Stores for each user task ids that are taken by tasks residing in persistent storage. These
      * tasks may or may not currently be in memory.
      */
-    final SparseArray<SparseBooleanArray> mPersistedTaskIds = new SparseArray<>(
+    private final SparseArray<SparseBooleanArray> mPersistedTaskIds = new SparseArray<>(
             DEFAULT_INITIAL_CAPACITY);
 
+    // List of all active recent tasks
+    private final ArrayList<TaskRecord> mTasks = new ArrayList<>();
+    private final ArrayList<Callbacks> mCallbacks = new ArrayList<>();
+
+    // These values are generally loaded from resources, but can be set dynamically in the tests
+    private boolean mHasVisibleRecentTasks;
+    private int mGlobalMaxNumTasks;
+    private int mMinNumVisibleTasks;
+    private int mMaxNumVisibleTasks;
+    private long mActiveTasksSessionDurationMs;
+
     // Mainly to avoid object recreation on multiple calls.
-    private final ArrayList<TaskRecord> mTmpRecents = new ArrayList<TaskRecord>();
+    private final ArrayList<TaskRecord> mTmpRecents = new ArrayList<>();
     private final HashMap<ComponentName, ActivityInfo> mTmpAvailActCache = new HashMap<>();
     private final HashMap<String, ApplicationInfo> mTmpAvailAppCache = new HashMap<>();
-    private final ActivityInfo mTmpActivityInfo = new ActivityInfo();
-    private final ApplicationInfo mTmpAppInfo = new ApplicationInfo();
+    private final SparseBooleanArray mTmpQuietProfileUserIds = new SparseBooleanArray();
 
-    RecentTasks(ActivityManagerService service, ActivityStackSupervisor mStackSupervisor) {
-        File systemDir = Environment.getDataSystemDirectory();
+    @VisibleForTesting
+    RecentTasks(ActivityManagerService service, TaskPersister taskPersister,
+            UserController userController) {
         mService = service;
-        mTaskPersister = new TaskPersister(systemDir, mStackSupervisor, service, this);
-        mStackSupervisor.setRecentTasks(this);
+        mUserController = userController;
+        mTaskPersister = taskPersister;
+        mGlobalMaxNumTasks = ActivityManager.getMaxRecentTasksStatic();
+        mHasVisibleRecentTasks = true;
+    }
+
+    RecentTasks(ActivityManagerService service, ActivityStackSupervisor stackSupervisor) {
+        final File systemDir = Environment.getDataSystemDirectory();
+        final Resources res = service.mContext.getResources();
+        mService = service;
+        mUserController = service.mUserController;
+        mTaskPersister = new TaskPersister(systemDir, stackSupervisor, service, this);
+        mGlobalMaxNumTasks = ActivityManager.getMaxRecentTasksStatic();
+        mHasVisibleRecentTasks = res.getBoolean(com.android.internal.R.bool.config_hasRecents);
+        loadParametersFromResources(service.mContext.getResources());
+    }
+
+    @VisibleForTesting
+    void setParameters(int minNumVisibleTasks, int maxNumVisibleTasks,
+            long activeSessionDurationMs) {
+        mMinNumVisibleTasks = minNumVisibleTasks;
+        mMaxNumVisibleTasks = maxNumVisibleTasks;
+        mActiveTasksSessionDurationMs = activeSessionDurationMs;
+    }
+
+    @VisibleForTesting
+    void setGlobalMaxNumTasks(int globalMaxNumTasks) {
+        mGlobalMaxNumTasks = globalMaxNumTasks;
+    }
+
+    /**
+     * Loads the parameters from the system resources.
+     */
+    @VisibleForTesting
+    void loadParametersFromResources(Resources res) {
+        if (ActivityManager.isLowRamDeviceStatic()) {
+            mMinNumVisibleTasks = res.getInteger(
+                    com.android.internal.R.integer.config_minNumVisibleRecentTasks_lowRam);
+            mMaxNumVisibleTasks = res.getInteger(
+                    com.android.internal.R.integer.config_maxNumVisibleRecentTasks_lowRam);
+        } else if (SystemProperties.getBoolean("ro.recents.grid", false)) {
+            mMinNumVisibleTasks = res.getInteger(
+                    com.android.internal.R.integer.config_minNumVisibleRecentTasks_grid);
+            mMaxNumVisibleTasks = res.getInteger(
+                    com.android.internal.R.integer.config_maxNumVisibleRecentTasks_grid);
+        } else {
+            mMinNumVisibleTasks = res.getInteger(
+                    com.android.internal.R.integer.config_minNumVisibleRecentTasks);
+            mMaxNumVisibleTasks = res.getInteger(
+                    com.android.internal.R.integer.config_maxNumVisibleRecentTasks);
+        }
+        final int sessionDurationHrs = res.getInteger(
+                com.android.internal.R.integer.config_activeTaskDurationHours);
+        mActiveTasksSessionDurationMs = (sessionDurationHrs > 0)
+                ? TimeUnit.HOURS.toMillis(sessionDurationHrs)
+                : -1;
+    }
+
+    void registerCallback(Callbacks callback) {
+        mCallbacks.add(callback);
+    }
+
+    void unregisterCallback(Callbacks callback) {
+        mCallbacks.remove(callback);
+    }
+
+    private void notifyTaskAdded(TaskRecord task) {
+        for (int i = 0; i < mCallbacks.size(); i++) {
+            mCallbacks.get(i).onRecentTaskAdded(task);
+        }
+    }
+
+    private void notifyTaskRemoved(TaskRecord task, boolean wasTrimmed) {
+        for (int i = 0; i < mCallbacks.size(); i++) {
+            mCallbacks.get(i).onRecentTaskRemoved(task, wasTrimmed);
+        }
     }
 
     /**
@@ -106,6 +245,7 @@
      */
     void loadUserRecentsLocked(int userId) {
         if (mUsersWithRecentsLoaded.get(userId)) {
+            // User already loaded, return early
             return;
         }
 
@@ -114,14 +254,14 @@
 
         // Check if any tasks are added before recents is loaded
         final SparseBooleanArray preaddedTasks = new SparseBooleanArray();
-        for (final TaskRecord task : this) {
+        for (final TaskRecord task : mTasks) {
             if (task.userId == userId && shouldPersistTaskLocked(task)) {
                 preaddedTasks.put(task.taskId, true);
             }
         }
 
         Slog.i(TAG, "Loading recents for user " + userId + " into memory.");
-        addAll(mTaskPersister.restoreTasksForUserLocked(userId, preaddedTasks));
+        mTasks.addAll(mTaskPersister.restoreTasksForUserLocked(userId, preaddedTasks));
         cleanupLocked(userId);
         mUsersWithRecentsLoaded.put(userId, true);
 
@@ -140,11 +280,25 @@
         }
     }
 
-    boolean taskIdTakenForUserLocked(int taskId, int userId) {
+    /**
+     * @return whether the {@param taskId} is currently in use for the given user.
+     */
+    boolean containsTaskId(int taskId, int userId) {
         loadPersistedTaskIdsForUserLocked(userId);
         return mPersistedTaskIds.get(userId).get(taskId);
     }
 
+    /**
+     * @return all the task ids for the user with the given {@param userId}.
+     */
+    SparseBooleanArray getTaskIdsForUser(int userId) {
+        loadPersistedTaskIdsForUserLocked(userId);
+        return mPersistedTaskIds.get(userId);
+    }
+
+    /**
+     * Kicks off the task persister to write any pending tasks to disk.
+     */
     void notifyTaskPersisterLocked(TaskRecord task, boolean flush) {
         final ActivityStack stack = task != null ? task.getStack() : null;
         if (stack != null && stack.isHomeOrRecentsStack()) {
@@ -164,8 +318,8 @@
                 mPersistedTaskIds.valueAt(i).clear();
             }
         }
-        for (int i = size() - 1; i >= 0; i--) {
-            final TaskRecord task = get(i);
+        for (int i = mTasks.size() - 1; i >= 0; i--) {
+            final TaskRecord task = mTasks.get(i);
             if (shouldPersistTaskLocked(task)) {
                 // Set of persisted taskIds for task.userId should not be null here
                 // TODO Investigate why it can happen. For now initialize with an empty set
@@ -180,12 +334,12 @@
     }
 
     private static boolean shouldPersistTaskLocked(TaskRecord task) {
-        final ActivityStack<?> stack = task.getStack();
+        final ActivityStack stack = task.getStack();
         return task.isPersistable && (stack == null || !stack.isHomeOrRecentsStack());
     }
 
     void onSystemReadyLocked() {
-        clear();
+        mTasks.clear();
         mTaskPersister.startPersisting();
     }
 
@@ -225,14 +379,6 @@
         return usersWithRecentsLoaded;
     }
 
-    private void unloadUserRecentsLocked(int userId) {
-        if (mUsersWithRecentsLoaded.get(userId)) {
-            Slog.i(TAG, "Unloading recents for user " + userId + " from memory.");
-            mUsersWithRecentsLoaded.delete(userId);
-            removeTasksForUserLocked(userId);
-        }
-    }
-
     /**
      * Removes recent tasks and any other state kept in memory for the passed in user. Does not
      * touch the information present on persistent storage.
@@ -240,44 +386,36 @@
      * @param userId the id of the user
      */
     void unloadUserDataFromMemoryLocked(int userId) {
-        unloadUserRecentsLocked(userId);
+        if (mUsersWithRecentsLoaded.get(userId)) {
+            Slog.i(TAG, "Unloading recents for user " + userId + " from memory.");
+            mUsersWithRecentsLoaded.delete(userId);
+            removeTasksForUserLocked(userId);
+        }
         mPersistedTaskIds.delete(userId);
         mTaskPersister.unloadUserDataFromMemory(userId);
     }
 
-    TaskRecord taskForIdLocked(int id) {
-        final int recentsCount = size();
-        for (int i = 0; i < recentsCount; i++) {
-            TaskRecord tr = get(i);
-            if (tr.taskId == id) {
-                return tr;
-            }
-        }
-        return null;
-    }
-
     /** Remove recent tasks for a user. */
-    void removeTasksForUserLocked(int userId) {
+    private void removeTasksForUserLocked(int userId) {
         if(userId <= 0) {
             Slog.i(TAG, "Can't remove recent task on user " + userId);
             return;
         }
 
-        for (int i = size() - 1; i >= 0; --i) {
-            TaskRecord tr = get(i);
+        for (int i = mTasks.size() - 1; i >= 0; --i) {
+            TaskRecord tr = mTasks.get(i);
             if (tr.userId == userId) {
                 if(DEBUG_TASKS) Slog.i(TAG_TASKS,
                         "remove RecentTask " + tr + " when finishing user" + userId);
-                remove(i);
-                tr.removedFromRecents();
+                remove(mTasks.get(i));
             }
         }
     }
 
     void onPackagesSuspendedChanged(String[] packages, boolean suspended, int userId) {
         final Set<String> packageNames = Sets.newHashSet(packages);
-        for (int i = size() - 1; i >= 0; --i) {
-            final TaskRecord tr = get(i);
+        for (int i = mTasks.size() - 1; i >= 0; --i) {
+            final TaskRecord tr = mTasks.get(i);
             if (tr.realActivity != null
                     && packageNames.contains(tr.realActivity.getPackageName())
                     && tr.userId == userId
@@ -286,7 +424,36 @@
                notifyTaskPersisterLocked(tr, false);
             }
         }
+    }
 
+    void removeTasksByPackageName(String packageName, int userId) {
+        for (int i = mTasks.size() - 1; i >= 0; --i) {
+            final TaskRecord tr = mTasks.get(i);
+            final String taskPackageName =
+                    tr.getBaseIntent().getComponent().getPackageName();
+            if (tr.userId != userId) return;
+            if (!taskPackageName.equals(packageName)) return;
+
+            mService.mStackSupervisor.removeTaskByIdLocked(tr.taskId, true, REMOVE_FROM_RECENTS);
+        }
+    }
+
+    void cleanupDisabledPackageTasksLocked(String packageName, Set<String> filterByClasses,
+            int userId) {
+        for (int i = mTasks.size() - 1; i >= 0; --i) {
+            final TaskRecord tr = mTasks.get(i);
+            if (userId != UserHandle.USER_ALL && tr.userId != userId) {
+                continue;
+            }
+
+            ComponentName cn = tr.intent.getComponent();
+            final boolean sameComponent = cn != null && cn.getPackageName().equals(packageName)
+                    && (filterByClasses == null || filterByClasses.contains(cn.getClassName()));
+            if (sameComponent) {
+                mService.mStackSupervisor.removeTaskByIdLocked(tr.taskId, false,
+                        REMOVE_FROM_RECENTS);
+            }
+        }
     }
 
     /**
@@ -295,24 +462,28 @@
      * of affiliations.
      */
     void cleanupLocked(int userId) {
-        int recentsCount = size();
+        int recentsCount = mTasks.size();
         if (recentsCount == 0) {
             // Happens when called from the packagemanager broadcast before boot,
             // or just any empty list.
             return;
         }
 
+        // Clear the temp lists
+        mTmpAvailActCache.clear();
+        mTmpAvailAppCache.clear();
+
         final IPackageManager pm = AppGlobals.getPackageManager();
         for (int i = recentsCount - 1; i >= 0; i--) {
-            final TaskRecord task = get(i);
+            final TaskRecord task = mTasks.get(i);
             if (userId != UserHandle.USER_ALL && task.userId != userId) {
                 // Only look at tasks for the user ID of interest.
                 continue;
             }
             if (task.autoRemoveRecents && task.getTopActivity() == null) {
                 // This situation is broken, and we should just get rid of it now.
-                remove(i);
-                task.removedFromRecents();
+                mTasks.remove(i);
+                notifyTaskRemoved(task, !TRIMMED);
                 Slog.w(TAG, "Removing auto-remove without activity: " + task);
                 continue;
             }
@@ -331,11 +502,11 @@
                         continue;
                     }
                     if (ai == null) {
-                        ai = mTmpActivityInfo;
+                        ai = NO_ACTIVITY_INFO_TOKEN;
                     }
                     mTmpAvailActCache.put(task.realActivity, ai);
                 }
-                if (ai == mTmpActivityInfo) {
+                if (ai == NO_ACTIVITY_INFO_TOKEN) {
                     // This could be either because the activity no longer exists, or the
                     // app is temporarily gone. For the former we want to remove the recents
                     // entry; for the latter we want to mark it as unavailable.
@@ -350,15 +521,15 @@
                             continue;
                         }
                         if (app == null) {
-                            app = mTmpAppInfo;
+                            app = NO_APPLICATION_INFO_TOKEN;
                         }
                         mTmpAvailAppCache.put(task.realActivity.getPackageName(), app);
                     }
-                    if (app == mTmpAppInfo
+                    if (app == NO_APPLICATION_INFO_TOKEN
                             || (app.flags & ApplicationInfo.FLAG_INSTALLED) == 0) {
                         // Doesn't exist any more! Good-bye.
-                        remove(i);
-                        task.removedFromRecents();
+                        mTasks.remove(i);
+                        notifyTaskRemoved(task, !TRIMMED);
                         Slog.w(TAG, "Removing no longer valid recent: " + task);
                         continue;
                     } else {
@@ -390,15 +561,670 @@
 
         // Verify the affiliate chain for each task.
         int i = 0;
-        recentsCount = size();
+        recentsCount = mTasks.size();
         while (i < recentsCount) {
             i = processNextAffiliateChainLocked(i);
         }
         // recent tasks are now in sorted, affiliated order.
     }
 
-    private final boolean moveAffiliatedTasksToFront(TaskRecord task, int taskIndex) {
-        int recentsCount = size();
+    /**
+     * @return whether the given {@param task} can be added to the list without causing another
+     * task to be trimmed as a result of that add.
+     */
+    private boolean canAddTaskWithoutTrim(TaskRecord task) {
+        return findTrimIndexForAddTask(task) == -1;
+    }
+
+    /**
+     * Returns the list of {@link ActivityManager.AppTask}s.
+     */
+    ArrayList<IBinder> getAppTasksList(int callingUid, String callingPackage) {
+        final ArrayList<IBinder> list = new ArrayList<>();
+        final int size = mTasks.size();
+        for (int i = 0; i < size; i++) {
+            final TaskRecord tr = mTasks.get(i);
+            // Skip tasks that do not match the caller.  We don't need to verify
+            // callingPackage, because we are also limiting to callingUid and know
+            // that will limit to the correct security sandbox.
+            if (tr.effectiveUid != callingUid) {
+                continue;
+            }
+            Intent intent = tr.getBaseIntent();
+            if (intent == null || !callingPackage.equals(intent.getComponent().getPackageName())) {
+                continue;
+            }
+            ActivityManager.RecentTaskInfo taskInfo = createRecentTaskInfo(tr);
+            AppTaskImpl taskImpl = new AppTaskImpl(mService, taskInfo.persistentId, callingUid);
+            list.add(taskImpl.asBinder());
+        }
+        return list;
+    }
+
+    /**
+     * @return the list of recent tasks for presentation.
+     */
+    ParceledListSlice<ActivityManager.RecentTaskInfo> getRecentTasks(int maxNum, int flags,
+            boolean getTasksAllowed, boolean getDetailedTasks, int userId, int callingUid) {
+        final boolean withExcluded = (flags & RECENT_WITH_EXCLUDED) != 0;
+
+        if (!mService.isUserRunning(userId, FLAG_AND_UNLOCKED)) {
+            Slog.i(TAG, "user " + userId + " is still locked. Cannot load recents");
+            return ParceledListSlice.emptyList();
+        }
+        loadUserRecentsLocked(userId);
+
+        final Set<Integer> includedUsers = mUserController.getProfileIds(userId);
+        includedUsers.add(Integer.valueOf(userId));
+
+        final ArrayList<ActivityManager.RecentTaskInfo> res = new ArrayList<>();
+        final int size = mTasks.size();
+        int numVisibleTasks = 0;
+        for (int i = 0; i < size; i++) {
+            final TaskRecord tr = mTasks.get(i);
+
+            if (isVisibleRecentTask(tr)) {
+                numVisibleTasks++;
+                if (isInVisibleRange(tr, numVisibleTasks)) {
+                    // Fall through
+                } else {
+                    // Not in visible range
+                    continue;
+                }
+            } else {
+                // Not visible
+                continue;
+            }
+
+            // Skip remaining tasks once we reach the requested size
+            if (res.size() >= maxNum) {
+                continue;
+            }
+
+            // Only add calling user or related users recent tasks
+            if (!includedUsers.contains(Integer.valueOf(tr.userId))) {
+                if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "Skipping, not user: " + tr);
+                continue;
+            }
+
+            if (tr.realActivitySuspended) {
+                if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "Skipping, activity suspended: " + tr);
+                continue;
+            }
+
+            // Return the entry if desired by the caller.  We always return
+            // the first entry, because callers always expect this to be the
+            // foreground app.  We may filter others if the caller has
+            // not supplied RECENT_WITH_EXCLUDED and there is some reason
+            // we should exclude the entry.
+
+            if (i == 0
+                    || withExcluded
+                    || (tr.intent == null)
+                    || ((tr.intent.getFlags() & Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
+                    == 0)) {
+                if (!getTasksAllowed) {
+                    // If the caller doesn't have the GET_TASKS permission, then only
+                    // allow them to see a small subset of tasks -- their own and home.
+                    if (!tr.isActivityTypeHome() && tr.effectiveUid != callingUid) {
+                        if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "Skipping, not allowed: " + tr);
+                        continue;
+                    }
+                }
+                if (tr.autoRemoveRecents && tr.getTopActivity() == null) {
+                    // Don't include auto remove tasks that are finished or finishing.
+                    if (DEBUG_RECENTS) Slog.d(TAG_RECENTS,
+                            "Skipping, auto-remove without activity: " + tr);
+                    continue;
+                }
+                if ((flags & RECENT_IGNORE_UNAVAILABLE) != 0 && !tr.isAvailable) {
+                    if (DEBUG_RECENTS) Slog.d(TAG_RECENTS,
+                            "Skipping, unavail real act: " + tr);
+                    continue;
+                }
+
+                if (!tr.mUserSetupComplete) {
+                    // Don't include task launched while user is not done setting-up.
+                    if (DEBUG_RECENTS) Slog.d(TAG_RECENTS,
+                            "Skipping, user setup not complete: " + tr);
+                    continue;
+                }
+
+                ActivityManager.RecentTaskInfo rti = RecentTasks.createRecentTaskInfo(tr);
+                if (!getDetailedTasks) {
+                    rti.baseIntent.replaceExtras((Bundle)null);
+                }
+
+                res.add(rti);
+            }
+        }
+        return new ParceledListSlice<>(res);
+    }
+
+    /**
+     * @return the list of persistable task ids.
+     */
+    void getPersistableTaskIds(ArraySet<Integer> persistentTaskIds) {
+        final int size = mTasks.size();
+        for (int i = 0; i < size; i++) {
+            final TaskRecord task = mTasks.get(i);
+            if (TaskPersister.DEBUG) Slog.d(TAG, "LazyTaskWriter: task=" + task
+                    + " persistable=" + task.isPersistable);
+            final ActivityStack stack = task.getStack();
+            if ((task.isPersistable || task.inRecents)
+                    && (stack == null || !stack.isHomeOrRecentsStack())) {
+                if (TaskPersister.DEBUG) Slog.d(TAG, "adding to persistentTaskIds task=" + task);
+                persistentTaskIds.add(task.taskId);
+            } else {
+                if (TaskPersister.DEBUG) Slog.d(TAG, "omitting from persistentTaskIds task="
+                        + task);
+            }
+        }
+    }
+
+    @VisibleForTesting
+    ArrayList<TaskRecord> getRawTasks() {
+        return mTasks;
+    }
+
+    /**
+     * @return the task in the task list with the given {@param id} if one exists.
+     */
+    TaskRecord getTask(int id) {
+        final int recentsCount = mTasks.size();
+        for (int i = 0; i < recentsCount; i++) {
+            TaskRecord tr = mTasks.get(i);
+            if (tr.taskId == id) {
+                return tr;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Add a new task to the recent tasks list.
+     */
+    void add(TaskRecord task) {
+        if (DEBUG_RECENTS_TRIM_TASKS) Slog.d(TAG, "add: task=" + task);
+
+        final boolean isAffiliated = task.mAffiliatedTaskId != task.taskId
+                || task.mNextAffiliateTaskId != INVALID_TASK_ID
+                || task.mPrevAffiliateTaskId != INVALID_TASK_ID;
+
+        int recentsCount = mTasks.size();
+        // Quick case: never add voice sessions.
+        // TODO: VI what about if it's just an activity?
+        // Probably nothing to do here
+        if (task.voiceSession != null) {
+            if (DEBUG_RECENTS) Slog.d(TAG_RECENTS,
+                    "addRecent: not adding voice interaction " + task);
+            return;
+        }
+        // Another quick case: check if the top-most recent task is the same.
+        if (!isAffiliated && recentsCount > 0 && mTasks.get(0) == task) {
+            if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: already at top: " + task);
+            return;
+        }
+        // Another quick case: check if this is part of a set of affiliated
+        // tasks that are at the top.
+        if (isAffiliated && recentsCount > 0 && task.inRecents
+                && task.mAffiliatedTaskId == mTasks.get(0).mAffiliatedTaskId) {
+            if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: affiliated " + mTasks.get(0)
+                    + " at top when adding " + task);
+            return;
+        }
+
+        boolean needAffiliationFix = false;
+
+        // Slightly less quick case: the task is already in recents, so all we need
+        // to do is move it.
+        if (task.inRecents) {
+            int taskIndex = mTasks.indexOf(task);
+            if (taskIndex >= 0) {
+                if (!isAffiliated || !MOVE_AFFILIATED_TASKS_TO_FRONT) {
+                    // Simple case: this is not an affiliated task, so we just move it to the front.
+                    mTasks.remove(taskIndex);
+                    mTasks.add(0, task);
+                    notifyTaskPersisterLocked(task, false);
+                    if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: moving to top " + task
+                            + " from " + taskIndex);
+                    return;
+                } else {
+                    // More complicated: need to keep all affiliated tasks together.
+                    if (moveAffiliatedTasksToFront(task, taskIndex)) {
+                        // All went well.
+                        return;
+                    }
+
+                    // Uh oh...  something bad in the affiliation chain, try to rebuild
+                    // everything and then go through our general path of adding a new task.
+                    needAffiliationFix = true;
+                }
+            } else {
+                Slog.wtf(TAG, "Task with inRecent not in recents: " + task);
+                needAffiliationFix = true;
+            }
+        }
+
+        if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: trimming tasks for " + task);
+        trimForAddTask(task);
+
+        task.inRecents = true;
+        if (!isAffiliated || needAffiliationFix) {
+            // If this is a simple non-affiliated task, or we had some failure trying to
+            // handle it as part of an affilated task, then just place it at the top.
+            mTasks.add(0, task);
+            notifyTaskAdded(task);
+            if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: adding " + task);
+        } else if (isAffiliated) {
+            // If this is a new affiliated task, then move all of the affiliated tasks
+            // to the front and insert this new one.
+            TaskRecord other = task.mNextAffiliate;
+            if (other == null) {
+                other = task.mPrevAffiliate;
+            }
+            if (other != null) {
+                int otherIndex = mTasks.indexOf(other);
+                if (otherIndex >= 0) {
+                    // Insert new task at appropriate location.
+                    int taskIndex;
+                    if (other == task.mNextAffiliate) {
+                        // We found the index of our next affiliation, which is who is
+                        // before us in the list, so add after that point.
+                        taskIndex = otherIndex+1;
+                    } else {
+                        // We found the index of our previous affiliation, which is who is
+                        // after us in the list, so add at their position.
+                        taskIndex = otherIndex;
+                    }
+                    if (DEBUG_RECENTS) Slog.d(TAG_RECENTS,
+                            "addRecent: new affiliated task added at " + taskIndex + ": " + task);
+                    mTasks.add(taskIndex, task);
+                    notifyTaskAdded(task);
+
+                    // Now move everything to the front.
+                    if (moveAffiliatedTasksToFront(task, taskIndex)) {
+                        // All went well.
+                        return;
+                    }
+
+                    // Uh oh...  something bad in the affiliation chain, try to rebuild
+                    // everything and then go through our general path of adding a new task.
+                    needAffiliationFix = true;
+                } else {
+                    if (DEBUG_RECENTS) Slog.d(TAG_RECENTS,
+                            "addRecent: couldn't find other affiliation " + other);
+                    needAffiliationFix = true;
+                }
+            } else {
+                if (DEBUG_RECENTS) Slog.d(TAG_RECENTS,
+                        "addRecent: adding affiliated task without next/prev:" + task);
+                needAffiliationFix = true;
+            }
+        }
+
+        if (needAffiliationFix) {
+            if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: regrouping affiliations");
+            cleanupLocked(task.userId);
+        }
+
+        // Trim the set of tasks to the active set
+        trimInactiveRecentTasks();
+    }
+
+    /**
+     * Add the task to the bottom if possible.
+     */
+    boolean addToBottom(TaskRecord task) {
+        if (!canAddTaskWithoutTrim(task)) {
+            // Adding this task would cause the task to be removed (since it's appended at
+            // the bottom and would be trimmed) so just return now
+            return false;
+        }
+
+        add(task);
+        return true;
+    }
+
+    /**
+     * Remove a task from the recent tasks list.
+     */
+    void remove(TaskRecord task) {
+        mTasks.remove(task);
+        notifyTaskRemoved(task, !TRIMMED);
+    }
+
+    /**
+     * Trims the recents task list to the global max number of recents.
+     */
+    private void trimInactiveRecentTasks() {
+        int recentsCount = mTasks.size();
+
+        // Remove from the end of the list until we reach the max number of recents
+        while (recentsCount > mGlobalMaxNumTasks) {
+            final TaskRecord tr = mTasks.remove(recentsCount - 1);
+            notifyTaskRemoved(tr, TRIMMED);
+            recentsCount--;
+            if (DEBUG_RECENTS_TRIM_TASKS) Slog.d(TAG, "Trimming over max-recents task=" + tr
+                    + " max=" + mGlobalMaxNumTasks);
+        }
+
+        // Remove any tasks that belong to currently quiet profiles
+        final int[] profileUserIds = mUserController.getCurrentProfileIds();
+        mTmpQuietProfileUserIds.clear();
+        for (int userId : profileUserIds) {
+            final UserInfo userInfo = mUserController.getUserInfo(userId);
+            if (userInfo != null && userInfo.isManagedProfile() && userInfo.isQuietModeEnabled()) {
+                mTmpQuietProfileUserIds.put(userId, true);
+            }
+            if (DEBUG_RECENTS_TRIM_TASKS) Slog.d(TAG, "User: " + userInfo
+                    + " quiet=" + mTmpQuietProfileUserIds.get(userId));
+        }
+
+        // Remove any inactive tasks, calculate the latest set of visible tasks
+        int numVisibleTasks = 0;
+        for (int i = 0; i < mTasks.size();) {
+            final TaskRecord task = mTasks.get(i);
+
+            if (isActiveRecentTask(task, mTmpQuietProfileUserIds)) {
+                if (!mHasVisibleRecentTasks) {
+                    // Keep all active tasks if visible recent tasks is not supported
+                    i++;
+                    continue;
+                }
+
+                if (!isVisibleRecentTask(task)) {
+                    // Keep all active-but-invisible tasks
+                    i++;
+                    continue;
+                } else {
+                    numVisibleTasks++;
+                    if (isInVisibleRange(task, numVisibleTasks)) {
+                        // Keep visible tasks in range
+                        i++;
+                        continue;
+                    } else {
+                        // Fall through to trim visible tasks that are no longer in range
+                        if (DEBUG_RECENTS_TRIM_TASKS) Slog.d(TAG,
+                                "Trimming out-of-range visible task=" + task);
+                    }
+                }
+            } else {
+                // Fall through to trim inactive tasks
+                if (DEBUG_RECENTS_TRIM_TASKS) Slog.d(TAG, "Trimming inactive task=" + task);
+            }
+
+            // Task is no longer active, trim it from the list
+            mTasks.remove(task);
+            notifyTaskRemoved(task, TRIMMED);
+            notifyTaskPersisterLocked(task, false /* flush */);
+        }
+    }
+
+    /**
+     * @return whether the given task should be considered active.
+     */
+    private boolean isActiveRecentTask(TaskRecord task, SparseBooleanArray quietProfileUserIds) {
+        if (DEBUG_RECENTS_TRIM_TASKS) Slog.d(TAG, "isActiveRecentTask: task=" + task
+                + " globalMax=" + mGlobalMaxNumTasks);
+
+        if (quietProfileUserIds.get(task.userId)) {
+            // Quiet profile user's tasks are never active
+            if (DEBUG_RECENTS_TRIM_TASKS) Slog.d(TAG, "\tisQuietProfileTask=true");
+            return false;
+        }
+
+        if (task.mAffiliatedTaskId != INVALID_TASK_ID && task.mAffiliatedTaskId != task.taskId) {
+            // Keep the task active if its affiliated task is also active
+            final TaskRecord affiliatedTask = getTask(task.mAffiliatedTaskId);
+            if (affiliatedTask != null) {
+                if (!isActiveRecentTask(affiliatedTask, quietProfileUserIds)) {
+                    if (DEBUG_RECENTS_TRIM_TASKS) Slog.d(TAG,
+                            "\taffiliatedWithTask=" + affiliatedTask + " is not active");
+                    return false;
+                }
+            }
+        }
+
+        // All other tasks are considered active
+        return true;
+    }
+
+    /**
+     * @return whether the given active task should be presented to the user through SystemUI.
+     */
+    private boolean isVisibleRecentTask(TaskRecord task) {
+        if (DEBUG_RECENTS_TRIM_TASKS) Slog.d(TAG, "isVisibleRecentTask: task=" + task
+                + " minVis=" + mMinNumVisibleTasks + " maxVis=" + mMaxNumVisibleTasks
+                + " sessionDuration=" + mActiveTasksSessionDurationMs
+                + " inactiveDuration=" + task.getInactiveDuration()
+                + " activityType=" + task.getActivityType()
+                + " windowingMode=" + task.getWindowingMode());
+
+        // Ignore certain activity types completely
+        switch (task.getActivityType()) {
+            case ACTIVITY_TYPE_HOME:
+            case ACTIVITY_TYPE_RECENTS:
+                return false;
+        }
+
+        // Ignore certain windowing modes
+        switch (task.getWindowingMode()) {
+            case WINDOWING_MODE_PINNED:
+                return false;
+            case WINDOWING_MODE_SPLIT_SCREEN_PRIMARY:
+                if (DEBUG_RECENTS_TRIM_TASKS) Slog.d(TAG, "\ttop=" + task.getStack().topTask());
+                final ActivityStack stack = task.getStack();
+                if (stack != null && stack.topTask() == task) {
+                    // Only the non-top task of the primary split screen mode is visible
+                    return false;
+                }
+        }
+
+        return true;
+    }
+
+    /**
+     * @return whether the given visible task is within the policy range.
+     */
+    private boolean isInVisibleRange(TaskRecord task, int numVisibleTasks) {
+        // Keep the last most task even if it is excluded from recents
+        final boolean isExcludeFromRecents =
+                (task.getBaseIntent().getFlags() & Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
+                        == Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
+        if (isExcludeFromRecents) {
+            if (DEBUG_RECENTS_TRIM_TASKS) Slog.d(TAG, "\texcludeFromRecents=true");
+            return numVisibleTasks == 1;
+        }
+
+        if (mMinNumVisibleTasks >= 0 && numVisibleTasks <= mMinNumVisibleTasks) {
+            // Always keep up to the min number of recent tasks, after that fall through to the
+            // checks below
+            return true;
+        }
+
+        if (mMaxNumVisibleTasks >= 0) {
+            // Always keep up to the max number of recent tasks, but return false afterwards
+            return numVisibleTasks <= mMaxNumVisibleTasks;
+        }
+
+        if (mActiveTasksSessionDurationMs > 0) {
+            // Keep the task if the inactive time is within the session window, this check must come
+            // after the checks for the min/max visible task range
+            if (task.getInactiveDuration() <= mActiveTasksSessionDurationMs) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * If needed, remove oldest existing entries in recents that are for the same kind
+     * of task as the given one.
+     */
+    private void trimForAddTask(TaskRecord task) {
+        final int removeIndex = findTrimIndexForAddTask(task);
+        if (removeIndex == -1) {
+            // Nothing to trim
+            return;
+        }
+
+        // There is a similar task that will be removed for the addition of {@param task}, but it
+        // can be the same task, and if so, the task will be re-added in add(), so skip the
+        // callbacks here.
+        final TaskRecord removedTask = mTasks.remove(removeIndex);
+        if (removedTask != task) {
+            notifyTaskRemoved(removedTask, TRIMMED);
+            if (DEBUG_RECENTS_TRIM_TASKS) Slog.d(TAG, "Trimming task=" + removedTask
+                    + " for addition of task=" + task);
+        }
+        notifyTaskPersisterLocked(removedTask, false /* flush */);
+    }
+
+    /**
+     * Find the task that would be removed if the given {@param task} is added to the recent tasks
+     * list (if any).
+     */
+    private int findTrimIndexForAddTask(TaskRecord task) {
+        int recentsCount = mTasks.size();
+        final Intent intent = task.intent;
+        final boolean document = intent != null && intent.isDocument();
+        int maxRecents = task.maxRecents - 1;
+        final ActivityStack stack = task.getStack();
+        for (int i = 0; i < recentsCount; i++) {
+            final TaskRecord tr = mTasks.get(i);
+            final ActivityStack trStack = tr.getStack();
+
+            if (task != tr) {
+                if (stack != null && trStack != null && stack != trStack) {
+                    continue;
+                }
+                if (task.userId != tr.userId) {
+                    continue;
+                }
+                final Intent trIntent = tr.intent;
+                final boolean sameAffinity =
+                        task.affinity != null && task.affinity.equals(tr.affinity);
+                final boolean sameIntent = intent != null && intent.filterEquals(trIntent);
+                boolean multiTasksAllowed = false;
+                final int flags = intent.getFlags();
+                if ((flags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_NEW_DOCUMENT)) != 0
+                        && (flags & FLAG_ACTIVITY_MULTIPLE_TASK) != 0) {
+                    multiTasksAllowed = true;
+                }
+                final boolean trIsDocument = trIntent != null && trIntent.isDocument();
+                final boolean bothDocuments = document && trIsDocument;
+                if (!sameAffinity && !sameIntent && !bothDocuments) {
+                    continue;
+                }
+
+                if (bothDocuments) {
+                    // Do these documents belong to the same activity?
+                    final boolean sameActivity = task.realActivity != null
+                            && tr.realActivity != null
+                            && task.realActivity.equals(tr.realActivity);
+                    if (!sameActivity) {
+                        // If the document is open in another app or is not the same document, we
+                        // don't need to trim it.
+                        continue;
+                    } else if (maxRecents > 0) {
+                        // Otherwise only trim if we are over our max recents for this task
+                        --maxRecents;
+                        if (!sameIntent || multiTasksAllowed) {
+                            // We don't want to trim if we are not over the max allowed entries and
+                            // the tasks are not of the same intent filter, or multiple entries for
+                            // the task is allowed.
+                            continue;
+                        }
+                    }
+                    // Hit the maximum number of documents for this task. Fall through
+                    // and remove this document from recents.
+                } else if (document || trIsDocument) {
+                    // Only one of these is a document. Not the droid we're looking for.
+                    continue;
+                }
+            }
+            return i;
+        }
+        return -1;
+    }
+
+    // Extract the affiliates of the chain containing recent at index start.
+    private int processNextAffiliateChainLocked(int start) {
+        final TaskRecord startTask = mTasks.get(start);
+        final int affiliateId = startTask.mAffiliatedTaskId;
+
+        // Quick identification of isolated tasks. I.e. those not launched behind.
+        if (startTask.taskId == affiliateId && startTask.mPrevAffiliate == null &&
+                startTask.mNextAffiliate == null) {
+            // There is still a slim chance that there are other tasks that point to this task
+            // and that the chain is so messed up that this task no longer points to them but
+            // the gain of this optimization outweighs the risk.
+            startTask.inRecents = true;
+            return start + 1;
+        }
+
+        // Remove all tasks that are affiliated to affiliateId and put them in mTmpRecents.
+        mTmpRecents.clear();
+        for (int i = mTasks.size() - 1; i >= start; --i) {
+            final TaskRecord task = mTasks.get(i);
+            if (task.mAffiliatedTaskId == affiliateId) {
+                mTasks.remove(i);
+                mTmpRecents.add(task);
+            }
+        }
+
+        // Sort them all by taskId. That is the order they were create in and that order will
+        // always be correct.
+        Collections.sort(mTmpRecents, TASK_ID_COMPARATOR);
+
+        // Go through and fix up the linked list.
+        // The first one is the end of the chain and has no next.
+        final TaskRecord first = mTmpRecents.get(0);
+        first.inRecents = true;
+        if (first.mNextAffiliate != null) {
+            Slog.w(TAG, "Link error 1 first.next=" + first.mNextAffiliate);
+            first.setNextAffiliate(null);
+            notifyTaskPersisterLocked(first, false);
+        }
+        // Everything in the middle is doubly linked from next to prev.
+        final int tmpSize = mTmpRecents.size();
+        for (int i = 0; i < tmpSize - 1; ++i) {
+            final TaskRecord next = mTmpRecents.get(i);
+            final TaskRecord prev = mTmpRecents.get(i + 1);
+            if (next.mPrevAffiliate != prev) {
+                Slog.w(TAG, "Link error 2 next=" + next + " prev=" + next.mPrevAffiliate +
+                        " setting prev=" + prev);
+                next.setPrevAffiliate(prev);
+                notifyTaskPersisterLocked(next, false);
+            }
+            if (prev.mNextAffiliate != next) {
+                Slog.w(TAG, "Link error 3 prev=" + prev + " next=" + prev.mNextAffiliate +
+                        " setting next=" + next);
+                prev.setNextAffiliate(next);
+                notifyTaskPersisterLocked(prev, false);
+            }
+            prev.inRecents = true;
+        }
+        // The last one is the beginning of the list and has no prev.
+        final TaskRecord last = mTmpRecents.get(tmpSize - 1);
+        if (last.mPrevAffiliate != null) {
+            Slog.w(TAG, "Link error 4 last.prev=" + last.mPrevAffiliate);
+            last.setPrevAffiliate(null);
+            notifyTaskPersisterLocked(last, false);
+        }
+
+        // Insert the group back into mTmpTasks at start.
+        mTasks.addAll(start, mTmpRecents);
+        mTmpRecents.clear();
+
+        // Let the caller know where we left off.
+        return start + tmpSize;
+    }
+
+    private boolean moveAffiliatedTasksToFront(TaskRecord task, int taskIndex) {
+        int recentsCount = mTasks.size();
         TaskRecord top = task;
         int topIndex = taskIndex;
         while (top.mNextAffiliate != null && topIndex > 0) {
@@ -412,7 +1238,7 @@
         int endIndex = topIndex;
         TaskRecord prev = top;
         while (endIndex < recentsCount) {
-            TaskRecord cur = get(endIndex);
+            TaskRecord cur = mTasks.get(endIndex);
             if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: looking at next chain @"
                     + endIndex + " " + cur);
             if (cur == top) {
@@ -487,8 +1313,8 @@
             for (int i=topIndex; i<=endIndex; i++) {
                 if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: moving affiliated " + task
                         + " from " + i + " to " + (i-topIndex));
-                TaskRecord cur = remove(i);
-                add(i - topIndex, cur);
+                TaskRecord cur = mTasks.remove(i);
+                mTasks.add(i - topIndex, cur);
             }
             if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: done moving tasks  " +  topIndex
                     + " to " + endIndex);
@@ -499,301 +1325,87 @@
         return false;
     }
 
-    final void addLocked(TaskRecord task) {
-        final boolean isAffiliated = task.mAffiliatedTaskId != task.taskId
-                || task.mNextAffiliateTaskId != INVALID_TASK_ID
-                || task.mPrevAffiliateTaskId != INVALID_TASK_ID;
-
-        int recentsCount = size();
-        // Quick case: never add voice sessions.
-        // TODO: VI what about if it's just an activity?
-        // Probably nothing to do here
-        if (task.voiceSession != null) {
-            if (DEBUG_RECENTS) Slog.d(TAG_RECENTS,
-                    "addRecent: not adding voice interaction " + task);
-            return;
-        }
-        // Another quick case: check if the top-most recent task is the same.
-        if (!isAffiliated && recentsCount > 0 && get(0) == task) {
-            if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: already at top: " + task);
-            return;
-        }
-        // Another quick case: check if this is part of a set of affiliated
-        // tasks that are at the top.
-        if (isAffiliated && recentsCount > 0 && task.inRecents
-                && task.mAffiliatedTaskId == get(0).mAffiliatedTaskId) {
-            if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: affiliated " + get(0)
-                    + " at top when adding " + task);
+    void dump(PrintWriter pw, boolean dumpAll, String dumpPackage) {
+        pw.println("ACTIVITY MANAGER RECENT TASKS (dumpsys activity recents)");
+        if (mTasks.isEmpty()) {
             return;
         }
 
-        boolean needAffiliationFix = false;
+        final MutableBoolean printedAnything = new MutableBoolean(false);
+        final MutableBoolean printedHeader = new MutableBoolean(false);
+        final int size = mTasks.size();
+        for (int i = 0; i < size; i++) {
+            final TaskRecord tr = mTasks.get(i);
+            if (dumpPackage != null && (tr.realActivity == null ||
+                    !dumpPackage.equals(tr.realActivity.getPackageName()))) {
+                continue;
+            }
 
-        // Slightly less quick case: the task is already in recents, so all we need
-        // to do is move it.
-        if (task.inRecents) {
-            int taskIndex = indexOf(task);
-            if (taskIndex >= 0) {
-                if (!isAffiliated || MOVE_AFFILIATED_TASKS_TO_FRONT) {
-                    // Simple case: this is not an affiliated task, so we just move it to the front.
-                    remove(taskIndex);
-                    add(0, task);
-                    notifyTaskPersisterLocked(task, false);
-                    if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: moving to top " + task
-                            + " from " + taskIndex);
-                    return;
-                } else {
-                    // More complicated: need to keep all affiliated tasks together.
-                    if (moveAffiliatedTasksToFront(task, taskIndex)) {
-                        // All went well.
-                        return;
-                    }
-
-                    // Uh oh...  something bad in the affiliation chain, try to rebuild
-                    // everything and then go through our general path of adding a new task.
-                    needAffiliationFix = true;
-                }
-            } else {
-                Slog.wtf(TAG, "Task with inRecent not in recents: " + task);
-                needAffiliationFix = true;
+            if (!printedHeader.value) {
+                pw.println("  Recent tasks:");
+                printedHeader.value = true;
+                printedAnything.value = true;
+            }
+            pw.print("  * Recent #"); pw.print(i); pw.print(": ");
+            pw.println(tr);
+            if (dumpAll) {
+                tr.dump(pw, "    ");
             }
         }
 
-        if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: trimming tasks for " + task);
-        trimForTaskLocked(task, true);
-
-        recentsCount = size();
-        final int maxRecents = ActivityManager.getMaxRecentTasksStatic();
-        while (recentsCount >= maxRecents) {
-            final TaskRecord tr = remove(recentsCount - 1);
-            tr.removedFromRecents();
-            recentsCount--;
-        }
-        task.inRecents = true;
-        if (!isAffiliated || needAffiliationFix) {
-            // If this is a simple non-affiliated task, or we had some failure trying to
-            // handle it as part of an affilated task, then just place it at the top.
-            add(0, task);
-            if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: adding " + task);
-        } else if (isAffiliated) {
-            // If this is a new affiliated task, then move all of the affiliated tasks
-            // to the front and insert this new one.
-            TaskRecord other = task.mNextAffiliate;
-            if (other == null) {
-                other = task.mPrevAffiliate;
-            }
-            if (other != null) {
-                int otherIndex = indexOf(other);
-                if (otherIndex >= 0) {
-                    // Insert new task at appropriate location.
-                    int taskIndex;
-                    if (other == task.mNextAffiliate) {
-                        // We found the index of our next affiliation, which is who is
-                        // before us in the list, so add after that point.
-                        taskIndex = otherIndex+1;
-                    } else {
-                        // We found the index of our previous affiliation, which is who is
-                        // after us in the list, so add at their position.
-                        taskIndex = otherIndex;
-                    }
-                    if (DEBUG_RECENTS) Slog.d(TAG_RECENTS,
-                            "addRecent: new affiliated task added at " + taskIndex + ": " + task);
-                    add(taskIndex, task);
-
-                    // Now move everything to the front.
-                    if (moveAffiliatedTasksToFront(task, taskIndex)) {
-                        // All went well.
-                        return;
-                    }
-
-                    // Uh oh...  something bad in the affiliation chain, try to rebuild
-                    // everything and then go through our general path of adding a new task.
-                    needAffiliationFix = true;
-                } else {
-                    if (DEBUG_RECENTS) Slog.d(TAG_RECENTS,
-                            "addRecent: couldn't find other affiliation " + other);
-                    needAffiliationFix = true;
-                }
-            } else {
-                if (DEBUG_RECENTS) Slog.d(TAG_RECENTS,
-                        "addRecent: adding affiliated task without next/prev:" + task);
-                needAffiliationFix = true;
-            }
-        }
-
-        if (needAffiliationFix) {
-            if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: regrouping affiliations");
-            cleanupLocked(task.userId);
+        if (!printedAnything.value) {
+            pw.println("  (nothing)");
         }
     }
 
     /**
-     * If needed, remove oldest existing entries in recents that are for the same kind
-     * of task as the given one.
+     * Creates a new RecentTaskInfo from a TaskRecord.
      */
-    int trimForTaskLocked(TaskRecord task, boolean doTrim) {
-        int recentsCount = size();
-        final Intent intent = task.intent;
-        final boolean document = intent != null && intent.isDocument();
-        int maxRecents = task.maxRecents - 1;
-        final ActivityStack stack = task.getStack();
-        for (int i = 0; i < recentsCount; i++) {
-            final TaskRecord tr = get(i);
-            final ActivityStack trStack = tr.getStack();
-            if (task != tr) {
-                if (stack != null && trStack != null && stack != trStack) {
-                    continue;
-                }
-                if (task.userId != tr.userId) {
-                    continue;
-                }
-                final Intent trIntent = tr.intent;
-                final boolean sameAffinity =
-                        task.affinity != null && task.affinity.equals(tr.affinity);
-                final boolean sameIntentFilter = intent != null && intent.filterEquals(trIntent);
-                boolean multiTasksAllowed = false;
-                final int flags = intent.getFlags();
-                if ((flags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_NEW_DOCUMENT)) != 0
-                        && (flags & FLAG_ACTIVITY_MULTIPLE_TASK) != 0) {
-                    multiTasksAllowed = true;
-                }
-                final boolean trIsDocument = trIntent != null && trIntent.isDocument();
-                final boolean bothDocuments = document && trIsDocument;
-                if (!sameAffinity && !sameIntentFilter && !bothDocuments) {
-                    continue;
-                }
+    static ActivityManager.RecentTaskInfo createRecentTaskInfo(TaskRecord tr) {
+        // Update the task description to reflect any changes in the task stack
+        tr.updateTaskDescription();
 
-                if (bothDocuments) {
-                    // Do these documents belong to the same activity?
-                    final boolean sameActivity = task.realActivity != null
-                            && tr.realActivity != null
-                            && task.realActivity.equals(tr.realActivity);
-                    // If the document is open in another app or is not the same
-                    // document, we don't need to trim it.
-                    if (!sameActivity) {
-                        continue;
-                    // Otherwise only trim if we are over our max recents for this task
-                    } else if (maxRecents > 0) {
-                        --maxRecents;
-                        if (!doTrim || !sameIntentFilter || multiTasksAllowed) {
-                            // We don't want to trim if we are not over the max allowed entries and
-                            // the caller doesn't want us to trim, the tasks are not of the same
-                            // intent filter, or multiple entries fot the task is allowed.
-                            continue;
-                        }
-                    }
-                    // Hit the maximum number of documents for this task. Fall through
-                    // and remove this document from recents.
-                } else if (document || trIsDocument) {
-                    // Only one of these is a document. Not the droid we're looking for.
-                    continue;
-                }
-            }
+        // Compose the recent task info
+        ActivityManager.RecentTaskInfo rti = new ActivityManager.RecentTaskInfo();
+        rti.id = tr.getTopActivity() == null ? INVALID_TASK_ID : tr.taskId;
+        rti.persistentId = tr.taskId;
+        rti.baseIntent = new Intent(tr.getBaseIntent());
+        rti.origActivity = tr.origActivity;
+        rti.realActivity = tr.realActivity;
+        rti.description = tr.lastDescription;
+        rti.stackId = tr.getStackId();
+        rti.userId = tr.userId;
+        rti.taskDescription = new ActivityManager.TaskDescription(tr.lastTaskDescription);
+        rti.lastActiveTime = tr.lastActiveTime;
+        rti.affiliatedTaskId = tr.mAffiliatedTaskId;
+        rti.affiliatedTaskColor = tr.mAffiliatedTaskColor;
+        rti.numActivities = 0;
+        if (tr.mBounds != null) {
+            rti.bounds = new Rect(tr.mBounds);
+        }
+        rti.supportsSplitScreenMultiWindow = tr.supportsSplitScreenWindowingMode();
+        rti.resizeMode = tr.mResizeMode;
+        rti.configuration.setTo(tr.getConfiguration());
 
-            if (!doTrim) {
-                // If the caller is not actually asking for a trim, just tell them we reached
-                // a point where the trim would happen.
-                return i;
-            }
+        ActivityRecord base = null;
+        ActivityRecord top = null;
+        ActivityRecord tmp;
 
-            // Either task and tr are the same or, their affinities match or their intents match
-            // and neither of them is a document, or they are documents using the same activity
-            // and their maxRecents has been reached.
-            remove(i);
-            if (task != tr) {
-                tr.removedFromRecents();
+        for (int i = tr.mActivities.size() - 1; i >= 0; --i) {
+            tmp = tr.mActivities.get(i);
+            if (tmp.finishing) {
+                continue;
             }
-            i--;
-            recentsCount--;
-            if (task.intent == null) {
-                // If the new recent task we are adding is not fully
-                // specified, then replace it with the existing recent task.
-                task = tr;
+            base = tmp;
+            if (top == null || (top.state == ActivityState.INITIALIZING)) {
+                top = base;
             }
-            notifyTaskPersisterLocked(tr, false);
+            rti.numActivities++;
         }
 
-        return -1;
-    }
+        rti.baseActivity = (base != null) ? base.intent.getComponent() : null;
+        rti.topActivity = (top != null) ? top.intent.getComponent() : null;
 
-    // Sort by taskId
-    private static Comparator<TaskRecord> sTaskRecordComparator = new Comparator<TaskRecord>() {
-        @Override
-        public int compare(TaskRecord lhs, TaskRecord rhs) {
-            return rhs.taskId - lhs.taskId;
-        }
-    };
-
-    // Extract the affiliates of the chain containing recent at index start.
-    private int processNextAffiliateChainLocked(int start) {
-        final TaskRecord startTask = get(start);
-        final int affiliateId = startTask.mAffiliatedTaskId;
-
-        // Quick identification of isolated tasks. I.e. those not launched behind.
-        if (startTask.taskId == affiliateId && startTask.mPrevAffiliate == null &&
-                startTask.mNextAffiliate == null) {
-            // There is still a slim chance that there are other tasks that point to this task
-            // and that the chain is so messed up that this task no longer points to them but
-            // the gain of this optimization outweighs the risk.
-            startTask.inRecents = true;
-            return start + 1;
-        }
-
-        // Remove all tasks that are affiliated to affiliateId and put them in mTmpRecents.
-        mTmpRecents.clear();
-        for (int i = size() - 1; i >= start; --i) {
-            final TaskRecord task = get(i);
-            if (task.mAffiliatedTaskId == affiliateId) {
-                remove(i);
-                mTmpRecents.add(task);
-            }
-        }
-
-        // Sort them all by taskId. That is the order they were create in and that order will
-        // always be correct.
-        Collections.sort(mTmpRecents, sTaskRecordComparator);
-
-        // Go through and fix up the linked list.
-        // The first one is the end of the chain and has no next.
-        final TaskRecord first = mTmpRecents.get(0);
-        first.inRecents = true;
-        if (first.mNextAffiliate != null) {
-            Slog.w(TAG, "Link error 1 first.next=" + first.mNextAffiliate);
-            first.setNextAffiliate(null);
-            notifyTaskPersisterLocked(first, false);
-        }
-        // Everything in the middle is doubly linked from next to prev.
-        final int tmpSize = mTmpRecents.size();
-        for (int i = 0; i < tmpSize - 1; ++i) {
-            final TaskRecord next = mTmpRecents.get(i);
-            final TaskRecord prev = mTmpRecents.get(i + 1);
-            if (next.mPrevAffiliate != prev) {
-                Slog.w(TAG, "Link error 2 next=" + next + " prev=" + next.mPrevAffiliate +
-                        " setting prev=" + prev);
-                next.setPrevAffiliate(prev);
-                notifyTaskPersisterLocked(next, false);
-            }
-            if (prev.mNextAffiliate != next) {
-                Slog.w(TAG, "Link error 3 prev=" + prev + " next=" + prev.mNextAffiliate +
-                        " setting next=" + next);
-                prev.setNextAffiliate(next);
-                notifyTaskPersisterLocked(prev, false);
-            }
-            prev.inRecents = true;
-        }
-        // The last one is the beginning of the list and has no prev.
-        final TaskRecord last = mTmpRecents.get(tmpSize - 1);
-        if (last.mPrevAffiliate != null) {
-            Slog.w(TAG, "Link error 4 last.prev=" + last.mPrevAffiliate);
-            last.setPrevAffiliate(null);
-            notifyTaskPersisterLocked(last, false);
-        }
-
-        // Insert the group back into mRecentTasks at start.
-        addAll(start, mTmpRecents);
-        mTmpRecents.clear();
-
-        // Let the caller know where we left off.
-        return start + tmpSize;
+        return rti;
     }
 }
diff --git a/com/android/server/am/ServiceRecord.java b/com/android/server/am/ServiceRecord.java
index ac85e6b..16995e5 100644
--- a/com/android/server/am/ServiceRecord.java
+++ b/com/android/server/am/ServiceRecord.java
@@ -33,6 +33,7 @@
 import android.content.pm.ServiceInfo;
 import android.net.Uri;
 import android.os.Binder;
+import android.os.Build;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.SystemClock;
@@ -517,14 +518,27 @@
                             } catch (PackageManager.NameNotFoundException e) {
                             }
                         }
-                        if (localForegroundNoti.getSmallIcon() == null
-                                || nm.getNotificationChannel(localPackageName, appUid,
+                        if (nm.getNotificationChannel(localPackageName, appUid,
                                 localForegroundNoti.getChannelId()) == null) {
+                            int targetSdkVersion = Build.VERSION_CODES.O_MR1;
+                            try {
+                                final ApplicationInfo applicationInfo =
+                                        ams.mContext.getPackageManager().getApplicationInfoAsUser(
+                                                appInfo.packageName, 0, userId);
+                                targetSdkVersion = applicationInfo.targetSdkVersion;
+                            } catch (PackageManager.NameNotFoundException e) {
+                            }
+                            if (targetSdkVersion >= Build.VERSION_CODES.O_MR1) {
+                                throw new RuntimeException(
+                                        "invalid channel for service notification: "
+                                                + foregroundNoti);
+                            }
+                        }
+                        if (localForegroundNoti.getSmallIcon() == null) {
                             // Notifications whose icon is 0 are defined to not show
                             // a notification, silently ignoring it.  We don't want to
                             // just ignore it, we want to prevent the service from
                             // being foreground.
-                            // Also every notification needs a channel.
                             throw new RuntimeException("invalid service notification: "
                                     + foregroundNoti);
                         }
diff --git a/com/android/server/am/TaskPersister.java b/com/android/server/am/TaskPersister.java
index 61994b5..2689d6a 100644
--- a/com/android/server/am/TaskPersister.java
+++ b/com/android/server/am/TaskPersister.java
@@ -567,7 +567,7 @@
         SparseArray<SparseBooleanArray> changedTaskIdsPerUser = new SparseArray<>();
         synchronized (mService) {
             for (int userId : mRecentTasks.usersWithRecentsLoadedLocked()) {
-                SparseBooleanArray taskIdsToSave = mRecentTasks.mPersistedTaskIds.get(userId);
+                SparseBooleanArray taskIdsToSave = mRecentTasks.getTaskIdsForUser(userId);
                 SparseBooleanArray persistedIdsInFile = mTaskIdsInFile.get(userId);
                 if (persistedIdsInFile != null && persistedIdsInFile.equals(taskIdsToSave)) {
                     continue;
@@ -640,7 +640,7 @@
         @Override
         public void run() {
             Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
-            ArraySet<Integer> persistentTaskIds = new ArraySet<Integer>();
+            ArraySet<Integer> persistentTaskIds = new ArraySet<>();
             while (true) {
                 // We can't lock mService while holding TaskPersister.this, but we don't want to
                 // call removeObsoleteFiles every time through the loop, only the last time before
@@ -654,20 +654,7 @@
                     persistentTaskIds.clear();
                     synchronized (mService) {
                         if (DEBUG) Slog.d(TAG, "mRecents=" + mRecentTasks);
-                        for (int taskNdx = mRecentTasks.size() - 1; taskNdx >= 0; --taskNdx) {
-                            final TaskRecord task = mRecentTasks.get(taskNdx);
-                            if (DEBUG) Slog.d(TAG, "LazyTaskWriter: task=" + task +
-                                    " persistable=" + task.isPersistable);
-                            final ActivityStack stack = task.getStack();
-                            if ((task.isPersistable || task.inRecents)
-                                    && (stack == null || !stack.isHomeOrRecentsStack())) {
-                                if (DEBUG) Slog.d(TAG, "adding to persistentTaskIds task=" + task);
-                                persistentTaskIds.add(task.taskId);
-                            } else {
-                                if (DEBUG) Slog.d(TAG,
-                                        "omitting from persistentTaskIds task=" + task);
-                            }
-                        }
+                        mRecentTasks.getPersistableTaskIds(persistentTaskIds);
                         mService.mWindowManager.removeObsoleteTaskFiles(persistentTaskIds,
                                 mRecentTasks.usersWithRecentsLoadedLocked());
                     }
diff --git a/com/android/server/am/TaskRecord.java b/com/android/server/am/TaskRecord.java
index 0d8df79..c451235 100644
--- a/com/android/server/am/TaskRecord.java
+++ b/com/android/server/am/TaskRecord.java
@@ -18,10 +18,7 @@
 
 import static android.app.ActivityManager.RESIZE_MODE_FORCED;
 import static android.app.ActivityManager.RESIZE_MODE_SYSTEM;
-import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
 import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
-import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
@@ -31,6 +28,7 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
 import static android.content.Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS;
 import static android.content.pm.ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY;
@@ -45,7 +43,6 @@
 import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
 import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_AND_PIPABLE_DEPRECATED;
 import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION;
-import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
 import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
 import static android.provider.Settings.Secure.USER_SETUP_COMPLETE;
 import static android.view.Display.DEFAULT_DISPLAY;
@@ -63,6 +60,7 @@
 import static com.android.server.am.ActivityRecord.STARTING_WINDOW_SHOWN;
 import static com.android.server.am.ActivityStack.REMOVE_TASK_MODE_MOVING;
 import static com.android.server.am.ActivityStack.REMOVE_TASK_MODE_MOVING_TO_TOP;
+import static com.android.server.am.ActivityStackSupervisor.ON_TOP;
 import static com.android.server.am.ActivityStackSupervisor.PAUSE_IMMEDIATELY;
 import static com.android.server.am.ActivityStackSupervisor.PRESERVE_WINDOWS;
 import static com.android.server.am.proto.TaskRecordProto.ACTIVITIES;
@@ -86,7 +84,6 @@
 import android.annotation.Nullable;
 import android.app.Activity;
 import android.app.ActivityManager;
-import android.app.ActivityManager.StackId;
 import android.app.ActivityManager.TaskDescription;
 import android.app.ActivityManager.TaskSnapshot;
 import android.app.ActivityOptions;
@@ -102,6 +99,7 @@
 import android.graphics.Rect;
 import android.os.Debug;
 import android.os.RemoteException;
+import android.os.SystemClock;
 import android.os.Trace;
 import android.os.UserHandle;
 import android.provider.Settings;
@@ -155,8 +153,6 @@
     private static final String ATTR_EFFECTIVE_UID = "effective_uid";
     @Deprecated
     private static final String ATTR_TASKTYPE = "task_type";
-    private static final String ATTR_FIRSTACTIVETIME = "first_active_time";
-    private static final String ATTR_LASTACTIVETIME = "last_active_time";
     private static final String ATTR_LASTDESCRIPTION = "last_description";
     private static final String ATTR_LASTTIMEMOVED = "last_time_moved";
     private static final String ATTR_NEVERRELINQUISH = "never_relinquish_identity";
@@ -168,7 +164,6 @@
     private static final String ATTR_CALLING_PACKAGE = "calling_package";
     private static final String ATTR_SUPPORTS_PICTURE_IN_PICTURE = "supports_picture_in_picture";
     private static final String ATTR_RESIZE_MODE = "resize_mode";
-    private static final String ATTR_PRIVILEGED = "privileged";
     private static final String ATTR_NON_FULLSCREEN_BOUNDS = "non_fullscreen_bounds";
     private static final String ATTR_MIN_WIDTH = "min_width";
     private static final String ATTR_MIN_HEIGHT = "min_height";
@@ -212,9 +207,10 @@
     ComponentName realActivity; // The actual activity component that started the task.
     boolean realActivitySuspended; // True if the actual activity component that started the
                                    // task is suspended.
-    long firstActiveTime;   // First time this task was active.
-    long lastActiveTime;    // Last time this task was active, including sleep.
     boolean inRecents;      // Actually in the recents list?
+    long lastActiveTime;    // Last time this task was active in the current device session,
+                            // including sleep. This time is initialized to the elapsed time when
+                            // restored from disk.
     boolean isAvailable;    // Is the activity available to be launched?
     boolean rootWasReset;   // True if the intent at the root of the task had
                             // the FLAG_ACTIVITY_RESET_TASK_IF_NEEDED flag.
@@ -237,10 +233,6 @@
             // of the root activity.
     boolean mTemporarilyUnresizable; // Separate flag from mResizeMode used to suppress resize
                                      // changes on a temporary basis.
-    private int mLockTaskMode;  // Which tasklock mode to launch this task in. One of
-                                // ActivityManager.LOCK_TASK_LAUNCH_MODE_*
-    private boolean mPrivileged;    // The root activity application of this task holds
-                                    // privileged permissions.
 
     /** Can't be put in lockTask mode. */
     final static int LOCK_TASK_AUTH_DONT_LOCK = 0;
@@ -339,6 +331,7 @@
                 TaskPersister.IMAGE_EXTENSION;
         userId = UserHandle.getUserId(info.applicationInfo.uid);
         taskId = _taskId;
+        lastActiveTime = SystemClock.elapsedRealtime();
         mAffiliatedTaskId = _taskId;
         voiceSession = _voiceSession;
         voiceInteractor = _voiceInteractor;
@@ -359,6 +352,7 @@
                 TaskPersister.IMAGE_EXTENSION;
         userId = UserHandle.getUserId(info.applicationInfo.uid);
         taskId = _taskId;
+        lastActiveTime = SystemClock.elapsedRealtime();
         mAffiliatedTaskId = _taskId;
         voiceSession = null;
         voiceInteractor = null;
@@ -385,12 +379,11 @@
             ComponentName _realActivity, ComponentName _origActivity, boolean _rootWasReset,
             boolean _autoRemoveRecents, boolean _askedCompatMode, int _userId,
             int _effectiveUid, String _lastDescription, ArrayList<ActivityRecord> activities,
-            long _firstActiveTime, long _lastActiveTime, long lastTimeMoved,
-            boolean neverRelinquishIdentity, TaskDescription _lastTaskDescription,
-            int taskAffiliation, int prevTaskId, int nextTaskId, int taskAffiliationColor,
-            int callingUid, String callingPackage, int resizeMode, boolean supportsPictureInPicture,
-            boolean privileged, boolean _realActivitySuspended, boolean userSetupComplete,
-            int minWidth, int minHeight) {
+            long lastTimeMoved, boolean neverRelinquishIdentity,
+            TaskDescription _lastTaskDescription, int taskAffiliation, int prevTaskId,
+            int nextTaskId, int taskAffiliationColor, int callingUid, String callingPackage,
+            int resizeMode, boolean supportsPictureInPicture, boolean _realActivitySuspended,
+            boolean userSetupComplete, int minWidth, int minHeight) {
         mService = service;
         mFilename = String.valueOf(_taskId) + TASK_THUMBNAIL_SUFFIX +
                 TaskPersister.IMAGE_EXTENSION;
@@ -412,8 +405,7 @@
         userId = _userId;
         mUserSetupComplete = userSetupComplete;
         effectiveUid = _effectiveUid;
-        firstActiveTime = _firstActiveTime;
-        lastActiveTime = _lastActiveTime;
+        lastActiveTime = SystemClock.elapsedRealtime();
         lastDescription = _lastDescription;
         mActivities = activities;
         mLastTimeMoved = lastTimeMoved;
@@ -427,7 +419,6 @@
         mCallingPackage = callingPackage;
         mResizeMode = resizeMode;
         mSupportsPictureInPicture = supportsPictureInPicture;
-        mPrivileged = privileged;
         mMinWidth = minWidth;
         mMinHeight = minHeight;
         mService.mTaskChangeNotificationController.notifyTaskCreated(_taskId, realActivity);
@@ -520,9 +511,9 @@
             // All we can do for now is update the bounds so it can be used when the task is
             // added to window manager.
             updateOverrideConfiguration(bounds);
-            if (getStackId() != FREEFORM_WORKSPACE_STACK_ID) {
+            if (!inFreeformWindowingMode()) {
                 // re-restore the task so it can have the proper stack association.
-                mService.mStackSupervisor.restoreRecentTaskLocked(this, null);
+                mService.mStackSupervisor.restoreRecentTaskLocked(this, null, !ON_TOP);
             }
             return true;
         }
@@ -616,8 +607,7 @@
      * @return whether the task was reparented
      */
     // TODO: Inspect all call sites and change to just changing windowing mode of the stack vs.
-    // re-parenting the task. Can only be done when we are no longer using static stack Ids like
-    /** {@link ActivityManager.StackId#FULLSCREEN_WORKSPACE_STACK_ID} */
+    // re-parenting the task. Can only be done when we are no longer using static stack Ids.
     boolean reparent(ActivityStack preferredStack, int position,
             @ReparentMoveStackMode int moveStackMode, boolean animate, boolean deferResume,
             boolean schedulePictureInPictureModeChange, String reason) {
@@ -630,12 +620,12 @@
             return false;
         }
 
-        final int sourceStackId = getStackId();
-        final int stackId = toStack.getStackId();
+        final int toStackWindowingMode = toStack.getWindowingMode();
         final ActivityRecord topActivity = getTopActivity();
 
-        final boolean mightReplaceWindow = StackId.replaceWindowsOnTaskMove(sourceStackId, stackId)
-                && topActivity != null;
+        final boolean mightReplaceWindow =
+                replaceWindowsOnTaskMove(getWindowingMode(), toStackWindowingMode)
+                        && topActivity != null;
         if (mightReplaceWindow) {
             // We are about to relaunch the activity because its configuration changed due to
             // being maximized, i.e. size change. The activity will first remove the old window
@@ -660,7 +650,7 @@
             // In some cases the focused stack isn't the front stack. E.g. pinned stack.
             // Whenever we are moving the top activity from the front stack we want to make sure to
             // move the stack to the front.
-            final boolean wasFront = r != null && supervisor.isFrontStackOnDisplay(sourceStack)
+            final boolean wasFront = r != null && sourceStack.isTopStackOnDisplay()
                     && (sourceStack.topRunningActivityLocked() == r);
 
             // Adjust the position for the new parent stack as needed.
@@ -707,10 +697,10 @@
             toStack.prepareFreezingTaskBounds();
 
             // Make sure the task has the appropriate bounds/size for the stack it is in.
-            final int toStackWindowingMode = toStack.getWindowingMode();
             final boolean toStackSplitScreenPrimary =
                     toStackWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
-            if (stackId == FULLSCREEN_WORKSPACE_STACK_ID
+            if ((toStackWindowingMode == WINDOWING_MODE_FULLSCREEN
+                    || toStackWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY)
                     && !Objects.equals(mBounds, toStack.mBounds)) {
                 kept = resize(toStack.mBounds, RESIZE_MODE_SYSTEM, !mightReplaceWindow,
                         deferResume);
@@ -749,9 +739,9 @@
         }
 
         // TODO: Handle incorrect request to move before the actual move, not after.
-        final boolean inSplitScreenMode = supervisor.getDefaultDisplay().hasSplitScreenStack();
+        final boolean inSplitScreenMode = supervisor.getDefaultDisplay().hasSplitScreenPrimaryStack();
         supervisor.handleNonResizableTaskIfNeeded(this, preferredStack.getWindowingMode(),
-                DEFAULT_DISPLAY, stackId);
+                DEFAULT_DISPLAY, toStack);
 
         boolean successful = (preferredStack == toStack);
         if (successful && toStack.getWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
@@ -761,6 +751,18 @@
         return successful;
     }
 
+    /**
+     * Returns true if the windows of tasks being moved to the target stack from the source
+     * stack should be replaced, meaning that window manager will keep the old window around
+     * until the new is ready.
+     * @hide
+     */
+    private static boolean replaceWindowsOnTaskMove(
+            int sourceWindowingMode, int targetWindowingMode) {
+        return sourceWindowingMode == WINDOWING_MODE_FREEFORM
+                || targetWindowingMode == WINDOWING_MODE_FREEFORM;
+    }
+
     void cancelWindowTransition() {
         mWindowContainerController.cancelWindowTransition();
     }
@@ -780,14 +782,11 @@
     }
 
     void touchActiveTime() {
-        lastActiveTime = System.currentTimeMillis();
-        if (firstActiveTime == 0) {
-            firstActiveTime = lastActiveTime;
-        }
+        lastActiveTime = SystemClock.elapsedRealtime();
     }
 
     long getInactiveDuration() {
-        return System.currentTimeMillis() - lastActiveTime;
+        return SystemClock.elapsedRealtime() - lastActiveTime;
     }
 
     /** Sets the original intent, and the calling uid and package. */
@@ -795,6 +794,7 @@
         mCallingUid = r.launchedFromUid;
         mCallingPackage = r.launchedFromPackage;
         setIntent(r.intent, r.info);
+        setLockTaskAuth(r);
     }
 
     /** Sets the original intent, _without_ updating the calling uid or package. */
@@ -878,14 +878,6 @@
         }
         mResizeMode = info.resizeMode;
         mSupportsPictureInPicture = info.supportsPictureInPicture();
-        mPrivileged = (info.applicationInfo.privateFlags & PRIVATE_FLAG_PRIVILEGED) != 0;
-        mLockTaskMode = info.lockTaskLaunchMode;
-        if (!mPrivileged && (mLockTaskMode == LOCK_TASK_LAUNCH_MODE_ALWAYS
-                || mLockTaskMode == LOCK_TASK_LAUNCH_MODE_NEVER)) {
-            // Non-priv apps are not allowed to use always or never, fall back to default
-            mLockTaskMode = LOCK_TASK_LAUNCH_MODE_DEFAULT;
-        }
-        setLockTaskAuth();
     }
 
     /** Sets the original minimal width and height. */
@@ -1263,7 +1255,7 @@
             mService.notifyTaskPersisterLocked(this, false);
         }
 
-        if (getStackId() == PINNED_STACK_ID) {
+        if (inPinnedWindowingMode()) {
             // We normally notify listeners of task stack changes on pause, however pinned stack
             // activities are normally in the paused state so no notification will be sent there
             // before the activity is removed. We send it here so instead.
@@ -1422,8 +1414,17 @@
     }
 
     void setLockTaskAuth() {
+        setLockTaskAuth(getRootActivity());
+    }
+
+    private void setLockTaskAuth(@Nullable ActivityRecord r) {
+        if (r == null) {
+            mLockTaskAuth = LOCK_TASK_AUTH_PINNABLE;
+            return;
+        }
+
         final String pkg = (realActivity != null) ? realActivity.getPackageName() : null;
-        switch (mLockTaskMode) {
+        switch (r.lockTaskLaunchMode) {
             case LOCK_TASK_LAUNCH_MODE_DEFAULT:
                 mLockTaskAuth = mService.mLockTaskController.isPackageWhitelisted(userId, pkg)
                         ? LOCK_TASK_AUTH_WHITELISTED : LOCK_TASK_AUTH_PINNABLE;
@@ -1493,7 +1494,7 @@
      * @return True if the requested bounds are okay for a resizing request.
      */
     private boolean canResizeToBounds(Rect bounds) {
-        if (bounds == null || getStackId() != FREEFORM_WORKSPACE_STACK_ID) {
+        if (bounds == null || !inFreeformWindowingMode()) {
             // Note: If not on the freeform workspace, we ignore the bounds.
             return true;
         }
@@ -1559,6 +1560,7 @@
             // values in the TaskRecord.
             String label = null;
             String iconFilename = null;
+            int iconResource = -1;
             int colorPrimary = 0;
             int colorBackground = 0;
             int statusBarColor = 0;
@@ -1570,6 +1572,9 @@
                     if (label == null) {
                         label = r.taskDescription.getLabel();
                     }
+                    if (iconResource == -1) {
+                        iconResource = r.taskDescription.getIconResource();
+                    }
                     if (iconFilename == null) {
                         iconFilename = r.taskDescription.getIconFilename();
                     }
@@ -1584,8 +1589,8 @@
                 }
                 topActivity = false;
             }
-            lastTaskDescription = new TaskDescription(label, null, iconFilename, colorPrimary,
-                    colorBackground, statusBarColor, navigationBarColor);
+            lastTaskDescription = new TaskDescription(label, null, iconResource, iconFilename,
+                    colorPrimary, colorBackground, statusBarColor, navigationBarColor);
             if (mWindowContainerController != null) {
                 mWindowContainerController.setTaskDescription(lastTaskDescription);
             }
@@ -1647,8 +1652,6 @@
         out.attribute(null, ATTR_USERID, String.valueOf(userId));
         out.attribute(null, ATTR_USER_SETUP_COMPLETE, String.valueOf(mUserSetupComplete));
         out.attribute(null, ATTR_EFFECTIVE_UID, String.valueOf(effectiveUid));
-        out.attribute(null, ATTR_FIRSTACTIVETIME, String.valueOf(firstActiveTime));
-        out.attribute(null, ATTR_LASTACTIVETIME, String.valueOf(lastActiveTime));
         out.attribute(null, ATTR_LASTTIMEMOVED, String.valueOf(mLastTimeMoved));
         out.attribute(null, ATTR_NEVERRELINQUISH, String.valueOf(mNeverRelinquishIdentity));
         if (lastDescription != null) {
@@ -1666,7 +1669,6 @@
         out.attribute(null, ATTR_RESIZE_MODE, String.valueOf(mResizeMode));
         out.attribute(null, ATTR_SUPPORTS_PICTURE_IN_PICTURE,
                 String.valueOf(mSupportsPictureInPicture));
-        out.attribute(null, ATTR_PRIVILEGED, String.valueOf(mPrivileged));
         if (mLastNonFullscreenBounds != null) {
             out.attribute(
                     null, ATTR_NON_FULLSCREEN_BOUNDS, mLastNonFullscreenBounds.flattenToString());
@@ -1721,8 +1723,6 @@
         boolean userSetupComplete = true;
         int effectiveUid = -1;
         String lastDescription = null;
-        long firstActiveTime = -1;
-        long lastActiveTime = -1;
         long lastTimeOnTop = 0;
         boolean neverRelinquishIdentity = true;
         int taskId = INVALID_TASK_ID;
@@ -1736,7 +1736,6 @@
         String callingPackage = "";
         int resizeMode = RESIZE_MODE_FORCE_RESIZEABLE;
         boolean supportsPictureInPicture = false;
-        boolean privileged = false;
         Rect bounds = null;
         int minWidth = INVALID_MIN_SIZE;
         int minHeight = INVALID_MIN_SIZE;
@@ -1774,10 +1773,6 @@
                 effectiveUid = Integer.parseInt(attrValue);
             } else if (ATTR_TASKTYPE.equals(attrName)) {
                 taskType = Integer.parseInt(attrValue);
-            } else if (ATTR_FIRSTACTIVETIME.equals(attrName)) {
-                firstActiveTime = Long.parseLong(attrValue);
-            } else if (ATTR_LASTACTIVETIME.equals(attrName)) {
-                lastActiveTime = Long.parseLong(attrValue);
             } else if (ATTR_LASTDESCRIPTION.equals(attrName)) {
                 lastDescription = attrValue;
             } else if (ATTR_LASTTIMEMOVED.equals(attrName)) {
@@ -1802,8 +1797,6 @@
                 resizeMode = Integer.parseInt(attrValue);
             } else if (ATTR_SUPPORTS_PICTURE_IN_PICTURE.equals(attrName)) {
                 supportsPictureInPicture = Boolean.parseBoolean(attrValue);
-            } else if (ATTR_PRIVILEGED.equals(attrName)) {
-                privileged = Boolean.parseBoolean(attrValue);
             } else if (ATTR_NON_FULLSCREEN_BOUNDS.equals(attrName)) {
                 bounds = Rect.unflattenFromString(attrValue);
             } else if (ATTR_MIN_WIDTH.equals(attrName)) {
@@ -1888,10 +1881,10 @@
         final TaskRecord task = new TaskRecord(stackSupervisor.mService, taskId, intent,
                 affinityIntent, affinity, rootAffinity, realActivity, origActivity, rootHasReset,
                 autoRemoveRecents, askedCompatMode, userId, effectiveUid, lastDescription,
-                activities, firstActiveTime, lastActiveTime, lastTimeOnTop, neverRelinquishIdentity,
-                taskDescription, taskAffiliation, prevTaskId, nextTaskId, taskAffiliationColor,
-                callingUid, callingPackage, resizeMode, supportsPictureInPicture, privileged,
-                realActivitySuspended, userSetupComplete, minWidth, minHeight);
+                activities, lastTimeOnTop, neverRelinquishIdentity, taskDescription,
+                taskAffiliation, prevTaskId, nextTaskId, taskAffiliationColor, callingUid,
+                callingPackage, resizeMode, supportsPictureInPicture, realActivitySuspended,
+                userSetupComplete, minWidth, minHeight);
         task.updateOverrideConfiguration(bounds);
 
         for (int activityNdx = activities.size() - 1; activityNdx >=0; --activityNdx) {
@@ -1911,7 +1904,7 @@
         // If the task has no requested minimal size, we'd like to enforce a minimal size
         // so that the user can not render the task too small to manipulate. We don't need
         // to do this for the pinned stack as the bounds are controlled by the system.
-        if (getStackId() != PINNED_STACK_ID) {
+        if (!inPinnedWindowingMode()) {
             if (minWidth == INVALID_MIN_SIZE) {
                 minWidth = mService.mStackSupervisor.mDefaultMinSizeOfResizeableTask;
             }
@@ -2085,7 +2078,7 @@
             return;
         }
 
-        if (inStack.mStackId == FREEFORM_WORKSPACE_STACK_ID) {
+        if (inStack.inFreeformWindowingMode()) {
             if (!isResizeable()) {
                 throw new IllegalArgumentException("Can not position non-resizeable task="
                         + this + " in stack=" + inStack);
@@ -2220,7 +2213,6 @@
                 pw.print(" mResizeMode=" + ActivityInfo.resizeModeToString(mResizeMode));
                 pw.print(" mSupportsPictureInPicture=" + mSupportsPictureInPicture);
                 pw.print(" isResizeable=" + isResizeable());
-                pw.print(" firstActiveTime=" + firstActiveTime);
                 pw.print(" lastActiveTime=" + lastActiveTime);
                 pw.println(" (inactive for " + (getInactiveDuration() / 1000) + "s)");
     }
diff --git a/com/android/server/am/UserController.java b/com/android/server/am/UserController.java
index 5a29594..4aa8adb 100644
--- a/com/android/server/am/UserController.java
+++ b/com/android/server/am/UserController.java
@@ -67,6 +67,7 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemClock;
+import android.os.Trace;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.UserManagerInternal;
@@ -79,6 +80,7 @@
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
+import android.util.TimingsTraceLog;
 
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
@@ -232,6 +234,7 @@
         mUiHandler = mInjector.getUiHandler(this);
         // User 0 is the first and only user that runs at boot.
         final UserState uss = new UserState(UserHandle.SYSTEM);
+        uss.mUnlockProgress.addListener(new UserProgressListener());
         mStartedUsers.put(UserHandle.USER_SYSTEM, uss);
         mUserLru.add(UserHandle.USER_SYSTEM);
         mLockPatternUtils = mInjector.getLockPatternUtils();
@@ -903,6 +906,7 @@
                 uss = mStartedUsers.get(userId);
                 if (uss == null) {
                     uss = new UserState(UserHandle.of(userId));
+                    uss.mUnlockProgress.addListener(new UserProgressListener());
                     mStartedUsers.put(userId, uss);
                     updateStartedUserArrayLU();
                     needStart = true;
@@ -1854,6 +1858,33 @@
         return false;
     }
 
+    private static class UserProgressListener extends IProgressListener.Stub {
+        private volatile long mUnlockStarted;
+        @Override
+        public void onStarted(int id, Bundle extras) throws RemoteException {
+            Slog.d(TAG, "Started unlocking user " + id);
+            mUnlockStarted = SystemClock.uptimeMillis();
+        }
+
+        @Override
+        public void onProgress(int id, int progress, Bundle extras) throws RemoteException {
+            Slog.d(TAG, "Unlocking user " + id + " progress " + progress);
+        }
+
+        @Override
+        public void onFinished(int id, Bundle extras) throws RemoteException {
+            long unlockTime = SystemClock.uptimeMillis() - mUnlockStarted;
+
+            // Report system user unlock time to perf dashboard
+            if (id == UserHandle.USER_SYSTEM) {
+                new TimingsTraceLog("SystemServerTiming", Trace.TRACE_TAG_SYSTEM_SERVER)
+                        .logDuration("SystemUserUnlock", unlockTime);
+            } else {
+                Slog.d(TAG, "Unlocking user " + id + " took " + unlockTime + " ms");
+            }
+        }
+    };
+
     @VisibleForTesting
     static class Injector {
         private final ActivityManagerService mService;
diff --git a/com/android/server/appwidget/AppWidgetServiceImpl.java b/com/android/server/appwidget/AppWidgetServiceImpl.java
index a6aaaa6..76e7782 100644
--- a/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -21,9 +21,12 @@
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 
 import android.annotation.UserIdInt;
+import android.app.ActivityManager;
 import android.app.AlarmManager;
 import android.app.AppGlobals;
 import android.app.AppOpsManager;
+import android.app.IApplicationThread;
+import android.app.IServiceConnection;
 import android.app.KeyguardManager;
 import android.app.PendingIntent;
 import android.app.admin.DevicePolicyManagerInternal;
@@ -71,7 +74,6 @@
 import android.os.Trace;
 import android.os.UserHandle;
 import android.os.UserManager;
-import android.os.storage.StorageManager;
 import android.service.appwidget.AppWidgetServiceDumpProto;
 import android.service.appwidget.WidgetProto;
 import android.text.TextUtils;
@@ -100,7 +102,6 @@
 import com.android.internal.os.SomeArgs;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.FastXmlSerializer;
-import com.android.internal.widget.IRemoteViewsAdapterConnection;
 import com.android.internal.widget.IRemoteViewsFactory;
 import com.android.server.LocalServices;
 import com.android.server.WidgetBackupProvider;
@@ -159,7 +160,9 @@
     // Bump if the stored widgets need to be upgraded.
     private static final int CURRENT_VERSION = 1;
 
-    private static final AtomicLong REQUEST_COUNTER = new AtomicLong();
+    // Every widget update request is associated which an increasing sequence number. This is
+    // used to verify which request has successfully been received by the host.
+    private static final AtomicLong UPDATE_COUNTER = new AtomicLong();
 
     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
         @Override
@@ -190,10 +193,6 @@
         }
     };
 
-    // Manages active connections to RemoteViewsServices.
-    private final HashMap<Pair<Integer, FilterComparison>, ServiceConnection>
-            mBoundRemoteViewsServices = new HashMap<>();
-
     // Manages persistent references to RemoteViewsServices from different App Widgets.
     private final HashMap<Pair<Integer, FilterComparison>, HashSet<Integer>>
             mRemoteViewsServicesAppWidgets = new HashMap<>();
@@ -814,9 +813,9 @@
             Host host = lookupOrAddHostLocked(id);
             host.callbacks = callbacks;
 
+            long updateSequenceNo = UPDATE_COUNTER.incrementAndGet();
             int N = appWidgetIds.length;
             ArrayList<PendingHostUpdate> outUpdates = new ArrayList<>(N);
-
             LongSparseArray<PendingHostUpdate> updatesMap = new LongSparseArray<>();
             for (int i = 0; i < N; i++) {
                 if (host.getPendingUpdatesForId(appWidgetIds[i], updatesMap)) {
@@ -828,6 +827,8 @@
                     }
                 }
             }
+            // Reset the update counter once all the updates have been calculated
+            host.lastWidgetUpdateSequenceNo = updateSequenceNo;
             return new ParceledListSlice<>(outUpdates);
         }
     }
@@ -1206,17 +1207,14 @@
     }
 
     @Override
-    public void bindRemoteViewsService(String callingPackage, int appWidgetId,
-            Intent intent, IBinder callbacks) {
+    public boolean bindRemoteViewsService(String callingPackage, int appWidgetId, Intent intent,
+            IApplicationThread caller, IBinder activtiyToken, IServiceConnection connection,
+            int flags) {
         final int userId = UserHandle.getCallingUserId();
-
         if (DEBUG) {
             Slog.i(TAG, "bindRemoteViewsService() " + userId);
         }
 
-        // Make sure the package runs under the caller uid.
-        mSecurityPolicy.enforceCallFromPackage(callingPackage);
-
         synchronized (mLock) {
             ensureGroupStateLoadedLocked(userId);
 
@@ -1251,76 +1249,35 @@
             mSecurityPolicy.enforceServiceExistsAndRequiresBindRemoteViewsPermission(
                     componentName, widget.provider.getUserId());
 
-            // Good to go - the service pakcage is correct, it exists for the correct
+            // Good to go - the service package is correct, it exists for the correct
             // user, and requires the bind permission.
 
-            // If there is already a connection made for this service intent, then
-            // disconnect from that first. (This does not allow multiple connections
-            // to the same service under the same key).
-            ServiceConnectionProxy connection = null;
-            FilterComparison fc = new FilterComparison(intent);
-            Pair<Integer, FilterComparison> key = Pair.create(appWidgetId, fc);
+            final long callingIdentity = Binder.clearCallingIdentity();
+            try {
+                // Ask ActivityManager to bind it. Notice that we are binding the service with the
+                // caller app instead of DevicePolicyManagerService.
+                if(ActivityManager.getService().bindService(
+                        caller, activtiyToken, intent,
+                        intent.resolveTypeIfNeeded(mContext.getContentResolver()),
+                        connection, flags, mContext.getOpPackageName(),
+                        widget.provider.getUserId()) != 0) {
 
-            if (mBoundRemoteViewsServices.containsKey(key)) {
-                connection = (ServiceConnectionProxy) mBoundRemoteViewsServices.get(key);
-                connection.disconnect();
-                unbindService(connection);
-                mBoundRemoteViewsServices.remove(key);
-            }
-
-            // Bind to the RemoteViewsService (which will trigger a callback to the
-            // RemoteViewsAdapter.onServiceConnected())
-            connection = new ServiceConnectionProxy(callbacks);
-            bindService(intent, connection, widget.provider.info.getProfile());
-            mBoundRemoteViewsServices.put(key, connection);
-
-            // Add it to the mapping of RemoteViewsService to appWidgetIds so that we
-            // can determine when we can call back to the RemoteViewsService later to
-            // destroy associated factories.
-            Pair<Integer, FilterComparison> serviceId = Pair.create(widget.provider.id.uid, fc);
-            incrementAppWidgetServiceRefCount(appWidgetId, serviceId);
-        }
-    }
-
-    @Override
-    public void unbindRemoteViewsService(String callingPackage, int appWidgetId, Intent intent) {
-        final int userId = UserHandle.getCallingUserId();
-
-        if (DEBUG) {
-            Slog.i(TAG, "unbindRemoteViewsService() " + userId);
-        }
-
-        // Make sure the package runs under the caller uid.
-        mSecurityPolicy.enforceCallFromPackage(callingPackage);
-
-        synchronized (mLock) {
-            ensureGroupStateLoadedLocked(userId);
-
-            // Unbind from the RemoteViewsService (which will trigger a callback to the bound
-            // RemoteViewsAdapter)
-            Pair<Integer, FilterComparison> key = Pair.create(appWidgetId,
-                    new FilterComparison(intent));
-            if (mBoundRemoteViewsServices.containsKey(key)) {
-                // We don't need to use the appWidgetId until after we are sure there is something
-                // to unbind. Note that this may mask certain issues with apps calling unbind()
-                // more than necessary.
-
-                // NOTE: The lookup is enforcing security across users by making
-                // sure the caller can only access widgets it hosts or provides.
-                Widget widget = lookupWidgetLocked(appWidgetId,
-                        Binder.getCallingUid(), callingPackage);
-
-                if (widget == null) {
-                    throw new IllegalArgumentException("Bad widget id " + appWidgetId);
+                    // Add it to the mapping of RemoteViewsService to appWidgetIds so that we
+                    // can determine when we can call back to the RemoteViewsService later to
+                    // destroy associated factories.
+                    incrementAppWidgetServiceRefCount(appWidgetId,
+                            Pair.create(widget.provider.id.uid, new FilterComparison(intent)));
+                    return true;
                 }
-
-                ServiceConnectionProxy connection = (ServiceConnectionProxy)
-                        mBoundRemoteViewsServices.get(key);
-                connection.disconnect();
-                mContext.unbindService(connection);
-                mBoundRemoteViewsServices.remove(key);
+            } catch (RemoteException ex) {
+                // Same process, should not happen.
+            } finally {
+                Binder.restoreCallingIdentity(callingIdentity);
             }
         }
+
+        // Failed to bind.
+        return false;
     }
 
     @Override
@@ -1751,7 +1708,9 @@
 
     private void deleteAppWidgetLocked(Widget widget) {
         // We first unbind all services that are bound to this id
-        unbindAppWidgetRemoteViewsServicesLocked(widget);
+        // Check if we need to destroy any services (if no other app widgets are
+        // referencing the same service)
+        decrementAppWidgetServiceRefCount(widget);
 
         Host host = widget.host;
         host.widgets.remove(widget);
@@ -1793,28 +1752,6 @@
         }
     }
 
-    // Unbinds from a RemoteViewsService when we delete an app widget
-    private void unbindAppWidgetRemoteViewsServicesLocked(Widget widget) {
-        int appWidgetId = widget.appWidgetId;
-        // Unbind all connections to Services bound to this AppWidgetId
-        Iterator<Pair<Integer, Intent.FilterComparison>> it = mBoundRemoteViewsServices.keySet()
-                .iterator();
-        while (it.hasNext()) {
-            final Pair<Integer, Intent.FilterComparison> key = it.next();
-            if (key.first == appWidgetId) {
-                final ServiceConnectionProxy conn = (ServiceConnectionProxy)
-                        mBoundRemoteViewsServices.get(key);
-                conn.disconnect();
-                mContext.unbindService(conn);
-                it.remove();
-            }
-        }
-
-        // Check if we need to destroy any services (if no other app widgets are
-        // referencing the same service)
-        decrementAppWidgetServiceRefCount(widget);
-    }
-
     // Destroys the cached factory on the RemoteViewsService's side related to the specified intent
     private void destroyRemoteViewsService(final Intent intent, Widget widget) {
         final ServiceConnection conn = new ServiceConnection() {
@@ -1850,7 +1787,7 @@
     // Adds to the ref-count for a given RemoteViewsService intent
     private void incrementAppWidgetServiceRefCount(int appWidgetId,
             Pair<Integer, FilterComparison> serviceId) {
-        HashSet<Integer> appWidgetIds = null;
+        final HashSet<Integer> appWidgetIds;
         if (mRemoteViewsServicesAppWidgets.containsKey(serviceId)) {
             appWidgetIds = mRemoteViewsServicesAppWidgets.get(serviceId);
         } else {
@@ -1914,9 +1851,9 @@
             // method with a wrong id. In that case, ignore the call.
             return;
         }
-        long requestId = REQUEST_COUNTER.incrementAndGet();
+        long requestId = UPDATE_COUNTER.incrementAndGet();
         if (widget != null) {
-            widget.updateRequestIds.put(viewId, requestId);
+            widget.updateSequenceNos.put(viewId, requestId);
         }
         if (widget == null || widget.host == null || widget.host.zombie
                 || widget.host.callbacks == null || widget.provider == null
@@ -1941,7 +1878,7 @@
             int appWidgetId, int viewId, long requestId) {
         try {
             callbacks.viewDataChanged(appWidgetId, viewId);
-            host.lastWidgetUpdateRequestId = requestId;
+            host.lastWidgetUpdateSequenceNo = requestId;
         } catch (RemoteException re) {
             // It failed; remove the callback. No need to prune because
             // we know that this host is still referenced by this instance.
@@ -1988,9 +1925,9 @@
     }
 
     private void scheduleNotifyUpdateAppWidgetLocked(Widget widget, RemoteViews updateViews) {
-        long requestId = REQUEST_COUNTER.incrementAndGet();
+        long requestId = UPDATE_COUNTER.incrementAndGet();
         if (widget != null) {
-            widget.updateRequestIds.put(ID_VIEWS_UPDATE, requestId);
+            widget.updateSequenceNos.put(ID_VIEWS_UPDATE, requestId);
         }
         if (widget == null || widget.provider == null || widget.provider.zombie
                 || widget.host.callbacks == null || widget.host.zombie) {
@@ -2013,7 +1950,7 @@
             int appWidgetId, RemoteViews views, long requestId) {
         try {
             callbacks.updateAppWidget(appWidgetId, views);
-            host.lastWidgetUpdateRequestId = requestId;
+            host.lastWidgetUpdateSequenceNo = requestId;
         } catch (RemoteException re) {
             synchronized (mLock) {
                 Slog.e(TAG, "Widget host dead: " + host.id, re);
@@ -2023,11 +1960,11 @@
     }
 
     private void scheduleNotifyProviderChangedLocked(Widget widget) {
-        long requestId = REQUEST_COUNTER.incrementAndGet();
+        long requestId = UPDATE_COUNTER.incrementAndGet();
         if (widget != null) {
             // When the provider changes, reset everything else.
-            widget.updateRequestIds.clear();
-            widget.updateRequestIds.append(ID_PROVIDER_CHANGED, requestId);
+            widget.updateSequenceNos.clear();
+            widget.updateSequenceNos.append(ID_PROVIDER_CHANGED, requestId);
         }
         if (widget == null || widget.provider == null || widget.provider.zombie
                 || widget.host.callbacks == null || widget.host.zombie) {
@@ -2050,7 +1987,7 @@
             int appWidgetId, AppWidgetProviderInfo info, long requestId) {
         try {
             callbacks.providerChanged(appWidgetId, info);
-            host.lastWidgetUpdateRequestId = requestId;
+            host.lastWidgetUpdateSequenceNo = requestId;
         } catch (RemoteException re) {
             synchronized (mLock){
                 Slog.e(TAG, "Widget host dead: " + host.id, re);
@@ -3887,7 +3824,11 @@
         boolean zombie; // if we're in safe mode, don't prune this just because nobody references it
 
         int tag = TAG_UNDEFINED; // for use while saving state (the index)
-        long lastWidgetUpdateRequestId; // request id for the last update successfully sent
+        // Sequence no for the last update successfully sent. This is updated whenever a
+        // widget update is successfully sent to the host callbacks. As all new/undelivered updates
+        // will have sequenceNo greater than this, all those updates will be sent when the host
+        // callbacks are attached again.
+        long lastWidgetUpdateSequenceNo;
 
         public int getUserId() {
             return UserHandle.getUserId(id.uid);
@@ -3914,18 +3855,18 @@
          */
         public boolean getPendingUpdatesForId(int appWidgetId,
                 LongSparseArray<PendingHostUpdate> outUpdates) {
-            long updateRequestId = lastWidgetUpdateRequestId;
+            long updateSequenceNo = lastWidgetUpdateSequenceNo;
             int N = widgets.size();
             for (int i = 0; i < N; i++) {
                 Widget widget = widgets.get(i);
                 if (widget.appWidgetId == appWidgetId) {
                     outUpdates.clear();
-                    for (int j = widget.updateRequestIds.size() - 1; j >= 0; j--) {
-                        long requestId = widget.updateRequestIds.valueAt(j);
-                        if (requestId <= updateRequestId) {
+                    for (int j = widget.updateSequenceNos.size() - 1; j >= 0; j--) {
+                        long requestId = widget.updateSequenceNos.valueAt(j);
+                        if (requestId <= updateSequenceNo) {
                             continue;
                         }
-                        int id = widget.updateRequestIds.keyAt(j);
+                        int id = widget.updateSequenceNos.keyAt(j);
                         final PendingHostUpdate update;
                         switch (id) {
                             case ID_PROVIDER_CHANGED:
@@ -4021,8 +3962,8 @@
         RemoteViews maskedViews;
         Bundle options;
         Host host;
-        // Request ids for various operations
-        SparseLongArray updateRequestIds = new SparseLongArray(2);
+        // Map of request type to updateSequenceNo.
+        SparseLongArray updateSequenceNos = new SparseLongArray(2);
 
         @Override
         public String toString() {
@@ -4048,40 +3989,6 @@
         }
     }
 
-    /**
-     * Acts as a proxy between the ServiceConnection and the RemoteViewsAdapterConnection. This
-     * needs to be a static inner class since a reference to the ServiceConnection is held globally
-     * and may lead us to leak AppWidgetService instances (if there were more than one).
-     */
-    private static final class ServiceConnectionProxy implements ServiceConnection {
-        private final IRemoteViewsAdapterConnection mConnectionCb;
-
-        ServiceConnectionProxy(IBinder connectionCb) {
-            mConnectionCb = IRemoteViewsAdapterConnection.Stub
-                    .asInterface(connectionCb);
-        }
-
-        public void onServiceConnected(ComponentName name, IBinder service) {
-            try {
-                mConnectionCb.onServiceConnected(service);
-            } catch (RemoteException re) {
-                Slog.e(TAG, "Error passing service interface", re);
-            }
-        }
-
-        public void onServiceDisconnected(ComponentName name) {
-            disconnect();
-        }
-
-        public void disconnect() {
-            try {
-                mConnectionCb.onServiceDisconnected();
-            } catch (RemoteException re) {
-                Slog.e(TAG, "Error clearing service interface", re);
-            }
-        }
-    }
-
     private class LoadedWidgetState {
         final Widget widget;
         final int hostTag;
@@ -4635,7 +4542,9 @@
                         // reconstructed due to the restore
                         host.widgets.remove(widget);
                         provider.widgets.remove(widget);
-                        unbindAppWidgetRemoteViewsServicesLocked(widget);
+                        // Check if we need to destroy any services (if no other app widgets are
+                        // referencing the same service)
+                        decrementAppWidgetServiceRefCount(widget);
                         removeWidgetLocked(widget);
                     }
                 }
diff --git a/com/android/server/audio/PlaybackActivityMonitor.java b/com/android/server/audio/PlaybackActivityMonitor.java
index 6506cf7..4943173 100644
--- a/com/android/server/audio/PlaybackActivityMonitor.java
+++ b/com/android/server/audio/PlaybackActivityMonitor.java
@@ -184,11 +184,15 @@
         }
     }
 
+    private static final int FLAGS_FOR_SILENCE_OVERRIDE =
+            AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY |
+            AudioAttributes.FLAG_BYPASS_MUTE;
+
     private void checkVolumeForPrivilegedAlarm(AudioPlaybackConfiguration apc, int event) {
         if (event == AudioPlaybackConfiguration.PLAYER_STATE_STARTED ||
                 apc.getPlayerState() == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
-            if ((apc.getAudioAttributes().getAllFlags() &
-                    AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY) != 0 &&
+            if ((apc.getAudioAttributes().getAllFlags() & FLAGS_FOR_SILENCE_OVERRIDE)
+                        == FLAGS_FOR_SILENCE_OVERRIDE  &&
                     apc.getAudioAttributes().getUsage() == AudioAttributes.USAGE_ALARM &&
                     mContext.checkPermission(android.Manifest.permission.MODIFY_PHONE_STATE,
                             apc.getClientPid(), apc.getClientUid()) ==
diff --git a/com/android/server/autofill/AutofillManagerServiceImpl.java b/com/android/server/autofill/AutofillManagerServiceImpl.java
index 862070a..075c741 100644
--- a/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -52,6 +52,7 @@
 import android.service.autofill.FillResponse;
 import android.service.autofill.IAutoFillService;
 import android.text.TextUtils;
+import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.DebugUtils;
 import android.util.LocalLog;
@@ -216,9 +217,12 @@
                 serviceComponent = ComponentName.unflattenFromString(componentName);
                 serviceInfo = AppGlobals.getPackageManager().getServiceInfo(serviceComponent,
                         0, mUserId);
+                if (serviceInfo == null) {
+                    Slog.e(TAG, "Bad AutofillService name: " + componentName);
+                }
             } catch (RuntimeException | RemoteException e) {
-                Slog.e(TAG, "Bad autofill service name " + componentName + ": " + e);
-                return;
+                Slog.e(TAG, "Error getting service info for '" + componentName + "': " + e);
+                serviceInfo = null;
             }
         }
         try {
@@ -228,21 +232,24 @@
                 if (sDebug) Slog.d(TAG, "Set component for user " + mUserId + " as " + mInfo);
             } else {
                 mInfo = null;
-                if (sDebug) Slog.d(TAG, "Reset component for user " + mUserId);
-            }
-            final boolean isEnabled = isEnabled();
-            if (wasEnabled != isEnabled) {
-                if (!isEnabled) {
-                    final int sessionCount = mSessions.size();
-                    for (int i = sessionCount - 1; i >= 0; i--) {
-                        final Session session = mSessions.valueAt(i);
-                        session.removeSelfLocked();
-                    }
+                if (sDebug) {
+                    Slog.d(TAG, "Reset component for user " + mUserId + " (" + componentName + ")");
                 }
-                sendStateToClients(false);
             }
         } catch (Exception e) {
-            Slog.e(TAG, "Bad AutofillService '" + componentName + "': " + e);
+            Slog.e(TAG, "Bad AutofillServiceInfo for '" + componentName + "': " + e);
+            mInfo = null;
+        }
+        final boolean isEnabled = isEnabled();
+        if (wasEnabled != isEnabled) {
+            if (!isEnabled) {
+                final int sessionCount = mSessions.size();
+                for (int i = sessionCount - 1; i >= 0; i--) {
+                    final Session session = mSessions.valueAt(i);
+                    session.removeSelfLocked();
+                }
+            }
+            sendStateToClients(false);
         }
     }
 
@@ -332,6 +339,8 @@
             return;
         }
 
+        session.logContextCommittedLocked();
+
         final boolean finished = session.showSaveLocked();
         if (sVerbose) Slog.v(TAG, "finishSessionLocked(): session finished on save? " + finished);
 
@@ -557,8 +566,9 @@
     void setAuthenticationSelected(int sessionId, @Nullable Bundle clientState) {
         synchronized (mLock) {
             if (isValidEventLocked("setAuthenticationSelected()", sessionId)) {
-                mEventHistory
-                        .addEvent(new Event(Event.TYPE_AUTHENTICATION_SELECTED, null, clientState));
+                mEventHistory.addEvent(
+                        new Event(Event.TYPE_AUTHENTICATION_SELECTED, null, clientState, null, null,
+                                null, null, null, null));
             }
         }
     }
@@ -572,7 +582,7 @@
             if (isValidEventLocked("logDatasetAuthenticationSelected()", sessionId)) {
                 mEventHistory.addEvent(
                         new Event(Event.TYPE_DATASET_AUTHENTICATION_SELECTED, selectedDataset,
-                                clientState));
+                                clientState, null, null, null, null, null, null));
             }
         }
     }
@@ -583,7 +593,8 @@
     void logSaveShown(int sessionId, @Nullable Bundle clientState) {
         synchronized (mLock) {
             if (isValidEventLocked("logSaveShown()", sessionId)) {
-                mEventHistory.addEvent(new Event(Event.TYPE_SAVE_SHOWN, null, clientState));
+                mEventHistory.addEvent(new Event(Event.TYPE_SAVE_SHOWN, null, clientState, null,
+                        null, null, null, null, null));
             }
         }
     }
@@ -596,7 +607,28 @@
         synchronized (mLock) {
             if (isValidEventLocked("logDatasetSelected()", sessionId)) {
                 mEventHistory.addEvent(
-                        new Event(Event.TYPE_DATASET_SELECTED, selectedDataset, clientState));
+                        new Event(Event.TYPE_DATASET_SELECTED, selectedDataset, clientState, null,
+                                null, null, null, null, null));
+            }
+        }
+    }
+
+    /**
+     * Updates the last fill response when an autofill context is committed.
+     */
+    void logContextCommitted(int sessionId, @Nullable Bundle clientState,
+            @Nullable ArrayList<String> selectedDatasets,
+            @Nullable ArraySet<String> ignoredDatasets,
+            @Nullable ArrayList<AutofillId> changedFieldIds,
+            @Nullable ArrayList<String> changedDatasetIds,
+            @Nullable ArrayList<AutofillId> manuallyFilledFieldIds,
+            @Nullable ArrayList<ArrayList<String>> manuallyFilledDatasetIds) {
+        synchronized (mLock) {
+            if (isValidEventLocked("logDatasetNotSelected()", sessionId)) {
+                mEventHistory.addEvent(new Event(Event.TYPE_CONTEXT_COMMITTED, null,
+                        clientState, selectedDatasets, ignoredDatasets,
+                        changedFieldIds, changedDatasetIds,
+                        manuallyFilledFieldIds, manuallyFilledDatasetIds));
             }
         }
     }
diff --git a/com/android/server/autofill/Session.java b/com/android/server/autofill/Session.java
index ed00ffe..b720f74 100644
--- a/com/android/server/autofill/Session.java
+++ b/com/android/server/autofill/Session.java
@@ -54,6 +54,7 @@
 import android.service.autofill.AutofillService;
 import android.service.autofill.Dataset;
 import android.service.autofill.FillContext;
+import android.service.autofill.FillEventHistory;
 import android.service.autofill.FillRequest;
 import android.service.autofill.FillResponse;
 import android.service.autofill.InternalSanitizer;
@@ -90,6 +91,7 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.atomic.AtomicInteger;
 
 /**
@@ -495,7 +497,7 @@
             notifyUnavailableToClient(false);
         }
         synchronized (mLock) {
-            processResponseLocked(response, requestFlags);
+            processResponseLocked(response, null, requestFlags);
         }
 
         final LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_REQUEST, servicePackageName)
@@ -762,13 +764,21 @@
         }
 
         final Parcelable result = data.getParcelable(AutofillManager.EXTRA_AUTHENTICATION_RESULT);
-        if (sDebug) Slog.d(TAG, "setAuthenticationResultLocked(): result=" + result);
+        final Bundle newClientState = data.getBundle(AutofillManager.EXTRA_CLIENT_STATE);
+        if (sDebug) {
+            Slog.d(TAG, "setAuthenticationResultLocked(): result=" + result
+                    + ", clientState=" + newClientState);
+        }
         if (result instanceof FillResponse) {
             writeLog(MetricsEvent.AUTOFILL_AUTHENTICATED);
-            replaceResponseLocked(authenticatedResponse, (FillResponse) result);
+            replaceResponseLocked(authenticatedResponse, (FillResponse) result, newClientState);
         } else if (result instanceof Dataset) {
             if (datasetIdx != AutofillManager.AUTHENTICATION_ID_DATASET_ID_UNDEFINED) {
                 writeLog(MetricsEvent.AUTOFILL_DATASET_AUTHENTICATED);
+                if (newClientState != null) {
+                    if (sDebug) Slog.d(TAG,  "Updating client state from auth dataset");
+                    mClientState = newClientState;
+                }
                 final Dataset dataset = (Dataset) result;
                 authenticatedResponse.getDatasets().set(datasetIdx, dataset);
                 autoFill(requestId, datasetIdx, dataset, false);
@@ -832,6 +842,191 @@
     }
 
     /**
+     * Generates a {@link android.service.autofill.FillEventHistory.Event#TYPE_CONTEXT_COMMITTED}
+     * when necessary.
+     */
+    public void logContextCommittedLocked() {
+        final FillResponse lastResponse = getLastResponseLocked("logContextCommited()");
+        if (lastResponse == null) return;
+
+        final int flags = lastResponse.getFlags();
+        if ((flags & FillResponse.FLAG_TRACK_CONTEXT_COMMITED) == 0) {
+            if (sDebug) Slog.d(TAG, "logContextCommittedLocked(): ignored by flags " + flags);
+            return;
+        }
+
+        ArraySet<String> ignoredDatasets = null;
+        ArrayList<AutofillId> changedFieldIds = null;
+        ArrayList<String> changedDatasetIds = null;
+        ArrayMap<AutofillId, ArraySet<String>> manuallyFilledIds = null;
+
+        boolean hasAtLeastOneDataset = false;
+        final int responseCount = mResponses.size();
+        for (int i = 0; i < responseCount; i++) {
+            final FillResponse response = mResponses.valueAt(i);
+            final List<Dataset> datasets = response.getDatasets();
+            if (datasets == null || datasets.isEmpty()) {
+                if (sVerbose) Slog.v(TAG,  "logContextCommitted() no datasets at " + i);
+            } else {
+                for (int j = 0; j < datasets.size(); j++) {
+                    final Dataset dataset = datasets.get(j);
+                    final String datasetId = dataset.getId();
+                    if (datasetId == null) {
+                        if (sVerbose) {
+                            Slog.v(TAG, "logContextCommitted() skipping idless dataset " + dataset);
+                        }
+                    } else {
+                        hasAtLeastOneDataset = true;
+                        if (mSelectedDatasetIds == null
+                                || !mSelectedDatasetIds.contains(datasetId)) {
+                            if (sVerbose) Slog.v(TAG, "adding ignored dataset " + datasetId);
+                            if (ignoredDatasets == null) {
+                                ignoredDatasets = new ArraySet<>();
+                            }
+                            ignoredDatasets.add(datasetId);
+                        }
+                    }
+                }
+            }
+        }
+        if (!hasAtLeastOneDataset) {
+            if (sVerbose) Slog.v(TAG, "logContextCommittedLocked(): skipped (no datasets)");
+            return;
+        }
+
+        for (int i = 0; i < mViewStates.size(); i++) {
+            final ViewState viewState = mViewStates.valueAt(i);
+            final int state = viewState.getState();
+
+            // When value changed, we need to log if it was:
+            // - autofilled -> changedDatasetIds
+            // - not autofilled but matches a dataset value -> manuallyFilledIds
+            if ((state & ViewState.STATE_CHANGED) != 0) {
+
+                // Check if autofilled value was changed
+                if ((state & ViewState.STATE_AUTOFILLED) != 0) {
+                    final String datasetId = viewState.getDatasetId();
+                    if (datasetId == null) {
+                        // Sanity check - should never happen.
+                        Slog.w(TAG, "logContextCommitted(): no dataset id on " + viewState);
+                        continue;
+                    }
+
+                    // Must first check if final changed value is not the same as value sent by
+                    // service.
+                    final AutofillValue autofilledValue = viewState.getAutofilledValue();
+                    final AutofillValue currentValue = viewState.getCurrentValue();
+                    if (autofilledValue != null && autofilledValue.equals(currentValue)) {
+                        if (sDebug) {
+                            Slog.d(TAG, "logContextCommitted(): ignoring changed " + viewState
+                                    + " because it has same value that was autofilled");
+                        }
+                        continue;
+                    }
+
+                    if (sDebug) {
+                        Slog.d(TAG, "logContextCommitted() found changed state: " + viewState);
+                    }
+                    if (changedFieldIds == null) {
+                        changedFieldIds = new ArrayList<>();
+                        changedDatasetIds = new ArrayList<>();
+                    }
+                    changedFieldIds.add(viewState.id);
+                    changedDatasetIds.add(datasetId);
+                } else {
+                    // Check if value match a dataset.
+                    final AutofillValue currentValue = viewState.getCurrentValue();
+                    if (currentValue == null) {
+                        if (sDebug) {
+                            Slog.d(TAG, "logContextCommitted(): skipping view witout current value "
+                                    + "( " + viewState + ")");
+                        }
+                        continue;
+                    }
+                    for (int j = 0; j < responseCount; j++) {
+                        final FillResponse response = mResponses.valueAt(j);
+                        final List<Dataset> datasets = response.getDatasets();
+                        if (datasets == null || datasets.isEmpty()) {
+                            if (sVerbose) Slog.v(TAG,  "logContextCommitted() no datasets at " + j);
+                        } else {
+                            for (int k = 0; k < datasets.size(); k++) {
+                                final Dataset dataset = datasets.get(k);
+                                final String datasetId = dataset.getId();
+                                if (datasetId == null) {
+                                    if (sVerbose) {
+                                        Slog.v(TAG, "logContextCommitted() skipping idless dataset "
+                                                + dataset);
+                                    }
+                                } else {
+                                    final ArrayList<AutofillValue> values = dataset.getFieldValues();
+                                    for (int l = 0; l < values.size(); l++) {
+                                        final AutofillValue candidate = values.get(l);
+                                        if (currentValue.equals(candidate)) {
+                                            if (sDebug) {
+                                                Slog.d(TAG, "field " + viewState.id
+                                                        + " was manually filled with value set by "
+                                                        + "dataset " + datasetId);
+                                            }
+                                            if (manuallyFilledIds == null) {
+                                                manuallyFilledIds = new ArrayMap<>();
+                                            }
+                                            ArraySet<String> datasetIds =
+                                                    manuallyFilledIds.get(viewState.id);
+                                            if (datasetIds == null) {
+                                                datasetIds = new ArraySet<>(1);
+                                                manuallyFilledIds.put(viewState.id, datasetIds);
+                                            }
+                                            datasetIds.add(datasetId);
+                                        }
+                                    }
+                                    if (mSelectedDatasetIds == null
+                                            || !mSelectedDatasetIds.contains(datasetId)) {
+                                        if (sVerbose) {
+                                            Slog.v(TAG, "adding ignored dataset " + datasetId);
+                                        }
+                                        if (ignoredDatasets == null) {
+                                            ignoredDatasets = new ArraySet<>();
+                                        }
+                                        ignoredDatasets.add(datasetId);
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        if (sVerbose) {
+            Slog.v(TAG, "logContextCommitted(): id=" + id
+                    + ", selectedDatasetids=" + mSelectedDatasetIds
+                    + ", ignoredDatasetIds=" + ignoredDatasets
+                    + ", changedAutofillIds=" + changedFieldIds
+                    + ", changedDatasetIds=" + changedDatasetIds
+                    + ", manuallyFilledIds=" + manuallyFilledIds);
+        }
+
+        ArrayList<AutofillId> manuallyFilledFieldIds = null;
+        ArrayList<ArrayList<String>> manuallyFilledDatasetIds = null;
+
+        // Must "flatten" the map to the parcellable collection primitives
+        if (manuallyFilledIds != null) {
+            final int size = manuallyFilledIds.size();
+            manuallyFilledFieldIds = new ArrayList<>(size);
+            manuallyFilledDatasetIds = new ArrayList<>(size);
+            for (int i = 0; i < size; i++) {
+                final AutofillId fieldId = manuallyFilledIds.keyAt(i);
+                final ArraySet<String> datasetIds = manuallyFilledIds.valueAt(i);
+                manuallyFilledFieldIds.add(fieldId);
+                manuallyFilledDatasetIds.add(new ArrayList<>(datasetIds));
+            }
+        }
+        mService.logContextCommitted(id, mClientState, mSelectedDatasetIds, ignoredDatasets,
+                changedFieldIds, changedDatasetIds,
+                manuallyFilledFieldIds, manuallyFilledDatasetIds);
+    }
+
+    /**
      * Shows the save UI, when session can be saved.
      *
      * @return {@code true} if session is done, or {@code false} if it's pending user action.
@@ -962,6 +1157,7 @@
                     boolean isValid;
                     try {
                         isValid = validator.isValid(valueFinder);
+                        if (sDebug) Slog.d(TAG, validator + " returned " + isValid);
                         log.setType(isValid
                                 ? MetricsEvent.TYPE_SUCCESS
                                 : MetricsEvent.TYPE_DISMISS);
@@ -1400,11 +1596,10 @@
      * Checks whether a view should be ignored.
      */
     private boolean isIgnoredLocked(AutofillId id) {
-        if (mResponses == null || mResponses.size() == 0) {
-            return false;
-        }
         // Always check the latest response only
-        final FillResponse response = mResponses.valueAt(mResponses.size() - 1);
+        final FillResponse response = getLastResponseLocked(null);
+        if (response == null) return false;
+
         return ArrayUtils.contains(response.getIgnoredIds(), id);
     }
 
@@ -1481,18 +1676,21 @@
     }
 
     private void updateTrackedIdsLocked() {
-        if (mResponses == null || mResponses.size() == 0) {
-            return;
-        }
-
         // Only track the views of the last response as only those are reported back to the
         // service, see #showSaveLocked
-        final FillResponse response = mResponses.valueAt(getLastResponseIndexLocked());
+        final FillResponse response = getLastResponseLocked(null);
+        if (response == null) return;
 
         ArraySet<AutofillId> trackedViews = null;
         boolean saveOnAllViewsInvisible = false;
+        boolean saveOnFinish = true;
         final SaveInfo saveInfo = response.getSaveInfo();
+        final AutofillId saveTriggerId;
         if (saveInfo != null) {
+            saveTriggerId = saveInfo.getTriggerId();
+            if (saveTriggerId != null) {
+                writeLog(MetricsEvent.AUTOFILL_EXPLICIT_SAVE_TRIGGER_DEFINITION);
+            }
             saveOnAllViewsInvisible =
                     (saveInfo.getFlags() & SaveInfo.FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE) != 0;
 
@@ -1509,6 +1707,12 @@
                     Collections.addAll(trackedViews, saveInfo.getOptionalIds());
                 }
             }
+            if ((saveInfo.getFlags() & SaveInfo.FLAG_DONT_SAVE_ON_FINISH) != 0) {
+                saveOnFinish = false;
+            }
+
+        } else {
+            saveTriggerId = null;
         }
 
         // Must also track that are part of datasets, otherwise the FillUI won't be hidden when
@@ -1533,17 +1737,18 @@
 
         try {
             if (sVerbose) {
-                Slog.v(TAG, "updateTrackedIdsLocked(): " + trackedViews + " => " + fillableIds);
+                Slog.v(TAG, "updateTrackedIdsLocked(): " + trackedViews + " => " + fillableIds
+                        + " (triggering on " + saveTriggerId + ")");
             }
             mClient.setTrackedViews(id, toArray(trackedViews), saveOnAllViewsInvisible,
-                    toArray(fillableIds));
+                    saveOnFinish, toArray(fillableIds), saveTriggerId);
         } catch (RemoteException e) {
             Slog.w(TAG, "Cannot set tracked ids", e);
         }
     }
 
     private void replaceResponseLocked(@NonNull FillResponse oldResponse,
-            @NonNull FillResponse newResponse) {
+            @NonNull FillResponse newResponse, @Nullable Bundle newClientState) {
         // Disassociate view states with the old response
         setViewStatesLocked(oldResponse, ViewState.STATE_INITIAL, true);
         // Move over the id
@@ -1551,7 +1756,7 @@
         // Replace the old response
         mResponses.put(newResponse.getRequestId(), newResponse);
         // Now process the new response
-        processResponseLocked(newResponse, 0);
+        processResponseLocked(newResponse, newClientState, 0);
     }
 
     private void processNullResponseLocked(int flags) {
@@ -1565,7 +1770,8 @@
         removeSelf();
     }
 
-    private void processResponseLocked(@NonNull FillResponse newResponse, int flags) {
+    private void processResponseLocked(@NonNull FillResponse newResponse,
+            @Nullable Bundle newClientState, int flags) {
         // Make sure we are hiding the UI which will be shown
         // only if handling the current response requires it.
         mUi.hideAll(this);
@@ -1573,14 +1779,18 @@
         final int requestId = newResponse.getRequestId();
         if (sVerbose) {
             Slog.v(TAG, "processResponseLocked(): mCurrentViewId=" + mCurrentViewId
-                    + ",flags=" + flags + ", reqId=" + requestId + ", resp=" + newResponse);
+                    + ",flags=" + flags + ", reqId=" + requestId + ", resp=" + newResponse
+                    + ",newClientState=" + newClientState);
         }
 
         if (mResponses == null) {
-            mResponses = new SparseArray<>(4);
+            // Set initial capacity as 2 to handle cases where service always requires auth.
+            // TODO: add a metric for number of responses set by server, so we can use its average
+            // as the initial array capacitiy.
+            mResponses = new SparseArray<>(2);
         }
         mResponses.put(requestId, newResponse);
-        mClientState = newResponse.getClientState();
+        mClientState = newClientState != null ? newClientState : newResponse.getClientState();
 
         setViewStatesLocked(newResponse, ViewState.STATE_FILLABLE, false);
         updateTrackedIdsLocked();
@@ -1653,6 +1863,10 @@
             final AutofillId id = ids.get(j);
             final AutofillValue value = values.get(j);
             final ViewState viewState = createOrUpdateViewStateLocked(id, state, value);
+            final String datasetId = dataset.getId();
+            if (datasetId != null) {
+                viewState.setDatasetId(datasetId);
+            }
             if (response != null) {
                 viewState.setResponse(response);
             } else if (clearResponse) {
diff --git a/com/android/server/autofill/ViewState.java b/com/android/server/autofill/ViewState.java
index 51659bb..1d8110f 100644
--- a/com/android/server/autofill/ViewState.java
+++ b/com/android/server/autofill/ViewState.java
@@ -78,6 +78,7 @@
     private AutofillValue mAutofilledValue;
     private Rect mVirtualBounds;
     private int mState;
+    private String mDatasetId;
 
     ViewState(Session session, AutofillId id, Listener listener, int state) {
         mSession = session;
@@ -148,6 +149,15 @@
         mState &= ~state;
     }
 
+    @Nullable
+    String getDatasetId() {
+        return mDatasetId;
+    }
+
+    void setDatasetId(String datasetId) {
+        mDatasetId = datasetId;
+    }
+
     // TODO: refactor / rename / document this method (and maybeCallOnFillReady) to make it clear
     // that it can change the value and update the UI; similarly, should replace code that
     // directly sets mAutofillValue to use encapsulation.
@@ -182,13 +192,15 @@
 
     @Override
     public String toString() {
-        return "ViewState: [id=" + id + ", currentValue=" + mCurrentValue
+        return "ViewState: [id=" + id + ", datasetId=" + mDatasetId
+                + ", currentValue=" + mCurrentValue
                 + ", autofilledValue=" + mAutofilledValue
                 + ", bounds=" + mVirtualBounds + ", state=" + getStateAsString() + "]";
     }
 
     void dump(String prefix, PrintWriter pw) {
         pw.print(prefix); pw.print("id:" ); pw.println(this.id);
+        pw.print(prefix); pw.print("datasetId:" ); pw.println(this.mDatasetId);
         pw.print(prefix); pw.print("state:" ); pw.println(getStateAsString());
         pw.print(prefix); pw.print("response:");
         if (mResponse == null) {
diff --git a/com/android/server/autofill/ui/FillUi.java b/com/android/server/autofill/ui/FillUi.java
index bf442dc..6d3d792 100644
--- a/com/android/server/autofill/ui/FillUi.java
+++ b/com/android/server/autofill/ui/FillUi.java
@@ -157,6 +157,11 @@
                 final int index = dataset.getFieldIds().indexOf(focusedViewId);
                 if (index >= 0) {
                     final RemoteViews presentation = dataset.getFieldPresentation(index);
+                    if (presentation == null) {
+                        Slog.w(TAG, "not displaying UI on field " + focusedViewId + " because "
+                                + "service didn't provide a presentation for it on " + dataset);
+                        continue;
+                    }
                     final View view;
                     try {
                         if (sVerbose) Slog.v(TAG, "setting remote view for " + focusedViewId);
diff --git a/com/android/server/autofill/ui/SaveUi.java b/com/android/server/autofill/ui/SaveUi.java
index d48f23c..307f74d 100644
--- a/com/android/server/autofill/ui/SaveUi.java
+++ b/com/android/server/autofill/ui/SaveUi.java
@@ -32,11 +32,15 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.service.autofill.BatchUpdates;
 import android.service.autofill.CustomDescription;
+import android.service.autofill.InternalTransformation;
+import android.service.autofill.InternalValidator;
 import android.service.autofill.SaveInfo;
 import android.service.autofill.ValueFinder;
 import android.text.Html;
 import android.util.ArraySet;
+import android.util.Pair;
 import android.util.Slog;
 import android.view.Gravity;
 import android.view.LayoutInflater;
@@ -57,6 +61,7 @@
 import com.android.server.UiThread;
 
 import java.io.PrintWriter;
+import java.util.ArrayList;
 
 /**
  * Autofill Save Prompt
@@ -185,68 +190,17 @@
 
         setServiceIcon(context, view, serviceIcon);
 
-        ScrollView subtitleContainer = null;
-        final CustomDescription customDescription = info.getCustomDescription();
-        if (customDescription != null) {
-            writeLog(MetricsEvent.AUTOFILL_SAVE_CUSTOM_DESCRIPTION, type);
-
+        final boolean hasCustomDescription =
+                applyCustomDescription(context, view, valueFinder, info);
+        if (hasCustomDescription) {
             mSubTitle = null;
-            if (sDebug) Slog.d(TAG, "Using custom description");
-
-            final RemoteViews presentation = customDescription.getPresentation(valueFinder);
-            if (presentation != null) {
-                final RemoteViews.OnClickHandler handler = new RemoteViews.OnClickHandler() {
-                    @Override
-                    public boolean onClickHandler(View view, PendingIntent pendingIntent,
-                            Intent intent) {
-                        final LogMaker log =
-                                newLogMaker(MetricsEvent.AUTOFILL_SAVE_LINK_TAPPED, type);
-                        // We need to hide the Save UI before launching the pending intent, and
-                        // restore back it once the activity is finished, and that's achieved by
-                        // adding a custom extra in the activity intent.
-                        final boolean isValid = isValidLink(pendingIntent, intent);
-                        if (!isValid) {
-                            log.setType(MetricsEvent.TYPE_UNKNOWN);
-                            mMetricsLogger.write(log);
-                            return false;
-                        }
-                        if (sVerbose) Slog.v(TAG, "Intercepting custom description intent");
-                        final IBinder token = mPendingUi.getToken();
-                        intent.putExtra(AutofillManager.EXTRA_RESTORE_SESSION_TOKEN, token);
-                        try {
-                            pendingUi.client.startIntentSender(pendingIntent.getIntentSender(),
-                                    intent);
-                            mPendingUi.setState(PendingUi.STATE_PENDING);
-                            if (sDebug) Slog.d(TAG, "hiding UI until restored with token " + token);
-                            hide();
-                            log.setType(MetricsEvent.TYPE_OPEN);
-                            mMetricsLogger.write(log);
-                            return true;
-                        } catch (RemoteException e) {
-                            Slog.w(TAG, "error triggering pending intent: " + intent);
-                            log.setType(MetricsEvent.TYPE_FAILURE);
-                            mMetricsLogger.write(log);
-                            return false;
-                        }
-                    }
-                };
-
-                try {
-                    final View customSubtitleView = presentation.apply(context, null, handler);
-                    subtitleContainer = view.findViewById(R.id.autofill_save_custom_subtitle);
-                    subtitleContainer.addView(customSubtitleView);
-                    subtitleContainer.setVisibility(View.VISIBLE);
-                } catch (Exception e) {
-                    Slog.e(TAG, "Could not inflate custom description. ", e);
-                }
-            } else {
-                Slog.w(TAG, "could not create remote presentation for custom title");
-            }
+            if (sDebug) Slog.d(TAG, "on constructor: applied custom description");
         } else {
             mSubTitle = info.getDescription();
             if (mSubTitle != null) {
                 writeLog(MetricsEvent.AUTOFILL_SAVE_CUSTOM_SUBTITLE, type);
-                subtitleContainer = view.findViewById(R.id.autofill_save_custom_subtitle);
+                final ScrollView subtitleContainer =
+                        view.findViewById(R.id.autofill_save_custom_subtitle);
                 final TextView subtitleView = new TextView(context);
                 subtitleView.setText(mSubTitle);
                 subtitleContainer.addView(subtitleView,
@@ -293,6 +247,122 @@
         show();
     }
 
+    private boolean applyCustomDescription(@NonNull Context context, @NonNull View saveUiView,
+            @NonNull ValueFinder valueFinder, @NonNull SaveInfo info) {
+        final CustomDescription customDescription = info.getCustomDescription();
+        if (customDescription == null) {
+            return false;
+        }
+        final int type = info.getType();
+        writeLog(MetricsEvent.AUTOFILL_SAVE_CUSTOM_DESCRIPTION, type);
+
+        final RemoteViews template = customDescription.getPresentation();
+        if (template == null) {
+            Slog.w(TAG, "No remote view on custom description");
+            return false;
+        }
+
+        // First apply the unconditional transformations (if any) to the templates.
+        final ArrayList<Pair<Integer, InternalTransformation>> transformations =
+                customDescription.getTransformations();
+        if (transformations != null) {
+            if (!InternalTransformation.batchApply(valueFinder, template, transformations)) {
+                Slog.w(TAG, "could not apply main transformations on custom description");
+                return false;
+            }
+        }
+
+        final RemoteViews.OnClickHandler handler = new RemoteViews.OnClickHandler() {
+            @Override
+            public boolean onClickHandler(View view, PendingIntent pendingIntent,
+                    Intent intent) {
+                final LogMaker log =
+                        newLogMaker(MetricsEvent.AUTOFILL_SAVE_LINK_TAPPED, type);
+                // We need to hide the Save UI before launching the pending intent, and
+                // restore back it once the activity is finished, and that's achieved by
+                // adding a custom extra in the activity intent.
+                final boolean isValid = isValidLink(pendingIntent, intent);
+                if (!isValid) {
+                    log.setType(MetricsEvent.TYPE_UNKNOWN);
+                    mMetricsLogger.write(log);
+                    return false;
+                }
+                if (sVerbose) Slog.v(TAG, "Intercepting custom description intent");
+                final IBinder token = mPendingUi.getToken();
+                intent.putExtra(AutofillManager.EXTRA_RESTORE_SESSION_TOKEN, token);
+                try {
+                    mPendingUi.client.startIntentSender(pendingIntent.getIntentSender(),
+                            intent);
+                    mPendingUi.setState(PendingUi.STATE_PENDING);
+                    if (sDebug) Slog.d(TAG, "hiding UI until restored with token " + token);
+                    hide();
+                    log.setType(MetricsEvent.TYPE_OPEN);
+                    mMetricsLogger.write(log);
+                    return true;
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "error triggering pending intent: " + intent);
+                    log.setType(MetricsEvent.TYPE_FAILURE);
+                    mMetricsLogger.write(log);
+                    return false;
+                }
+            }
+        };
+
+        try {
+            // Create the remote view peer.
+            final View customSubtitleView = template.apply(context, null, handler);
+
+            // And apply batch updates (if any).
+            final ArrayList<Pair<InternalValidator, BatchUpdates>> updates =
+                    customDescription.getUpdates();
+            if (updates != null) {
+                final int size = updates.size();
+                if (sDebug) Slog.d(TAG, "custom description has " + size + " batch updates");
+                for (int i = 0; i < size; i++) {
+                    final Pair<InternalValidator, BatchUpdates> pair = updates.get(i);
+                    final InternalValidator condition = pair.first;
+                    if (condition == null || !condition.isValid(valueFinder)) {
+                        if (sDebug) Slog.d(TAG, "Skipping batch update #" + i );
+                        continue;
+                    }
+                    final BatchUpdates batchUpdates = pair.second;
+                    // First apply the updates...
+                    final RemoteViews templateUpdates = batchUpdates.getUpdates();
+                    if (templateUpdates != null) {
+                        if (sDebug) Slog.d(TAG, "Applying template updates for batch update #" + i);
+                        templateUpdates.reapply(context, customSubtitleView);
+                    }
+                    // Then the transformations...
+                    final ArrayList<Pair<Integer, InternalTransformation>> batchTransformations =
+                            batchUpdates.getTransformations();
+                    if (batchTransformations != null) {
+                        if (sDebug) {
+                            Slog.d(TAG, "Applying child transformation for batch update #" + i
+                                    + ": " + batchTransformations);
+                        }
+                        if (!InternalTransformation.batchApply(valueFinder, template,
+                                batchTransformations)) {
+                            Slog.w(TAG, "Could not apply child transformation for batch update "
+                                    + "#" + i + ": " + batchTransformations);
+                            return false;
+                        }
+                        template.reapply(context, customSubtitleView);
+                    }
+                }
+            }
+
+            // Finally, add the custom description to the save UI.
+            final ScrollView subtitleContainer =
+                    saveUiView.findViewById(R.id.autofill_save_custom_subtitle);
+            subtitleContainer.addView(customSubtitleView);
+            subtitleContainer.setVisibility(View.VISIBLE);
+            return true;
+        } catch (Exception e) {
+            Slog.e(TAG, "Error applying custom description. ", e);
+        }
+        return false;
+    }
+
     private void setServiceIcon(Context context, View view, Drawable serviceIcon) {
         final ImageView iconView = view.findViewById(R.id.autofill_save_icon);
         final Resources res = context.getResources();
diff --git a/com/android/server/backup/BackupManagerService.java b/com/android/server/backup/BackupManagerService.java
index eabe21f..f9213aa 100644
--- a/com/android/server/backup/BackupManagerService.java
+++ b/com/android/server/backup/BackupManagerService.java
@@ -319,7 +319,6 @@
     boolean mProvisioned;
     boolean mAutoRestore;
     PowerManager.WakeLock mWakelock;
-    HandlerThread mHandlerThread;
     BackupHandler mBackupHandler;
     PendingIntent mRunBackupIntent, mRunInitIntent;
     BroadcastReceiver mRunBackupReceiver, mRunInitReceiver;
@@ -409,43 +408,37 @@
     // Called through the trampoline from onUnlockUser(), then we buck the work
     // off to the background thread to keep the unlock time down.
     public void unlockSystemUser() {
-        mBackupHandler.post(() -> {
-            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "backup init");
-            sInstance.initialize(UserHandle.USER_SYSTEM);
-            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
-
-            // Migrate legacy setting
-            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "backup migrate");
-            if (!backupSettingMigrated(UserHandle.USER_SYSTEM)) {
+        // Migrate legacy setting
+        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "backup migrate");
+        if (!backupSettingMigrated(UserHandle.USER_SYSTEM)) {
+            if (DEBUG) {
+                Slog.i(TAG, "Backup enable apparently not migrated");
+            }
+            final ContentResolver r = sInstance.mContext.getContentResolver();
+            final int enableState = Settings.Secure.getIntForUser(r,
+                    Settings.Secure.BACKUP_ENABLED, -1, UserHandle.USER_SYSTEM);
+            if (enableState >= 0) {
                 if (DEBUG) {
-                    Slog.i(TAG, "Backup enable apparently not migrated");
+                    Slog.i(TAG, "Migrating enable state " + (enableState != 0));
                 }
-                final ContentResolver r = sInstance.mContext.getContentResolver();
-                final int enableState = Settings.Secure.getIntForUser(r,
-                        Settings.Secure.BACKUP_ENABLED, -1, UserHandle.USER_SYSTEM);
-                if (enableState >= 0) {
-                    if (DEBUG) {
-                        Slog.i(TAG, "Migrating enable state " + (enableState != 0));
-                    }
-                    writeBackupEnableState(enableState != 0, UserHandle.USER_SYSTEM);
-                    Settings.Secure.putStringForUser(r,
-                            Settings.Secure.BACKUP_ENABLED, null, UserHandle.USER_SYSTEM);
-                } else {
-                    if (DEBUG) {
-                        Slog.i(TAG, "Backup not yet configured; retaining null enable state");
-                    }
+                writeBackupEnableState(enableState != 0, UserHandle.USER_SYSTEM);
+                Settings.Secure.putStringForUser(r,
+                        Settings.Secure.BACKUP_ENABLED, null, UserHandle.USER_SYSTEM);
+            } else {
+                if (DEBUG) {
+                    Slog.i(TAG, "Backup not yet configured; retaining null enable state");
                 }
             }
-            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+        }
+        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
 
-            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "backup enable");
-            try {
-                sInstance.setBackupEnabled(readBackupEnableState(UserHandle.USER_SYSTEM));
-            } catch (RemoteException e) {
-                // can't happen; it's a local object
-            }
-            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
-        });
+        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "backup enable");
+        try {
+            sInstance.setBackupEnabled(readBackupEnableState(UserHandle.USER_SYSTEM));
+        } catch (RemoteException e) {
+            // can't happen; it's a local object
+        }
+        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
     }
 
     class ProvisionedObserver extends ContentObserver {
@@ -1220,7 +1213,7 @@
 
     // ----- Main service implementation -----
 
-    public BackupManagerService(Context context, Trampoline parent) {
+    public BackupManagerService(Context context, Trampoline parent, HandlerThread backupThread) {
         mContext = context;
         mPackageManager = context.getPackageManager();
         mPackageManagerBinder = AppGlobals.getPackageManager();
@@ -1233,9 +1226,7 @@
         mBackupManagerBinder = Trampoline.asInterface(parent.asBinder());
 
         // spin up the backup/restore handler thread
-        mHandlerThread = new HandlerThread("backup", Process.THREAD_PRIORITY_BACKGROUND);
-        mHandlerThread.start();
-        mBackupHandler = new BackupHandler(mHandlerThread.getLooper());
+        mBackupHandler = new BackupHandler(backupThread.getLooper());
 
         // Set up our bookkeeping
         final ContentResolver resolver = context.getContentResolver();
@@ -1360,7 +1351,7 @@
         if (DEBUG) Slog.v(TAG, "Starting with transport " + currentTransport);
 
         mTransportManager = new TransportManager(context, transportWhitelist, currentTransport,
-                mTransportBoundListener, mHandlerThread.getLooper());
+                mTransportBoundListener, backupThread.getLooper());
         mTransportManager.registerAllTransports();
 
         // Now that we know about valid backup participants, parse any
diff --git a/com/android/server/backup/RefactoredBackupManagerService.java b/com/android/server/backup/RefactoredBackupManagerService.java
index f298065..20f2369 100644
--- a/com/android/server/backup/RefactoredBackupManagerService.java
+++ b/com/android/server/backup/RefactoredBackupManagerService.java
@@ -237,7 +237,6 @@
     private boolean mProvisioned;
     private boolean mAutoRestore;
     private PowerManager.WakeLock mWakelock;
-    private HandlerThread mHandlerThread;
     private BackupHandler mBackupHandler;
     private PendingIntent mRunBackupIntent;
     private PendingIntent mRunInitIntent;
@@ -556,43 +555,37 @@
     // Called through the trampoline from onUnlockUser(), then we buck the work
     // off to the background thread to keep the unlock time down.
     public void unlockSystemUser() {
-        mBackupHandler.post(() -> {
-            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "backup init");
-            sInstance.initialize(UserHandle.USER_SYSTEM);
-            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
-
-            // Migrate legacy setting
-            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "backup migrate");
-            if (!backupSettingMigrated(UserHandle.USER_SYSTEM)) {
+        // Migrate legacy setting
+        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "backup migrate");
+        if (!backupSettingMigrated(UserHandle.USER_SYSTEM)) {
+            if (DEBUG) {
+                Slog.i(TAG, "Backup enable apparently not migrated");
+            }
+            final ContentResolver r = sInstance.mContext.getContentResolver();
+            final int enableState = Settings.Secure.getIntForUser(r,
+                    Settings.Secure.BACKUP_ENABLED, -1, UserHandle.USER_SYSTEM);
+            if (enableState >= 0) {
                 if (DEBUG) {
-                    Slog.i(TAG, "Backup enable apparently not migrated");
+                    Slog.i(TAG, "Migrating enable state " + (enableState != 0));
                 }
-                final ContentResolver r = sInstance.mContext.getContentResolver();
-                final int enableState = Settings.Secure.getIntForUser(r,
-                        Settings.Secure.BACKUP_ENABLED, -1, UserHandle.USER_SYSTEM);
-                if (enableState >= 0) {
-                    if (DEBUG) {
-                        Slog.i(TAG, "Migrating enable state " + (enableState != 0));
-                    }
-                    writeBackupEnableState(enableState != 0, UserHandle.USER_SYSTEM);
-                    Settings.Secure.putStringForUser(r,
-                            Settings.Secure.BACKUP_ENABLED, null, UserHandle.USER_SYSTEM);
-                } else {
-                    if (DEBUG) {
-                        Slog.i(TAG, "Backup not yet configured; retaining null enable state");
-                    }
+                writeBackupEnableState(enableState != 0, UserHandle.USER_SYSTEM);
+                Settings.Secure.putStringForUser(r,
+                        Settings.Secure.BACKUP_ENABLED, null, UserHandle.USER_SYSTEM);
+            } else {
+                if (DEBUG) {
+                    Slog.i(TAG, "Backup not yet configured; retaining null enable state");
                 }
             }
-            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+        }
+        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
 
-            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "backup enable");
-            try {
-                sInstance.setBackupEnabled(readBackupEnableState(UserHandle.USER_SYSTEM));
-            } catch (RemoteException e) {
-                // can't happen; it's a local object
-            }
-            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
-        });
+        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "backup enable");
+        try {
+            sInstance.setBackupEnabled(readBackupEnableState(UserHandle.USER_SYSTEM));
+        } catch (RemoteException e) {
+            // can't happen; it's a local object
+        }
+        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
     }
 
     // Bookkeeping of in-flight operations for timeout etc. purposes.  The operation
@@ -729,7 +722,8 @@
 
     // ----- Main service implementation -----
 
-    public RefactoredBackupManagerService(Context context, Trampoline parent) {
+    public RefactoredBackupManagerService(Context context, Trampoline parent,
+            HandlerThread backupThread) {
         mContext = context;
         mPackageManager = context.getPackageManager();
         mPackageManagerBinder = AppGlobals.getPackageManager();
@@ -742,9 +736,7 @@
         mBackupManagerBinder = Trampoline.asInterface(parent.asBinder());
 
         // spin up the backup/restore handler thread
-        mHandlerThread = new HandlerThread("backup", Process.THREAD_PRIORITY_BACKGROUND);
-        mHandlerThread.start();
-        mBackupHandler = new BackupHandler(this, mHandlerThread.getLooper());
+        mBackupHandler = new BackupHandler(this, backupThread.getLooper());
 
         // Set up our bookkeeping
         final ContentResolver resolver = context.getContentResolver();
@@ -824,7 +816,7 @@
         if (DEBUG) Slog.v(TAG, "Starting with transport " + currentTransport);
 
         mTransportManager = new TransportManager(context, transportWhitelist, currentTransport,
-                mTransportBoundListener, mHandlerThread.getLooper());
+                mTransportBoundListener, backupThread.getLooper());
         mTransportManager.registerAllTransports();
 
         // Now that we know about valid backup participants, parse any
diff --git a/com/android/server/backup/Trampoline.java b/com/android/server/backup/Trampoline.java
index 9739e38..9847edf 100644
--- a/com/android/server/backup/Trampoline.java
+++ b/com/android/server/backup/Trampoline.java
@@ -28,11 +28,15 @@
 import android.content.Intent;
 import android.os.Binder;
 import android.os.Environment;
+import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.IBinder;
+import android.os.Looper;
 import android.os.ParcelFileDescriptor;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.SystemProperties;
+import android.os.Trace;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.util.Slog;
@@ -75,6 +79,8 @@
     final boolean mGlobalDisable;
     volatile BackupManagerServiceInterface mService;
 
+    private HandlerThread mHandlerThread;
+
     public Trampoline(Context context) {
         mContext = context;
         mGlobalDisable = isBackupDisabled();
@@ -111,11 +117,11 @@
     }
 
     protected BackupManagerServiceInterface createRefactoredBackupManagerService() {
-        return new RefactoredBackupManagerService(mContext, this);
+        return new RefactoredBackupManagerService(mContext, this, mHandlerThread);
     }
 
     protected BackupManagerServiceInterface createBackupManagerService() {
-        return new BackupManagerService(mContext, this);
+        return new BackupManagerService(mContext, this, mHandlerThread);
     }
 
     // internal control API
@@ -140,10 +146,21 @@
     }
 
     void unlockSystemUser() {
-        BackupManagerServiceInterface svc = mService;
-        if (svc != null) {
-            svc.unlockSystemUser();
-        }
+        mHandlerThread = new HandlerThread("backup", Process.THREAD_PRIORITY_BACKGROUND);
+        mHandlerThread.start();
+
+        Handler h = new Handler(mHandlerThread.getLooper());
+        h.post(() -> {
+            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "backup init");
+            initialize(UserHandle.USER_SYSTEM);
+            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+
+            BackupManagerServiceInterface svc = mService;
+            Slog.i(TAG, "Unlocking system user; mService=" + mService);
+            if (svc != null) {
+                svc.unlockSystemUser();
+            }
+        });
     }
 
     public void setBackupServiceActive(final int userHandle, boolean makeActive) {
diff --git a/com/android/server/clipboard/ClipboardService.java b/com/android/server/clipboard/ClipboardService.java
index e6228d4..0c9d70a 100644
--- a/com/android/server/clipboard/ClipboardService.java
+++ b/com/android/server/clipboard/ClipboardService.java
@@ -435,11 +435,12 @@
     }
 
     private boolean isDeviceLocked() {
+        int callingUserId = UserHandle.getCallingUserId();
         final long token = Binder.clearCallingIdentity();
         try {
             final KeyguardManager keyguardManager = getContext().getSystemService(
                     KeyguardManager.class);
-            return keyguardManager != null && keyguardManager.isDeviceLocked();
+            return keyguardManager != null && keyguardManager.isDeviceLocked(callingUserId);
         } finally {
             Binder.restoreCallingIdentity(token);
         }
diff --git a/com/android/server/connectivity/NetdEventListenerService.java b/com/android/server/connectivity/NetdEventListenerService.java
index 6206dfc..05c6e69 100644
--- a/com/android/server/connectivity/NetdEventListenerService.java
+++ b/com/android/server/connectivity/NetdEventListenerService.java
@@ -27,6 +27,7 @@
 import android.net.metrics.DnsEvent;
 import android.net.metrics.INetdEventListener;
 import android.net.metrics.IpConnectivityLog;
+import android.net.metrics.NetworkMetrics;
 import android.net.metrics.WakeupEvent;
 import android.net.metrics.WakeupStats;
 import android.os.RemoteException;
@@ -34,6 +35,7 @@
 import android.util.Log;
 import android.util.ArrayMap;
 import android.util.SparseArray;
+
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.BitUtils;
@@ -41,10 +43,11 @@
 import com.android.internal.util.RingBuffer;
 import com.android.internal.util.TokenBucket;
 import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent;
+
 import java.io.PrintWriter;
+import java.util.ArrayList;
 import java.util.List;
-import java.util.function.Function;
-import java.util.function.IntFunction;
+import java.util.StringJoiner;
 
 /**
  * Implementation of the INetdEventListener interface.
@@ -57,13 +60,13 @@
     private static final boolean DBG = false;
     private static final boolean VDBG = false;
 
-    private static final int INITIAL_DNS_BATCH_SIZE = 100;
-
     // Rate limit connect latency logging to 1 measurement per 15 seconds (5760 / day) with maximum
     // bursts of 5000 measurements.
     private static final int CONNECT_LATENCY_BURST_LIMIT  = 5000;
     private static final int CONNECT_LATENCY_FILL_RATE    = 15 * (int) DateUtils.SECOND_IN_MILLIS;
-    private static final int CONNECT_LATENCY_MAXIMUM_RECORDS = 20000;
+
+    private static final long METRICS_SNAPSHOT_SPAN_MS = 5 * DateUtils.MINUTE_IN_MILLIS;
+    private static final int METRICS_SNAPSHOT_BUFFER_SIZE = 48; // 4 hours
 
     @VisibleForTesting
     static final int WAKEUP_EVENT_BUFFER_LENGTH = 1024;
@@ -72,11 +75,15 @@
     @VisibleForTesting
     static final String WAKEUP_EVENT_IFACE_PREFIX = "iface:";
 
-    // Sparse arrays of DNS and connect events, grouped by net id.
+    // Array of aggregated DNS and connect events sent by netd, grouped by net id.
     @GuardedBy("this")
-    private final SparseArray<DnsEvent> mDnsEvents = new SparseArray<>();
+    private final SparseArray<NetworkMetrics> mNetworkMetrics = new SparseArray<>();
+
     @GuardedBy("this")
-    private final SparseArray<ConnectStats> mConnectEvents = new SparseArray<>();
+    private final RingBuffer<NetworkMetricsSnapshot> mNetworkMetricsSnapshots =
+            new RingBuffer<>(NetworkMetricsSnapshot.class, METRICS_SNAPSHOT_BUFFER_SIZE);
+    @GuardedBy("this")
+    private long mLastSnapshot = 0;
 
     // Array of aggregated wakeup event stats, grouped by interface name.
     @GuardedBy("this")
@@ -84,7 +91,7 @@
     // Ring buffer array for storing packet wake up events sent by Netd.
     @GuardedBy("this")
     private final RingBuffer<WakeupEvent> mWakeupEvents =
-            new RingBuffer(WakeupEvent.class, WAKEUP_EVENT_BUFFER_LENGTH);
+            new RingBuffer<>(WakeupEvent.class, WAKEUP_EVENT_BUFFER_LENGTH);
 
     private final ConnectivityManager mCm;
 
@@ -116,6 +123,41 @@
         mCm = cm;
     }
 
+    private static long projectSnapshotTime(long timeMs) {
+        return (timeMs / METRICS_SNAPSHOT_SPAN_MS) * METRICS_SNAPSHOT_SPAN_MS;
+    }
+
+    private NetworkMetrics getMetricsForNetwork(long timeMs, int netId) {
+        collectPendingMetricsSnapshot(timeMs);
+        NetworkMetrics metrics = mNetworkMetrics.get(netId);
+        if (metrics == null) {
+            // TODO: allow to change transport for a given netid.
+            metrics = new NetworkMetrics(netId, getTransports(netId), mConnectTb);
+            mNetworkMetrics.put(netId, metrics);
+        }
+        return metrics;
+    }
+
+    private NetworkMetricsSnapshot[] getNetworkMetricsSnapshots() {
+        collectPendingMetricsSnapshot(System.currentTimeMillis());
+        return mNetworkMetricsSnapshots.toArray();
+    }
+
+    private void collectPendingMetricsSnapshot(long timeMs) {
+        // Detects time differences larger than the snapshot collection period.
+        // This is robust against clock jumps and long inactivity periods.
+        if (Math.abs(timeMs - mLastSnapshot) <= METRICS_SNAPSHOT_SPAN_MS) {
+            return;
+        }
+        mLastSnapshot = projectSnapshotTime(timeMs);
+        NetworkMetricsSnapshot snapshot =
+                NetworkMetricsSnapshot.collect(mLastSnapshot, mNetworkMetrics);
+        if (snapshot.stats.isEmpty()) {
+            return;
+        }
+        mNetworkMetricsSnapshots.append(snapshot);
+    }
+
     @Override
     // Called concurrently by multiple binder threads.
     // This method must not block or perform long-running operations.
@@ -124,15 +166,10 @@
             throws RemoteException {
         maybeVerboseLog("onDnsEvent(%d, %d, %d, %dms)", netId, eventType, returnCode, latencyMs);
 
-        DnsEvent dnsEvent = mDnsEvents.get(netId);
-        if (dnsEvent == null) {
-            dnsEvent = makeDnsEvent(netId);
-            mDnsEvents.put(netId, dnsEvent);
-        }
-        dnsEvent.addResult((byte) eventType, (byte) returnCode, latencyMs);
+        long timestamp = System.currentTimeMillis();
+        getMetricsForNetwork(timestamp, netId).addDnsResult(eventType, returnCode, latencyMs);
 
         if (mNetdEventCallback != null) {
-            long timestamp = System.currentTimeMillis();
             mNetdEventCallback.onDnsEvent(hostname, ipAddresses, ipAddressesCount, timestamp, uid);
         }
     }
@@ -144,15 +181,11 @@
             int port, int uid) throws RemoteException {
         maybeVerboseLog("onConnectEvent(%d, %d, %dms)", netId, error, latencyMs);
 
-        ConnectStats connectStats = mConnectEvents.get(netId);
-        if (connectStats == null) {
-            connectStats = makeConnectStats(netId);
-            mConnectEvents.put(netId, connectStats);
-        }
-        connectStats.addEvent(error, latencyMs, ipAddr);
+        long timestamp = System.currentTimeMillis();
+        getMetricsForNetwork(timestamp, netId).addConnectResult(error, latencyMs, ipAddr);
 
         if (mNetdEventCallback != null) {
-            mNetdEventCallback.onConnectEvent(ipAddr, port, System.currentTimeMillis(), uid);
+            mNetdEventCallback.onConnectEvent(ipAddr, port, timestamp, uid);
         }
     }
 
@@ -189,11 +222,24 @@
     }
 
     public synchronized void flushStatistics(List<IpConnectivityEvent> events) {
-        flushProtos(events, mConnectEvents, IpConnectivityEventBuilder::toProto);
-        flushProtos(events, mDnsEvents, IpConnectivityEventBuilder::toProto);
+        for (int i = 0; i < mNetworkMetrics.size(); i++) {
+            ConnectStats stats = mNetworkMetrics.valueAt(i).connectMetrics;
+            if (stats.eventCount == 0) {
+                continue;
+            }
+            events.add(IpConnectivityEventBuilder.toProto(stats));
+        }
+        for (int i = 0; i < mNetworkMetrics.size(); i++) {
+            DnsEvent ev = mNetworkMetrics.valueAt(i).dnsMetrics;
+            if (ev.eventCount == 0) {
+                continue;
+            }
+            events.add(IpConnectivityEventBuilder.toProto(ev));
+        }
         for (int i = 0; i < mWakeupStats.size(); i++) {
             events.add(IpConnectivityEventBuilder.toProto(mWakeupStats.valueAt(i)));
         }
+        mNetworkMetrics.clear();
         mWakeupStats.clear();
     }
 
@@ -206,8 +252,15 @@
     }
 
     public synchronized void list(PrintWriter pw) {
-        listEvents(pw, mConnectEvents, (x) -> x, "\n");
-        listEvents(pw, mDnsEvents, (x) -> x, "\n");
+        for (int i = 0; i < mNetworkMetrics.size(); i++) {
+            pw.println(mNetworkMetrics.valueAt(i).connectMetrics);
+        }
+        for (int i = 0; i < mNetworkMetrics.size(); i++) {
+            pw.println(mNetworkMetrics.valueAt(i).dnsMetrics);
+        }
+        for (NetworkMetricsSnapshot s : getNetworkMetricsSnapshots()) {
+            pw.println(s);
+        }
         for (int i = 0; i < mWakeupStats.size(); i++) {
             pw.println(mWakeupStats.valueAt(i));
         }
@@ -217,41 +270,17 @@
     }
 
     public synchronized void listAsProtos(PrintWriter pw) {
-        listEvents(pw, mConnectEvents, IpConnectivityEventBuilder::toProto, "");
-        listEvents(pw, mDnsEvents, IpConnectivityEventBuilder::toProto, "");
+        for (int i = 0; i < mNetworkMetrics.size(); i++) {
+            pw.print(IpConnectivityEventBuilder.toProto(mNetworkMetrics.valueAt(i).connectMetrics));
+        }
+        for (int i = 0; i < mNetworkMetrics.size(); i++) {
+            pw.print(IpConnectivityEventBuilder.toProto(mNetworkMetrics.valueAt(i).dnsMetrics));
+        }
         for (int i = 0; i < mWakeupStats.size(); i++) {
             pw.print(IpConnectivityEventBuilder.toProto(mWakeupStats.valueAt(i)));
         }
     }
 
-    private static <T> void flushProtos(List<IpConnectivityEvent> out, SparseArray<T> in,
-            Function<T, IpConnectivityEvent> mapper) {
-        for (int i = 0; i < in.size(); i++) {
-            out.add(mapper.apply(in.valueAt(i)));
-        }
-        in.clear();
-    }
-
-    private static <T> void listEvents(
-            PrintWriter pw, SparseArray<T> events, Function<T, Object> mapper, String separator) {
-        // Proto derived Classes have toString method that adds a \n at the end.
-        // Let the caller control that by passing in the line separator explicitly.
-        for (int i = 0; i < events.size(); i++) {
-            pw.print(mapper.apply(events.valueAt(i)));
-            pw.print(separator);
-        }
-    }
-
-    private ConnectStats makeConnectStats(int netId) {
-        long transports = getTransports(netId);
-        return new ConnectStats(netId, transports, mConnectTb, CONNECT_LATENCY_MAXIMUM_RECORDS);
-    }
-
-    private DnsEvent makeDnsEvent(int netId) {
-        long transports = getTransports(netId);
-        return new DnsEvent(netId, transports, INITIAL_DNS_BATCH_SIZE);
-    }
-
     private long getTransports(int netId) {
         // TODO: directly query ConnectivityService instead of going through Binder interface.
         NetworkCapabilities nc = mCm.getNetworkCapabilities(new Network(netId));
@@ -268,4 +297,32 @@
     private static void maybeVerboseLog(String s, Object... args) {
         if (VDBG) Log.d(TAG, String.format(s, args));
     }
+
+    /** Helper class for buffering summaries of NetworkMetrics at regular time intervals */
+    static class NetworkMetricsSnapshot {
+
+        public long timeMs;
+        public List<NetworkMetrics.Summary> stats = new ArrayList<>();
+
+        static NetworkMetricsSnapshot collect(long timeMs, SparseArray<NetworkMetrics> networkMetrics) {
+            NetworkMetricsSnapshot snapshot = new NetworkMetricsSnapshot();
+            snapshot.timeMs = timeMs;
+            for (int i = 0; i < networkMetrics.size(); i++) {
+                NetworkMetrics.Summary s = networkMetrics.valueAt(i).getPendingStats();
+                if (s != null) {
+                    snapshot.stats.add(s);
+                }
+            }
+            return snapshot;
+        }
+
+        @Override
+        public String toString() {
+            StringJoiner j = new StringJoiner(", ");
+            for (NetworkMetrics.Summary s : stats) {
+                j.add(s.toString());
+            }
+            return String.format("%tT.%tL: %s", timeMs, timeMs, j.toString());
+        }
+    }
 }
diff --git a/com/android/server/connectivity/Tethering.java b/com/android/server/connectivity/Tethering.java
index 5583e86..d7cd81f 100644
--- a/com/android/server/connectivity/Tethering.java
+++ b/com/android/server/connectivity/Tethering.java
@@ -1371,6 +1371,7 @@
                     sendMessageDelayed(CMD_RETRY_UPSTREAM, UPSTREAM_SETTLE_TIME_MS);
                 }
             }
+            mUpstreamNetworkMonitor.setCurrentUpstream((ns != null) ? ns.network : null);
             setUpstreamNetwork(ns);
         }
 
diff --git a/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java b/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
index c5f7528..b35ed75 100644
--- a/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
+++ b/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
@@ -95,7 +95,10 @@
     private NetworkCallback mDefaultNetworkCallback;
     private NetworkCallback mMobileNetworkCallback;
     private boolean mDunRequired;
-    private Network mCurrentDefault;
+    // The current system default network (not really used yet).
+    private Network mDefaultInternetNetwork;
+    // The current upstream network used for tethering.
+    private Network mTetheringUpstreamNetwork;
 
     public UpstreamNetworkMonitor(Context ctx, StateMachine tgt, SharedLog log, int what) {
         mContext = ctx;
@@ -130,10 +133,12 @@
 
         releaseCallback(mDefaultNetworkCallback);
         mDefaultNetworkCallback = null;
+        mDefaultInternetNetwork = null;
 
         releaseCallback(mListenAllCallback);
         mListenAllCallback = null;
 
+        mTetheringUpstreamNetwork = null;
         mNetworkMap.clear();
     }
 
@@ -207,7 +212,7 @@
                 break;
             default:
                 /* If we've found an active upstream connection that's not DUN/HIPRI
-                 * we should stop any outstanding DUN/HIPRI start requests.
+                 * we should stop any outstanding DUN/HIPRI requests.
                  *
                  * If we found NONE we don't want to do this as we want any previous
                  * requests to keep trying to bring up something we can use.
@@ -219,6 +224,10 @@
         return typeStatePair.ns;
     }
 
+    public void setCurrentUpstream(Network upstream) {
+        mTetheringUpstreamNetwork = upstream;
+    }
+
     public Set<IpPrefix> getLocalPrefixes() {
         return (Set<IpPrefix>) mLocalPrefixes.clone();
     }
@@ -250,7 +259,7 @@
                     // These request*() calls can be deleted post oag/339444.
                     return;
                 }
-                mCurrentDefault = network;
+                mDefaultInternetNetwork = network;
                 break;
 
             case CALLBACK_MOBILE_REQUEST:
@@ -302,6 +311,13 @@
                     network, newNc));
         }
 
+        // Log changes in upstream network signal strength, if available.
+        if (network.equals(mTetheringUpstreamNetwork) && newNc.hasSignalStrength()) {
+            final int newSignal = newNc.getSignalStrength();
+            final String prevSignal = getSignalStrength(prev.networkCapabilities);
+            mLog.logf("upstream network signal strength: %s -> %s", prevSignal, newSignal);
+        }
+
         mNetworkMap.put(network, new NetworkState(
                 null, prev.linkProperties, newNc, network, null, null));
         // TODO: If sufficient information is available to select a more
@@ -330,9 +346,21 @@
         notifyTarget(EVENT_ON_LINKPROPERTIES, network);
     }
 
+    private void handleSuspended(int callbackType, Network network) {
+        if (callbackType != CALLBACK_LISTEN_ALL) return;
+        if (!network.equals(mTetheringUpstreamNetwork)) return;
+        mLog.log("SUSPENDED current upstream: " + network);
+    }
+
+    private void handleResumed(int callbackType, Network network) {
+        if (callbackType != CALLBACK_LISTEN_ALL) return;
+        if (!network.equals(mTetheringUpstreamNetwork)) return;
+        mLog.log("RESUMED current upstream: " + network);
+    }
+
     private void handleLost(int callbackType, Network network) {
         if (callbackType == CALLBACK_TRACK_DEFAULT) {
-            mCurrentDefault = null;
+            mDefaultInternetNetwork = null;
             // Receiving onLost() for a default network does not necessarily
             // mean the network is gone.  We wait for a separate notification
             // on either the LISTEN_ALL or MOBILE_REQUEST callbacks before
@@ -401,8 +429,15 @@
             recomputeLocalPrefixes();
         }
 
-        // TODO: Handle onNetworkSuspended();
-        // TODO: Handle onNetworkResumed();
+        @Override
+        public void onNetworkSuspended(Network network) {
+            handleSuspended(mCallbackType, network);
+        }
+
+        @Override
+        public void onNetworkResumed(Network network) {
+            handleResumed(mCallbackType, network);
+        }
 
         @Override
         public void onLost(Network network) {
@@ -467,4 +502,9 @@
 
         return prefixSet;
     }
+
+    private static String getSignalStrength(NetworkCapabilities nc) {
+        if (nc == null || !nc.hasSignalStrength()) return "unknown";
+        return Integer.toString(nc.getSignalStrength());
+    }
 }
diff --git a/com/android/server/devicepolicy/DevicePolicyManagerService.java b/com/android/server/devicepolicy/DevicePolicyManagerService.java
index c59f44e..80f6a4b 100644
--- a/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -175,6 +175,7 @@
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.FunctionalUtils.ThrowingRunnable;
 import com.android.internal.util.JournaledFile;
 import com.android.internal.util.Preconditions;
 import com.android.internal.util.XmlUtils;
@@ -1682,6 +1683,10 @@
             return getCallingUid() == Process.myUid();
         }
 
+        void binderWithCleanCallingIdentity(@NonNull ThrowingRunnable action) {
+             Binder.withCleanCallingIdentity(action);
+        }
+
         final int userHandleGetCallingUserId() {
             return UserHandle.getUserId(binderGetCallingUid());
         }
@@ -9023,6 +9028,31 @@
     }
 
     @Override
+    public boolean setTime(ComponentName who, long millis) {
+        Preconditions.checkNotNull(who, "ComponentName is null in setTime");
+        getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+        // Don't allow set time when auto time is on.
+        if (mInjector.settingsGlobalGetInt(Global.AUTO_TIME, 0) == 1) {
+            return false;
+        }
+        mInjector.binderWithCleanCallingIdentity(() -> mInjector.getAlarmManager().setTime(millis));
+        return true;
+    }
+
+    @Override
+    public boolean setTimeZone(ComponentName who, String timeZone) {
+        Preconditions.checkNotNull(who, "ComponentName is null in setTimeZone");
+        getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+        // Don't allow set timezone when auto timezone is on.
+        if (mInjector.settingsGlobalGetInt(Global.AUTO_TIME_ZONE, 0) == 1) {
+            return false;
+        }
+        mInjector.binderWithCleanCallingIdentity(() ->
+            mInjector.getAlarmManager().setTimeZone(timeZone));
+        return true;
+    }
+
+    @Override
     public void setSecureSetting(ComponentName who, String setting, String value) {
         Preconditions.checkNotNull(who, "ComponentName is null");
         int callingUserId = mInjector.userHandleGetCallingUserId();
diff --git a/com/android/server/display/AutomaticBrightnessController.java b/com/android/server/display/AutomaticBrightnessController.java
index 9aabdab..9a6e609 100644
--- a/com/android/server/display/AutomaticBrightnessController.java
+++ b/com/android/server/display/AutomaticBrightnessController.java
@@ -29,6 +29,7 @@
 import android.os.Message;
 import android.os.PowerManager;
 import android.os.SystemClock;
+import android.os.Trace;
 import android.text.format.DateUtils;
 import android.util.EventLog;
 import android.util.MathUtils;
@@ -304,6 +305,7 @@
     }
 
     private void handleLightSensorEvent(long time, float lux) {
+        Trace.traceCounter(Trace.TRACE_TAG_POWER, "ALS", (int) lux);
         mHandler.removeMessages(MSG_UPDATE_AMBIENT_LUX);
 
         if (mAmbientLightRingBuffer.size() == 0) {
diff --git a/com/android/server/display/DisplayPowerController.java b/com/android/server/display/DisplayPowerController.java
index f8e5836..f930b52 100644
--- a/com/android/server/display/DisplayPowerController.java
+++ b/com/android/server/display/DisplayPowerController.java
@@ -683,8 +683,8 @@
         // Configure auto-brightness.
         boolean autoBrightnessEnabled = false;
         if (mAutomaticBrightnessController != null) {
-            final boolean autoBrightnessEnabledInDoze = mAllowAutoBrightnessWhileDozingConfig
-                    && (state == Display.STATE_DOZE || state == Display.STATE_DOZE_SUSPEND);
+            final boolean autoBrightnessEnabledInDoze =
+                    mAllowAutoBrightnessWhileDozingConfig && Display.isDozeState(state);
             autoBrightnessEnabled = mPowerRequest.useAutoBrightness
                     && (state == Display.STATE_ON || autoBrightnessEnabledInDoze)
                     && brightness < 0;
@@ -726,8 +726,7 @@
         }
 
         // Use default brightness when dozing unless overridden.
-        if (brightness < 0 && (state == Display.STATE_DOZE
-                || state == Display.STATE_DOZE_SUSPEND)) {
+        if (brightness < 0 && Display.isDozeState(state)) {
             brightness = mScreenBrightnessDozeConfig;
         }
 
@@ -777,7 +776,6 @@
         // Skip the animation when the screen is off or suspended or transition to/from VR.
         if (!mPendingScreenOff) {
             if (mSkipScreenOnBrightnessRamp) {
-
                 if (state == Display.STATE_ON) {
                     if (mSkipRampState == RAMP_STATE_SKIP_NONE && mDozing) {
                         mInitialAutoBrightness = brightness;
@@ -794,15 +792,25 @@
                 }
             }
 
-            boolean wasOrWillBeInVr = (state == Display.STATE_VR || oldState == Display.STATE_VR);
-            if ((state == Display.STATE_ON
-                    && mSkipRampState == RAMP_STATE_SKIP_NONE
-                    || state == Display.STATE_DOZE && !mBrightnessBucketsInDozeConfig)
-                    && !wasOrWillBeInVr) {
+            final boolean wasOrWillBeInVr =
+                    (state == Display.STATE_VR || oldState == Display.STATE_VR);
+            final boolean initialRampSkip =
+                    state == Display.STATE_ON && mSkipRampState != RAMP_STATE_SKIP_NONE;
+            // While dozing, sometimes the brightness is split into buckets. Rather than animating
+            // through the buckets, which is unlikely to be smooth in the first place, just jump
+            // right to the suggested brightness.
+            final boolean hasBrightnessBuckets =
+                    Display.isDozeState(state) && mBrightnessBucketsInDozeConfig;
+            // If the color fade is totally covering the screen then we can change the backlight
+            // level without it being a noticeable jump since any actual content isn't yet visible.
+            final boolean isDisplayContentVisible =
+                    mColorFadeEnabled && mPowerState.getColorFadeLevel() == 1.0f;
+            if (initialRampSkip || hasBrightnessBuckets
+                    || wasOrWillBeInVr || !isDisplayContentVisible) {
+                animateScreenBrightness(brightness, 0);
+            } else {
                 animateScreenBrightness(brightness,
                         slowChange ? mBrightnessRampRateSlow : mBrightnessRampRateFast);
-            } else {
-                animateScreenBrightness(brightness, 0);
             }
         }
 
@@ -925,6 +933,7 @@
             }
 
             if (!reportOnly) {
+                Trace.traceCounter(Trace.TRACE_TAG_POWER, "ScreenState", state);
                 mPowerState.setScreenState(state);
                 // Tell battery stats about the transition.
                 try {
diff --git a/com/android/server/ethernet/EthernetNetworkFactory.java b/com/android/server/ethernet/EthernetNetworkFactory.java
index d6d0def..2d54fd2 100644
--- a/com/android/server/ethernet/EthernetNetworkFactory.java
+++ b/com/android/server/ethernet/EthernetNetworkFactory.java
@@ -248,25 +248,6 @@
         return true;
     }
 
-    private boolean setStaticIpAddress(StaticIpConfiguration staticConfig) {
-        if (staticConfig.ipAddress != null &&
-                staticConfig.gateway != null &&
-                staticConfig.dnsServers.size() > 0) {
-            try {
-                Log.i(TAG, "Applying static IPv4 configuration to " + mIface + ": " + staticConfig);
-                InterfaceConfiguration config = mNMService.getInterfaceConfig(mIface);
-                config.setLinkAddress(staticConfig.ipAddress);
-                mNMService.setInterfaceConfig(mIface, config);
-                return true;
-            } catch(RemoteException|IllegalStateException e) {
-               Log.e(TAG, "Setting static IP address failed: " + e.getMessage());
-            }
-        } else {
-            Log.e(TAG, "Invalid static IP configuration.");
-        }
-        return false;
-    }
-
     public void updateAgent() {
         if (mNetworkAgent == null) return;
         if (DBG) {
@@ -332,55 +313,52 @@
                     mNetworkInfo));
         }
 
-        LinkProperties linkProperties;
-
         IpConfiguration config = mEthernetManager.getConfiguration();
 
-        if (config.getIpAssignment() == IpAssignment.STATIC) {
-            if (!setStaticIpAddress(config.getStaticIpConfiguration())) {
-                // We've already logged an error.
-                return;
-            }
-            linkProperties = config.getStaticIpConfiguration().toLinkProperties(mIface);
-        } else {
-            mNetworkInfo.setDetailedState(DetailedState.OBTAINING_IPADDR, null, mHwAddr);
-            IpManager.Callback ipmCallback = new IpManager.Callback() {
-                @Override
-                public void onProvisioningSuccess(LinkProperties newLp) {
-                    mHandler.post(() -> onIpLayerStarted(newLp));
-                }
-
-                @Override
-                public void onProvisioningFailure(LinkProperties newLp) {
-                    mHandler.post(() -> onIpLayerStopped(newLp));
-                }
-
-                @Override
-                public void onLinkPropertiesChange(LinkProperties newLp) {
-                    mHandler.post(() -> updateLinkProperties(newLp));
-                }
-            };
-
-            stopIpManager();
-            mIpManager = new IpManager(mContext, mIface, ipmCallback);
-
-            if (config.getProxySettings() == ProxySettings.STATIC ||
-                    config.getProxySettings() == ProxySettings.PAC) {
-                mIpManager.setHttpProxy(config.getHttpProxy());
+        mNetworkInfo.setDetailedState(DetailedState.OBTAINING_IPADDR, null, mHwAddr);
+        IpManager.Callback ipmCallback = new IpManager.Callback() {
+            @Override
+            public void onProvisioningSuccess(LinkProperties newLp) {
+                mHandler.post(() -> onIpLayerStarted(newLp));
             }
 
-            final String tcpBufferSizes = mContext.getResources().getString(
-                    com.android.internal.R.string.config_ethernet_tcp_buffers);
-            if (!TextUtils.isEmpty(tcpBufferSizes)) {
-                mIpManager.setTcpBufferSizes(tcpBufferSizes);
+            @Override
+            public void onProvisioningFailure(LinkProperties newLp) {
+                mHandler.post(() -> onIpLayerStopped(newLp));
             }
 
-            final ProvisioningConfiguration provisioningConfiguration =
-                    mIpManager.buildProvisioningConfiguration()
-                            .withProvisioningTimeoutMs(0)
-                            .build();
-            mIpManager.startProvisioning(provisioningConfiguration);
+            @Override
+            public void onLinkPropertiesChange(LinkProperties newLp) {
+                mHandler.post(() -> updateLinkProperties(newLp));
+            }
+        };
+
+        stopIpManager();
+        mIpManager = new IpManager(mContext, mIface, ipmCallback);
+
+        if (config.getProxySettings() == ProxySettings.STATIC ||
+                config.getProxySettings() == ProxySettings.PAC) {
+            mIpManager.setHttpProxy(config.getHttpProxy());
         }
+
+        final String tcpBufferSizes = mContext.getResources().getString(
+                com.android.internal.R.string.config_ethernet_tcp_buffers);
+        if (!TextUtils.isEmpty(tcpBufferSizes)) {
+            mIpManager.setTcpBufferSizes(tcpBufferSizes);
+        }
+
+        final ProvisioningConfiguration provisioningConfiguration;
+        if (config.getIpAssignment() == IpAssignment.STATIC) {
+            provisioningConfiguration = IpManager.buildProvisioningConfiguration()
+                    .withStaticConfiguration(config.getStaticIpConfiguration())
+                    .build();
+        } else {
+            provisioningConfiguration = mIpManager.buildProvisioningConfiguration()
+                    .withProvisioningTimeoutMs(0)
+                    .build();
+        }
+
+        mIpManager.startProvisioning(provisioningConfiguration);
     }
 
     /**
diff --git a/com/android/server/job/controllers/BackgroundJobsController.java b/com/android/server/job/controllers/BackgroundJobsController.java
index 0539c02..78b4160 100644
--- a/com/android/server/job/controllers/BackgroundJobsController.java
+++ b/com/android/server/job/controllers/BackgroundJobsController.java
@@ -167,6 +167,7 @@
 
     @Override
     public void dumpControllerStateLocked(final PrintWriter pw, final int filterUid) {
+        pw.println("BackgroundJobsController");
         pw.print("Foreground uids: [");
         for (int i = 0; i < mForegroundUids.size(); i++) {
             if (mForegroundUids.valueAt(i)) pw.print(mForegroundUids.keyAt(i) + " ");
diff --git a/com/android/server/job/controllers/DeviceIdleJobsController.java b/com/android/server/job/controllers/DeviceIdleJobsController.java
index 85993b9..374ab43 100644
--- a/com/android/server/job/controllers/DeviceIdleJobsController.java
+++ b/com/android/server/job/controllers/DeviceIdleJobsController.java
@@ -22,6 +22,7 @@
 import android.content.IntentFilter;
 import android.os.PowerManager;
 import android.os.UserHandle;
+import android.util.ArraySet;
 import android.util.Slog;
 
 import com.android.internal.util.ArrayUtils;
@@ -55,6 +56,9 @@
      */
     private boolean mDeviceIdleMode;
     private int[] mDeviceIdleWhitelistAppIds;
+    private int[] mPowerSaveTempWhitelistAppIds;
+    // These jobs were added when the app was in temp whitelist, these should be exempted from doze
+    private final ArraySet<JobStatus> mTempWhitelistedJobs;
 
     final JobStore.JobStatusFunctor mUpdateFunctor = new JobStore.JobStatusFunctor() {
         @Override public void process(JobStatus jobStatus) {
@@ -79,15 +83,39 @@
     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
-            final String action = intent.getAction();
-            if (PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED.equals(action)
-                    || PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED.equals(action)) {
-                updateIdleMode(mPowerManager != null
-                        ? (mPowerManager.isDeviceIdleMode()
-                                || mPowerManager.isLightDeviceIdleMode())
-                        : false);
-            } else if (PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED.equals(action)) {
-                updateWhitelist();
+            switch (intent.getAction()) {
+                case PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED:
+                case PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED:
+                    updateIdleMode(mPowerManager != null && (mPowerManager.isDeviceIdleMode()
+                            || mPowerManager.isLightDeviceIdleMode()));
+                    break;
+                case PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED:
+                    synchronized (mLock) {
+                        mDeviceIdleWhitelistAppIds =
+                                mLocalDeviceIdleController.getPowerSaveWhitelistUserAppIds();
+                        if (LOG_DEBUG) {
+                            Slog.d(LOG_TAG, "Got whitelist "
+                                    + Arrays.toString(mDeviceIdleWhitelistAppIds));
+                        }
+                    }
+                    break;
+                case PowerManager.ACTION_POWER_SAVE_TEMP_WHITELIST_CHANGED:
+                    synchronized (mLock) {
+                        mPowerSaveTempWhitelistAppIds =
+                                mLocalDeviceIdleController.getPowerSaveTempWhitelistAppIds();
+                        if (LOG_DEBUG) {
+                            Slog.d(LOG_TAG, "Got temp whitelist "
+                                    + Arrays.toString(mPowerSaveTempWhitelistAppIds));
+                        }
+                        boolean changed = false;
+                        for (int i = 0; i < mTempWhitelistedJobs.size(); i ++) {
+                            changed |= updateTaskStateLocked(mTempWhitelistedJobs.valueAt(i));
+                        }
+                        if (changed) {
+                            mStateChangedListener.onControllerStateChanged();
+                        }
+                    }
+                    break;
             }
         }
     };
@@ -101,20 +129,21 @@
         mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
         mLocalDeviceIdleController =
                 LocalServices.getService(DeviceIdleController.LocalService.class);
+        mDeviceIdleWhitelistAppIds = mLocalDeviceIdleController.getPowerSaveWhitelistUserAppIds();
+        mPowerSaveTempWhitelistAppIds =
+                mLocalDeviceIdleController.getPowerSaveTempWhitelistAppIds();
+        mTempWhitelistedJobs = new ArraySet<>();
         final IntentFilter filter = new IntentFilter();
         filter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
         filter.addAction(PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED);
         filter.addAction(PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED);
+        filter.addAction(PowerManager.ACTION_POWER_SAVE_TEMP_WHITELIST_CHANGED);
         mContext.registerReceiverAsUser(
                 mBroadcastReceiver, UserHandle.ALL, filter, null, null);
     }
 
     void updateIdleMode(boolean enabled) {
         boolean changed = false;
-        // Need the whitelist to be ready when going into idle
-        if (mDeviceIdleWhitelistAppIds == null) {
-            updateWhitelist();
-        }
         synchronized (mLock) {
             if (mDeviceIdleMode != enabled) {
                 changed = true;
@@ -130,46 +159,42 @@
     }
 
     /**
-     * Fetches the latest whitelist from the device idle controller.
-     */
-    void updateWhitelist() {
-        synchronized (mLock) {
-            if (mLocalDeviceIdleController != null) {
-                mDeviceIdleWhitelistAppIds =
-                        mLocalDeviceIdleController.getPowerSaveWhitelistUserAppIds();
-                if (LOG_DEBUG) {
-                    Slog.d(LOG_TAG, "Got whitelist " + Arrays.toString(mDeviceIdleWhitelistAppIds));
-                }
-            }
-        }
-    }
-
-    /**
      * Checks if the given job's scheduling app id exists in the device idle user whitelist.
      */
     boolean isWhitelistedLocked(JobStatus job) {
-        if (mDeviceIdleWhitelistAppIds != null
-                && ArrayUtils.contains(mDeviceIdleWhitelistAppIds,
-                        UserHandle.getAppId(job.getSourceUid()))) {
-            return true;
-        }
-        return false;
+        return ArrayUtils.contains(mDeviceIdleWhitelistAppIds,
+                UserHandle.getAppId(job.getSourceUid()));
     }
 
-    private void updateTaskStateLocked(JobStatus task) {
-        final boolean whitelisted = isWhitelistedLocked(task);
+    /**
+     * Checks if the given job's scheduling app id exists in the device idle temp whitelist.
+     */
+    boolean isTempWhitelistedLocked(JobStatus job) {
+        return ArrayUtils.contains(mPowerSaveTempWhitelistAppIds,
+                UserHandle.getAppId(job.getSourceUid()));
+    }
+
+    private boolean updateTaskStateLocked(JobStatus task) {
+        final boolean whitelisted = isWhitelistedLocked(task)
+                || (mTempWhitelistedJobs.contains(task) && isTempWhitelistedLocked(task));
         final boolean enableTask = !mDeviceIdleMode || whitelisted;
-        task.setDeviceNotDozingConstraintSatisfied(enableTask, whitelisted);
+        return task.setDeviceNotDozingConstraintSatisfied(enableTask, whitelisted);
     }
 
     @Override
     public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) {
-        updateTaskStateLocked(jobStatus);
+        if (isTempWhitelistedLocked(jobStatus)) {
+            mTempWhitelistedJobs.add(jobStatus);
+            jobStatus.setDeviceNotDozingConstraintSatisfied(true, true);
+        } else {
+            updateTaskStateLocked(jobStatus);
+        }
     }
 
     @Override
     public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob,
             boolean forUpdate) {
+        mTempWhitelistedJobs.remove(jobStatus);
     }
 
     @Override
diff --git a/com/android/server/job/controllers/TimeController.java b/com/android/server/job/controllers/TimeController.java
index d90699a..ee4c606 100644
--- a/com/android/server/job/controllers/TimeController.java
+++ b/com/android/server/job/controllers/TimeController.java
@@ -110,7 +110,7 @@
             maybeUpdateAlarmsLocked(
                     job.hasTimingDelayConstraint() ? job.getEarliestRunTime() : Long.MAX_VALUE,
                     job.hasDeadlineConstraint() ? job.getLatestRunTimeElapsed() : Long.MAX_VALUE,
-                    job.getSourceUid());
+                    new WorkSource(job.getSourceUid(), job.getSourcePackageName()));
         }
     }
 
@@ -156,6 +156,7 @@
         synchronized (mLock) {
             long nextExpiryTime = Long.MAX_VALUE;
             int nextExpiryUid = 0;
+            String nextExpiryPackageName = null;
             final long nowElapsedMillis = SystemClock.elapsedRealtime();
 
             Iterator<JobStatus> it = mTrackedJobs.iterator();
@@ -171,10 +172,13 @@
                 } else {  // Sorted by expiry time, so take the next one and stop.
                     nextExpiryTime = job.getLatestRunTimeElapsed();
                     nextExpiryUid = job.getSourceUid();
+                    nextExpiryPackageName = job.getSourcePackageName();
                     break;
                 }
             }
-            setDeadlineExpiredAlarmLocked(nextExpiryTime, nextExpiryUid);
+            setDeadlineExpiredAlarmLocked(nextExpiryTime, nextExpiryPackageName != null
+                    ? new WorkSource(nextExpiryUid, nextExpiryPackageName)
+                    : new WorkSource(nextExpiryUid));
         }
     }
 
@@ -200,6 +204,7 @@
             final long nowElapsedMillis = SystemClock.elapsedRealtime();
             long nextDelayTime = Long.MAX_VALUE;
             int nextDelayUid = 0;
+            String nextDelayPackageName = null;
             boolean ready = false;
             Iterator<JobStatus> it = mTrackedJobs.iterator();
             while (it.hasNext()) {
@@ -221,13 +226,16 @@
                     if (nextDelayTime > jobDelayTime) {
                         nextDelayTime = jobDelayTime;
                         nextDelayUid = job.getSourceUid();
+                        nextDelayPackageName = job.getSourcePackageName();
                     }
                 }
             }
             if (ready) {
                 mStateChangedListener.onControllerStateChanged();
             }
-            setDelayExpiredAlarmLocked(nextDelayTime, nextDelayUid);
+            setDelayExpiredAlarmLocked(nextDelayTime, nextDelayPackageName != null
+                    ? new WorkSource(nextDelayUid, nextDelayPackageName)
+                    : new WorkSource(nextDelayUid));
         }
     }
 
@@ -241,12 +249,12 @@
     }
 
     private void maybeUpdateAlarmsLocked(long delayExpiredElapsed, long deadlineExpiredElapsed,
-            int uid) {
+            WorkSource ws) {
         if (delayExpiredElapsed < mNextDelayExpiredElapsedMillis) {
-            setDelayExpiredAlarmLocked(delayExpiredElapsed, uid);
+            setDelayExpiredAlarmLocked(delayExpiredElapsed, ws);
         }
         if (deadlineExpiredElapsed < mNextJobExpiredElapsedMillis) {
-            setDeadlineExpiredAlarmLocked(deadlineExpiredElapsed, uid);
+            setDeadlineExpiredAlarmLocked(deadlineExpiredElapsed, ws);
         }
     }
 
@@ -255,11 +263,11 @@
      * delay will expire.
      * This alarm <b>will</b> wake up the phone.
      */
-    private void setDelayExpiredAlarmLocked(long alarmTimeElapsedMillis, int uid) {
+    private void setDelayExpiredAlarmLocked(long alarmTimeElapsedMillis, WorkSource ws) {
         alarmTimeElapsedMillis = maybeAdjustAlarmTime(alarmTimeElapsedMillis);
         mNextDelayExpiredElapsedMillis = alarmTimeElapsedMillis;
         updateAlarmWithListenerLocked(DELAY_TAG, mNextDelayExpiredListener,
-                mNextDelayExpiredElapsedMillis, uid);
+                mNextDelayExpiredElapsedMillis, ws);
     }
 
     /**
@@ -267,11 +275,11 @@
      * deadline will expire.
      * This alarm <b>will</b> wake up the phone.
      */
-    private void setDeadlineExpiredAlarmLocked(long alarmTimeElapsedMillis, int uid) {
+    private void setDeadlineExpiredAlarmLocked(long alarmTimeElapsedMillis, WorkSource ws) {
         alarmTimeElapsedMillis = maybeAdjustAlarmTime(alarmTimeElapsedMillis);
         mNextJobExpiredElapsedMillis = alarmTimeElapsedMillis;
         updateAlarmWithListenerLocked(DEADLINE_TAG, mDeadlineExpiredListener,
-                mNextJobExpiredElapsedMillis, uid);
+                mNextJobExpiredElapsedMillis, ws);
     }
 
     private long maybeAdjustAlarmTime(long proposedAlarmTimeElapsedMillis) {
@@ -283,7 +291,7 @@
     }
 
     private void updateAlarmWithListenerLocked(String tag, OnAlarmListener listener,
-            long alarmTimeElapsed, int uid) {
+            long alarmTimeElapsed, WorkSource ws) {
         ensureAlarmServiceLocked();
         if (alarmTimeElapsed == Long.MAX_VALUE) {
             mAlarmService.cancel(listener);
@@ -292,7 +300,7 @@
                 Slog.d(TAG, "Setting " + tag + " for: " + alarmTimeElapsed);
             }
             mAlarmService.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, alarmTimeElapsed,
-                    AlarmManager.WINDOW_HEURISTIC, 0, tag, listener, null, new WorkSource(uid));
+                    AlarmManager.WINDOW_HEURISTIC, 0, tag, listener, null, ws);
         }
     }
 
diff --git a/com/android/server/location/GnssLocationProvider.java b/com/android/server/location/GnssLocationProvider.java
index 0aa6a90..e41c17d 100644
--- a/com/android/server/location/GnssLocationProvider.java
+++ b/com/android/server/location/GnssLocationProvider.java
@@ -417,7 +417,11 @@
     // stops output right at 600m/s, depriving this of the information of a device that reaches
     // greater than 600m/s, and higher than the speed of sound to avoid impacting most use cases.
     private static final float ITAR_SPEED_LIMIT_METERS_PER_SECOND = 400.0F;
-    private boolean mItarSpeedLimitExceeded = false;
+
+    // TODO: improve comment
+    // Volatile to ensure that potentially near-concurrent outputs from HAL
+    // react to this value change promptly
+    private volatile boolean mItarSpeedLimitExceeded = false;
 
     // GNSS Metrics
     private GnssMetrics mGnssMetrics;
diff --git a/com/android/server/media/MediaSessionRecord.java b/com/android/server/media/MediaSessionRecord.java
index 0b11479..664d2f9 100644
--- a/com/android/server/media/MediaSessionRecord.java
+++ b/com/android/server/media/MediaSessionRecord.java
@@ -784,6 +784,14 @@
                 mService.enforcePhoneStatePermission(pid, uid);
             }
             mFlags = flags;
+            if ((flags & MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY) != 0) {
+                final long token = Binder.clearCallingIdentity();
+                try {
+                    mService.setGlobalPrioritySession(MediaSessionRecord.this);
+                } finally {
+                    Binder.restoreCallingIdentity(token);
+                }
+            }
             mHandler.post(MessageHandler.MSG_UPDATE_SESSION_STATE);
         }
 
diff --git a/com/android/server/media/MediaSessionService.java b/com/android/server/media/MediaSessionService.java
index b9a2d18..aa65244 100644
--- a/com/android/server/media/MediaSessionService.java
+++ b/com/android/server/media/MediaSessionService.java
@@ -178,17 +178,6 @@
                 return;
             }
             if ((record.getFlags() & MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY) != 0) {
-                if (mGlobalPrioritySession != record) {
-                    Log.d(TAG, "Global priority session is changed from " + mGlobalPrioritySession
-                            + " to " + record);
-                    mGlobalPrioritySession = record;
-                    if (user != null && user.mPriorityStack.contains(record)) {
-                        // Handle the global priority session separately.
-                        // Otherwise, it will be the media button session even after it becomes
-                        // inactive because it has been the lastly played media app.
-                        user.mPriorityStack.removeSession(record);
-                    }
-                }
                 if (DEBUG_KEY_EVENT) {
                     Log.d(TAG, "Global priority session is updated, active=" + record.isActive());
                 }
@@ -204,10 +193,27 @@
         }
     }
 
+    public void setGlobalPrioritySession(MediaSessionRecord record) {
+        synchronized (mLock) {
+            FullUserRecord user = getFullUserRecordLocked(record.getUserId());
+            if (mGlobalPrioritySession != record) {
+                Log.d(TAG, "Global priority session is changed from " + mGlobalPrioritySession
+                        + " to " + record);
+                mGlobalPrioritySession = record;
+                if (user != null && user.mPriorityStack.contains(record)) {
+                    // Handle the global priority session separately.
+                    // Otherwise, it can be the media button session regardless of the active state
+                    // because it or other system components might have been the lastly played media
+                    // app.
+                    user.mPriorityStack.removeSession(record);
+                }
+            }
+        }
+    }
+
     private List<MediaSessionRecord> getActiveSessionsLocked(int userId) {
-        List<MediaSessionRecord> records;
+        List<MediaSessionRecord> records = new ArrayList<>();
         if (userId == UserHandle.USER_ALL) {
-            records = new ArrayList<>();
             int size = mUserRecords.size();
             for (int i = 0; i < size; i++) {
                 records.addAll(mUserRecords.valueAt(i).mPriorityStack.getActiveSessions(userId));
@@ -216,9 +222,9 @@
             FullUserRecord user = getFullUserRecordLocked(userId);
             if (user == null) {
                 Log.w(TAG, "getSessions failed. Unknown user " + userId);
-                return new ArrayList<>();
+                return records;
             }
-            records = user.mPriorityStack.getActiveSessions(userId);
+            records.addAll(user.mPriorityStack.getActiveSessions(userId));
         }
 
         // Return global priority session at the first whenever it's asked.
diff --git a/com/android/server/notification/NotificationManagerService.java b/com/android/server/notification/NotificationManagerService.java
index 14cd055..238d87b 100644
--- a/com/android/server/notification/NotificationManagerService.java
+++ b/com/android/server/notification/NotificationManagerService.java
@@ -133,7 +133,6 @@
 import android.service.notification.NotificationRankingUpdate;
 import android.service.notification.NotificationRecordProto;
 import android.service.notification.NotificationServiceDumpProto;
-import android.service.notification.NotificationServiceProto;
 import android.service.notification.NotificationStats;
 import android.service.notification.SnoozeCriterion;
 import android.service.notification.StatusBarNotification;
@@ -282,6 +281,7 @@
     private WindowManagerInternal mWindowManagerInternal;
     private AlarmManager mAlarmManager;
     private ICompanionDeviceManager mCompanionManager;
+    private AccessibilityManager mAccessibilityManager;
 
     final IBinder mForegroundToken = new Binder();
     private WorkerHandler mHandler;
@@ -1221,6 +1221,12 @@
         mUsageStats = us;
     }
 
+    @VisibleForTesting
+    void setAccessibilityManager(AccessibilityManager am) {
+        mAccessibilityManager = am;
+    }
+
+
     // TODO: All tests should use this init instead of the one-off setters above.
     @VisibleForTesting
     void init(Looper looper, IPackageManager packageManager,
@@ -1235,6 +1241,8 @@
                 Settings.Global.MAX_NOTIFICATION_ENQUEUE_RATE,
                 DEFAULT_MAX_NOTIFICATION_ENQUEUE_RATE);
 
+        mAccessibilityManager =
+                (AccessibilityManager) getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
         mAm = ActivityManager.getService();
         mPackageManager = packageManager;
         mPackageManagerClient = packageManagerClient;
@@ -3254,7 +3262,7 @@
                     final NotificationRecord nr = mNotificationList.get(i);
                     if (filter.filtered && !filter.matches(nr.sbn)) continue;
                     nr.dump(proto, filter.redact);
-                    proto.write(NotificationRecordProto.STATE, NotificationServiceProto.POSTED);
+                    proto.write(NotificationRecordProto.STATE, NotificationRecordProto.POSTED);
                 }
             }
             N = mEnqueuedNotifications.size();
@@ -3263,7 +3271,7 @@
                     final NotificationRecord nr = mEnqueuedNotifications.get(i);
                     if (filter.filtered && !filter.matches(nr.sbn)) continue;
                     nr.dump(proto, filter.redact);
-                    proto.write(NotificationRecordProto.STATE, NotificationServiceProto.ENQUEUED);
+                    proto.write(NotificationRecordProto.STATE, NotificationRecordProto.ENQUEUED);
                 }
             }
             List<NotificationRecord> snoozed = mSnoozeHelper.getSnoozed();
@@ -3273,7 +3281,7 @@
                     final NotificationRecord nr = snoozed.get(i);
                     if (filter.filtered && !filter.matches(nr.sbn)) continue;
                     nr.dump(proto, filter.redact);
-                    proto.write(NotificationRecordProto.STATE, NotificationServiceProto.SNOOZED);
+                    proto.write(NotificationRecordProto.STATE, NotificationRecordProto.SNOOZED);
                 }
             }
             proto.end(records);
@@ -4117,13 +4125,16 @@
         // These are set inside the conditional if the notification is allowed to make noise.
         boolean hasValidVibrate = false;
         boolean hasValidSound = false;
+        boolean sentAccessibilityEvent = false;
+        // If the notification will appear in the status bar, it should send an accessibility
+        // event
+        if (!record.isUpdate && record.getImportance() > IMPORTANCE_MIN) {
+            sendAccessibilityEvent(notification, record.sbn.getPackageName());
+            sentAccessibilityEvent = true;
+        }
 
         if (aboveThreshold && isNotificationForCurrentUser(record)) {
-            // If the notification will appear in the status bar, it should send an accessibility
-            // event
-            if (!record.isUpdate && record.getImportance() > IMPORTANCE_MIN) {
-                sendAccessibilityEvent(notification, record.sbn.getPackageName());
-            }
+
             if (mSystemReady && mAudioManager != null) {
                 Uri soundUri = record.getSound();
                 hasValidSound = soundUri != null && !Uri.EMPTY.equals(soundUri);
@@ -4141,6 +4152,10 @@
 
                 boolean hasAudibleAlert = hasValidSound || hasValidVibrate;
                 if (hasAudibleAlert && !shouldMuteNotificationLocked(record)) {
+                    if (!sentAccessibilityEvent) {
+                        sendAccessibilityEvent(notification, record.sbn.getPackageName());
+                        sentAccessibilityEvent = true;
+                    }
                     if (DBG) Slog.v(TAG, "Interrupting!");
                     if (hasValidSound) {
                         mSoundNotificationKey = key;
@@ -4661,8 +4676,7 @@
     }
 
     void sendAccessibilityEvent(Notification notification, CharSequence packageName) {
-        AccessibilityManager manager = AccessibilityManager.getInstance(getContext());
-        if (!manager.isEnabled()) {
+        if (!mAccessibilityManager.isEnabled()) {
             return;
         }
 
@@ -4676,7 +4690,7 @@
             event.getText().add(tickerText);
         }
 
-        manager.sendAccessibilityEvent(event);
+        mAccessibilityManager.sendAccessibilityEvent(event);
     }
 
     /**
diff --git a/com/android/server/notification/ZenModeFiltering.java b/com/android/server/notification/ZenModeFiltering.java
index a7a2743..abf2900 100644
--- a/com/android/server/notification/ZenModeFiltering.java
+++ b/com/android/server/notification/ZenModeFiltering.java
@@ -117,15 +117,19 @@
                 ZenLog.traceIntercepted(record, "alarmsOnly");
                 return true;
             case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS:
-                if (isAlarm(record)) {
-                    // Alarms are always priority
-                    return false;
-                }
                 // allow user-prioritized packages through in priority mode
                 if (record.getPackagePriority() == Notification.PRIORITY_MAX) {
                     ZenLog.traceNotIntercepted(record, "priorityApp");
                     return false;
                 }
+
+                if (isAlarm(record)) {
+                    if (!config.allowAlarms) {
+                        ZenLog.traceIntercepted(record, "!allowAlarms");
+                        return true;
+                    }
+                    return false;
+                }
                 if (isCall(record)) {
                     if (config.allowRepeatCallers
                             && REPEAT_CALLERS.isRepeat(mContext, extras(record))) {
@@ -159,6 +163,15 @@
                     }
                     return false;
                 }
+                AudioAttributes aa = record.getAudioAttributes();
+                if (aa != null && AudioAttributes.SUPPRESSIBLE_USAGES.get(aa.getUsage()) ==
+                        AudioAttributes.SUPPRESSIBLE_MEDIA_SYSTEM_OTHER) {
+                    if (!config.allowMediaSystemOther) {
+                        ZenLog.traceIntercepted(record, "!allowMediaSystemOther");
+                        return true;
+                    }
+                    return false;
+                }
                 ZenLog.traceIntercepted(record, "!priority");
                 return true;
             default:
diff --git a/com/android/server/notification/ZenModeHelper.java b/com/android/server/notification/ZenModeHelper.java
index 9fcc67d..f61cec9 100644
--- a/com/android/server/notification/ZenModeHelper.java
+++ b/com/android/server/notification/ZenModeHelper.java
@@ -59,6 +59,7 @@
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.MetricsLogger;
 import com.android.server.LocalServices;
 
@@ -87,7 +88,7 @@
     private final Context mContext;
     private final H mHandler;
     private final SettingsObserver mSettingsObserver;
-    private final AppOpsManager mAppOps;
+    @VisibleForTesting protected final AppOpsManager mAppOps;
     protected ZenModeConfig mDefaultConfig;
     private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>();
     private final ZenModeFiltering mFiltering;
@@ -102,9 +103,9 @@
     private final String SCHEDULED_DEFAULT_RULE_1 = "SCHEDULED_DEFAULT_RULE_1";
     private final String SCHEDULED_DEFAULT_RULE_2 = "SCHEDULED_DEFAULT_RULE_2";
 
-    private int mZenMode;
+    @VisibleForTesting protected int mZenMode;
     private int mUser = UserHandle.USER_SYSTEM;
-    protected ZenModeConfig mConfig;
+    @VisibleForTesting protected ZenModeConfig mConfig;
     private AudioManagerInternal mAudioManager;
     protected PackageManager mPm;
     private long mSuppressedEffects;
@@ -125,12 +126,6 @@
         mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
 
         mDefaultConfig = new ZenModeConfig();
-        mDefaultRuleWeeknightsName = mContext.getResources()
-                .getString(R.string.zen_mode_default_weeknights_name);
-        mDefaultRuleWeekendsName = mContext.getResources()
-                .getString(R.string.zen_mode_default_weekends_name);
-        mDefaultRuleEventsName = mContext.getResources()
-                .getString(R.string.zen_mode_default_events_name);
         setDefaultZenRules(mContext);
         mConfig = mDefaultConfig;
         mConfigs.put(UserHandle.USER_SYSTEM, mConfig);
@@ -435,6 +430,7 @@
     }
 
     private void appendDefaultRules (ZenModeConfig config) {
+        getDefaultRuleNames();
         appendDefaultScheduleRules(config);
         appendDefaultEventRules(config);
     }
@@ -595,8 +591,9 @@
             pw.println(config);
             return;
         }
-        pw.printf("allow(calls=%b,callsFrom=%s,repeatCallers=%b,messages=%b,messagesFrom=%s,"
+        pw.printf("allow(alarms=%b,media=%bcalls=%b,callsFrom=%s,repeatCallers=%b,messages=%b,messagesFrom=%s,"
                 + "events=%b,reminders=%b,whenScreenOff=%b,whenScreenOn=%b)\n",
+                config.allowAlarms, config.allowMediaSystemOther,
                 config.allowCalls, ZenModeConfig.sourceToString(config.allowCallsFrom),
                 config.allowRepeatCallers, config.allowMessages,
                 ZenModeConfig.sourceToString(config.allowMessagesFrom),
@@ -813,7 +810,18 @@
         }
     }
 
-    private void applyRestrictions() {
+    private void getDefaultRuleNames() {
+        // on locale-change, these values differ
+        mDefaultRuleWeeknightsName = mContext.getResources()
+                .getString(R.string.zen_mode_default_weeknights_name);
+        mDefaultRuleWeekendsName = mContext.getResources()
+                .getString(R.string.zen_mode_default_weekends_name);
+        mDefaultRuleEventsName = mContext.getResources()
+                .getString(R.string.zen_mode_default_events_name);
+    }
+
+    @VisibleForTesting
+    protected void applyRestrictions() {
         final boolean zen = mZenMode != Global.ZEN_MODE_OFF;
 
         // notification restrictions
@@ -822,6 +830,10 @@
         // call restrictions
         final boolean muteCalls = zen && !mConfig.allowCalls && !mConfig.allowRepeatCallers
                 || (mSuppressedEffects & SUPPRESSED_EFFECT_CALLS) != 0;
+        // alarm restrictions
+        final boolean muteAlarms = zen && !mConfig.allowAlarms;
+        // alarm restrictions
+        final boolean muteMediaAndSystemSounds = zen && !mConfig.allowMediaSystemOther;
         // total silence restrictions
         final boolean muteEverything = mZenMode == Global.ZEN_MODE_NO_INTERRUPTIONS;
 
@@ -833,13 +845,18 @@
                 applyRestrictions(muteNotifications || muteEverything, usage);
             } else if (suppressionBehavior == AudioAttributes.SUPPRESSIBLE_CALL) {
                 applyRestrictions(muteCalls || muteEverything, usage);
+            } else if (suppressionBehavior == AudioAttributes.SUPPRESSIBLE_ALARM) {
+                applyRestrictions(muteAlarms || muteEverything, usage);
+            } else if (suppressionBehavior == AudioAttributes.SUPPRESSIBLE_MEDIA_SYSTEM_OTHER) {
+                applyRestrictions(muteMediaAndSystemSounds || muteEverything, usage);
             } else {
                 applyRestrictions(muteEverything, usage);
             }
         }
     }
 
-    private void applyRestrictions(boolean mute, int usage) {
+    @VisibleForTesting
+    protected void applyRestrictions(boolean mute, int usage) {
         final String[] exceptionPackages = null; // none (for now)
         mAppOps.setRestriction(AppOpsManager.OP_VIBRATE, usage,
                 mute ? AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED,
@@ -916,6 +933,7 @@
         weeknights.days = ZenModeConfig.WEEKNIGHT_DAYS;
         weeknights.startHour = 22;
         weeknights.endHour = 7;
+        weeknights.exitAtAlarm = true;
         final ZenRule rule1 = new ZenRule();
         rule1.enabled = false;
         rule1.name = mDefaultRuleWeeknightsName;
@@ -931,6 +949,7 @@
         weekends.startHour = 23;
         weekends.startMinute = 30;
         weekends.endHour = 10;
+        weekends.exitAtAlarm = true;
         final ZenRule rule2 = new ZenRule();
         rule2.enabled = false;
         rule2.name = mDefaultRuleWeekendsName;
diff --git a/com/android/server/pm/LauncherAppsService.java b/com/android/server/pm/LauncherAppsService.java
index 4a5ce12..b06b583 100644
--- a/com/android/server/pm/LauncherAppsService.java
+++ b/com/android/server/pm/LauncherAppsService.java
@@ -30,12 +30,10 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.ILauncherApps;
 import android.content.pm.IOnAppsChangedListener;
-import android.content.pm.IPackageManager;
 import android.content.pm.LauncherApps.ShortcutQuery;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
-import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ShortcutInfo;
@@ -64,7 +62,6 @@
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
 
-import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 
@@ -89,10 +86,14 @@
     static class BroadcastCookie {
         public final UserHandle user;
         public final String packageName;
+        public final int callingUid;
+        public final int callingPid;
 
-        BroadcastCookie(UserHandle userHandle, String packageName) {
+        BroadcastCookie(UserHandle userHandle, String packageName, int callingPid, int callingUid) {
             this.user = userHandle;
             this.packageName = packageName;
+            this.callingUid = callingUid;
+            this.callingPid = callingPid;
         }
     }
 
@@ -127,6 +128,11 @@
             return getCallingUid();
         }
 
+        @VisibleForTesting
+        int injectBinderCallingPid() {
+            return getCallingPid();
+        }
+
         final int injectCallingUserId() {
             return UserHandle.getUserId(injectBinderCallingUid());
         }
@@ -166,7 +172,7 @@
                 }
                 mListeners.unregister(listener);
                 mListeners.register(listener, new BroadcastCookie(UserHandle.of(getCallingUserId()),
-                        callingPackage));
+                        callingPackage, injectBinderCallingPid(), injectBinderCallingUid()));
             }
         }
 
@@ -438,7 +444,7 @@
         private void ensureShortcutPermission(@NonNull String callingPackage) {
             verifyCallingPackage(callingPackage);
             if (!mShortcutServiceInternal.hasShortcutHostPermission(getCallingUserId(),
-                    callingPackage)) {
+                    callingPackage, injectBinderCallingPid(), injectBinderCallingUid())) {
                 throw new SecurityException("Caller can't access shortcut information");
             }
         }
@@ -461,7 +467,8 @@
             return new ParceledListSlice<>((List<ShortcutInfo>)
                     mShortcutServiceInternal.getShortcuts(getCallingUserId(),
                             callingPackage, changedSince, packageName, shortcutIds,
-                            componentName, flags, targetUser.getIdentifier()));
+                            componentName, flags, targetUser.getIdentifier(),
+                            injectBinderCallingPid(), injectBinderCallingUid()));
         }
 
         @Override
@@ -514,7 +521,7 @@
         public boolean hasShortcutHostPermission(String callingPackage) {
             verifyCallingPackage(callingPackage);
             return mShortcutServiceInternal.hasShortcutHostPermission(getCallingUserId(),
-                    callingPackage);
+                    callingPackage, injectBinderCallingPid(), injectBinderCallingUid());
         }
 
         @Override
@@ -536,7 +543,8 @@
             }
 
             final Intent[] intents = mShortcutServiceInternal.createShortcutIntents(
-                    getCallingUserId(), callingPackage, packageName, shortcutId, targetUserId);
+                    getCallingUserId(), callingPackage, packageName, shortcutId, targetUserId,
+                    injectBinderCallingPid(), injectBinderCallingUid());
             if (intents == null || intents.length == 0) {
                 return false;
             }
@@ -901,7 +909,8 @@
 
                         // Make sure the caller has the permission.
                         if (!mShortcutServiceInternal.hasShortcutHostPermission(
-                                launcherUserId, cookie.packageName)) {
+                                launcherUserId, cookie.packageName,
+                                cookie.callingPid, cookie.callingUid)) {
                             continue;
                         }
                         // Each launcher has a different set of pinned shortcuts, so we need to do a
@@ -914,8 +923,8 @@
                                         /* changedSince= */ 0, packageName, /* shortcutIds=*/ null,
                                         /* component= */ null,
                                         ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY
-                                        | ShortcutQuery.FLAG_GET_ALL_KINDS
-                                        , userId);
+                                        | ShortcutQuery.FLAG_MATCH_ALL_KINDS_WITH_ALL_PINNED
+                                        , userId, cookie.callingPid, cookie.callingUid);
                         try {
                             listener.onShortcutChanged(user, packageName,
                                     new ParceledListSlice<>(list));
diff --git a/com/android/server/pm/PackageDexOptimizer.java b/com/android/server/pm/PackageDexOptimizer.java
index 8ebeeae..cf0ffbb 100644
--- a/com/android/server/pm/PackageDexOptimizer.java
+++ b/com/android/server/pm/PackageDexOptimizer.java
@@ -236,9 +236,10 @@
      */
     @GuardedBy("mInstallLock")
     private int dexOptPath(PackageParser.Package pkg, String path, String isa,
-            String compilerFilter, boolean profileUpdated, String sharedLibrariesPath,
+            String compilerFilter, boolean profileUpdated, String classLoaderContext,
             int dexoptFlags, int uid, CompilerStats.PackageStats packageStats, boolean downgrade) {
-        int dexoptNeeded = getDexoptNeeded(path, isa, compilerFilter, profileUpdated, downgrade);
+        int dexoptNeeded = getDexoptNeeded(path, isa, compilerFilter, classLoaderContext,
+                profileUpdated, downgrade);
         if (Math.abs(dexoptNeeded) == DexFile.NO_DEXOPT_NEEDED) {
             return DEX_OPT_SKIPPED;
         }
@@ -251,8 +252,8 @@
         Log.i(TAG, "Running dexopt (dexoptNeeded=" + dexoptNeeded + ") on: " + path
                 + " pkg=" + pkg.applicationInfo.packageName + " isa=" + isa
                 + " dexoptFlags=" + printDexoptFlags(dexoptFlags)
-                + " target-filter=" + compilerFilter + " oatDir=" + oatDir
-                + " sharedLibraries=" + sharedLibrariesPath);
+                + " targetFilter=" + compilerFilter + " oatDir=" + oatDir
+                + " classLoaderContext=" + classLoaderContext);
 
         try {
             long startTime = System.currentTimeMillis();
@@ -261,7 +262,7 @@
             // installd only uses downgrade flag for secondary dex files and ignores it for
             // primary dex files.
             mInstaller.dexopt(path, uid, pkg.packageName, isa, dexoptNeeded, oatDir, dexoptFlags,
-                    compilerFilter, pkg.volumeUuid, sharedLibrariesPath, pkg.applicationInfo.seInfo,
+                    compilerFilter, pkg.volumeUuid, classLoaderContext, pkg.applicationInfo.seInfo,
                     false /* downgrade*/);
 
             if (packageStats != null) {
@@ -508,11 +509,11 @@
      * configuration (isa, compiler filter, profile).
      */
     private int getDexoptNeeded(String path, String isa, String compilerFilter,
-            boolean newProfile, boolean downgrade) {
+            String classLoaderContext, boolean newProfile, boolean downgrade) {
         int dexoptNeeded;
         try {
-            dexoptNeeded = DexFile.getDexOptNeeded(path, isa, compilerFilter, newProfile,
-                    downgrade);
+            dexoptNeeded = DexFile.getDexOptNeeded(path, isa, compilerFilter, classLoaderContext,
+                    newProfile, downgrade);
         } catch (IOException ioe) {
             Slog.w(TAG, "IOException reading apk: " + path, ioe);
             return DEX_OPT_FAILED;
diff --git a/com/android/server/pm/PackageInstallerSession.java b/com/android/server/pm/PackageInstallerSession.java
index d62f093..0e11cc7 100644
--- a/com/android/server/pm/PackageInstallerSession.java
+++ b/com/android/server/pm/PackageInstallerSession.java
@@ -95,7 +95,6 @@
 import com.android.server.pm.PackageInstallerService.PackageInstallObserverAdapter;
 
 import libcore.io.IoUtils;
-import libcore.io.Libcore;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -613,7 +612,7 @@
 
             // TODO: this should delegate to DCS so the system process avoids
             // holding open FDs into containers.
-            final FileDescriptor targetFd = Libcore.os.open(target.getAbsolutePath(),
+            final FileDescriptor targetFd = Os.open(target.getAbsolutePath(),
                     O_CREAT | O_WRONLY, 0644);
             Os.chmod(target.getAbsolutePath(), 0644);
 
@@ -625,7 +624,7 @@
             }
 
             if (offsetBytes > 0) {
-                Libcore.os.lseek(targetFd, offsetBytes, OsConstants.SEEK_SET);
+                Os.lseek(targetFd, offsetBytes, OsConstants.SEEK_SET);
             }
 
             if (PackageInstaller.ENABLE_REVOCABLE_FD) {
@@ -661,7 +660,7 @@
                 throw new IllegalArgumentException("Invalid name: " + name);
             }
             final File target = new File(resolveStageDirLocked(), name);
-            final FileDescriptor targetFd = Libcore.os.open(target.getAbsolutePath(), O_RDONLY, 0);
+            final FileDescriptor targetFd = Os.open(target.getAbsolutePath(), O_RDONLY, 0);
             return new ParcelFileDescriptor(targetFd);
         } catch (ErrnoException e) {
             throw e.rethrowAsIOException();
@@ -1127,15 +1126,8 @@
 
                         mResolvedInstructionSets.add(archSubDir.getName());
                         List<File> oatFiles = Arrays.asList(archSubDir.listFiles());
-
-                        // Only add compiled files associated with the base.
-                        // Once b/62269291 is resolved, we can add all compiled files again.
-                        for (File oatFile : oatFiles) {
-                            if (oatFile.getName().equals("base.art")
-                                    || oatFile.getName().equals("base.odex")
-                                    || oatFile.getName().equals("base.vdex")) {
-                                mResolvedInheritedFiles.add(oatFile);
-                            }
+                        if (!oatFiles.isEmpty()) {
+                            mResolvedInheritedFiles.addAll(oatFiles);
                         }
                     }
                 }
diff --git a/com/android/server/pm/PackageManagerService.java b/com/android/server/pm/PackageManagerService.java
index 7d1a647..391deb7 100644
--- a/com/android/server/pm/PackageManagerService.java
+++ b/com/android/server/pm/PackageManagerService.java
@@ -398,7 +398,7 @@
     static final boolean DEBUG_DOMAIN_VERIFICATION = false;
     private static final boolean DEBUG_BACKUP = false;
     private static final boolean DEBUG_INSTALL = false;
-    private static final boolean DEBUG_REMOVE = false;
+    public static final boolean DEBUG_REMOVE = false;
     private static final boolean DEBUG_BROADCASTS = false;
     private static final boolean DEBUG_SHOW_INFO = false;
     private static final boolean DEBUG_PACKAGE_INFO = false;
@@ -406,7 +406,7 @@
     public static final boolean DEBUG_PACKAGE_SCANNING = false;
     private static final boolean DEBUG_VERIFY = false;
     private static final boolean DEBUG_FILTERS = false;
-    private static final boolean DEBUG_PERMISSIONS = false;
+    public static final boolean DEBUG_PERMISSIONS = false;
     private static final boolean DEBUG_SHARED_LIBRARIES = false;
     private static final boolean DEBUG_COMPRESSION = Build.IS_DEBUGGABLE;
 
@@ -954,9 +954,6 @@
     final SparseArray<PackageVerificationState> mPendingVerification
             = new SparseArray<PackageVerificationState>();
 
-    /** Set of packages associated with each app op permission. */
-    final ArrayMap<String, ArraySet<String>> mAppOpPermissionPackages = new ArrayMap<>();
-
     final PackageInstallerService mInstallerService;
 
     private final PackageDexOptimizer mPackageDexOptimizer;
@@ -2881,7 +2878,7 @@
                         + mSdkVersion + "; regranting permissions for internal storage");
                 updateFlags |= UPDATE_PERMISSIONS_REPLACE_PKG | UPDATE_PERMISSIONS_REPLACE_ALL;
             }
-            updatePermissionsLPw(null, null, StorageManager.UUID_PRIVATE_INTERNAL, updateFlags);
+            updatePermissionsLocked(null, null, StorageManager.UUID_PRIVATE_INTERNAL, updateFlags);
             ver.sdkVersion = mSdkVersion;
 
             // If this is the first boot or an update from pre-M, and it is a normal
@@ -3264,23 +3261,6 @@
             return null;
         }
 
-        // If we have a profile for a compressed APK, copy it to the reference location.
-        // Since the package is the stub one, remove the stub suffix to get the normal package and
-        // APK name.
-        File profileFile = new File(getPrebuildProfilePath(pkg).replace(STUB_SUFFIX, ""));
-        if (profileFile.exists()) {
-            try {
-                // We could also do this lazily before calling dexopt in
-                // PackageDexOptimizer to prevent this happening on first boot. The issue
-                // is that we don't have a good way to say "do this only once".
-                if (!mInstaller.copySystemProfile(profileFile.getAbsolutePath(),
-                        pkg.applicationInfo.uid, pkg.packageName)) {
-                    Log.e(TAG, "decompressPackage failed to copy system profile!");
-                }
-            } catch (Exception e) {
-                Log.e(TAG, "Failed to copy profile " + profileFile.getAbsolutePath() + " ", e);
-            }
-        }
         return dstCodePath;
     }
 
@@ -3368,7 +3348,9 @@
     @Override
     public boolean isUpgrade() {
         // allow instant applications
-        return mIsUpgrade;
+        // The system property allows testing ota flow when upgraded to the same image.
+        return mIsUpgrade || SystemProperties.getBoolean(
+                "persist.pm.mock-upgrade", false /* default */);
     }
 
     private @Nullable String getRequiredButNotReallyRequiredVerifierLPr() {
@@ -5182,7 +5164,7 @@
                 final PermissionsState permissionsState = settingBase.getPermissionsState();
                 if (permissionsState.hasPermission(permName, userId)) {
                     if (isUidInstantApp) {
-                        if (mPermissionManager.isPermissionInstant(permName)) {
+                        if (mSettings.mPermissions.isPermissionInstant(permName)) {
                             return PackageManager.PERMISSION_GRANTED;
                         }
                     } else {
@@ -5251,8 +5233,8 @@
         }
     }
 
-    boolean addPermission(PermissionInfo info, final boolean async) {
-        return mPermissionManager.addPermission(
+    private boolean addDynamicPermission(PermissionInfo info, final boolean async) {
+        return mPermissionManager.addDynamicPermission(
                 info, async, getCallingUid(), new PermissionCallback() {
                     @Override
                     public void onPermissionChanged() {
@@ -5268,20 +5250,20 @@
     @Override
     public boolean addPermission(PermissionInfo info) {
         synchronized (mPackages) {
-            return addPermission(info, false);
+            return addDynamicPermission(info, false);
         }
     }
 
     @Override
     public boolean addPermissionAsync(PermissionInfo info) {
         synchronized (mPackages) {
-            return addPermission(info, true);
+            return addDynamicPermission(info, true);
         }
     }
 
     @Override
     public void removePermission(String permName) {
-        mPermissionManager.removePermission(permName, getCallingUid(), mPermissionCallback);
+        mPermissionManager.removeDynamicPermission(permName, getCallingUid(), mPermissionCallback);
     }
 
     @Override
@@ -5942,17 +5924,8 @@
     }
 
     @Override
-    public String[] getAppOpPermissionPackages(String permissionName) {
-        if (getInstantAppPackageName(Binder.getCallingUid()) != null) {
-            return null;
-        }
-        synchronized (mPackages) {
-            ArraySet<String> pkgs = mAppOpPermissionPackages.get(permissionName);
-            if (pkgs == null) {
-                return null;
-            }
-            return pkgs.toArray(new String[pkgs.size()]);
-        }
+    public String[] getAppOpPermissionPackages(String permName) {
+        return mPermissionManager.getAppOpPermissionPackages(permName);
     }
 
     @Override
@@ -6927,6 +6900,13 @@
                     && info.activityInfo.splitName != null
                     && !ArrayUtils.contains(info.activityInfo.applicationInfo.splitNames,
                             info.activityInfo.splitName)) {
+                if (mInstantAppInstallerInfo == null) {
+                    if (DEBUG_INSTALL) {
+                        Slog.v(TAG, "No installer - not adding it to the ResolveInfo list");
+                    }
+                    resolveInfos.remove(i);
+                    continue;
+                }
                 // requested activity is defined in a split that hasn't been installed yet.
                 // add the installer to the resolve list
                 if (DEBUG_INSTALL) {
@@ -6940,14 +6920,22 @@
                         installFailureActivity,
                         info.activityInfo.applicationInfo.versionCode,
                         null /*failureIntent*/);
-                // make sure this resolver is the default
-                installerInfo.isDefault = true;
                 installerInfo.match = IntentFilter.MATCH_CATEGORY_SCHEME_SPECIFIC_PART
                         | IntentFilter.MATCH_ADJUSTMENT_NORMAL;
                 // add a non-generic filter
                 installerInfo.filter = new IntentFilter();
-                // load resources from the correct package
+
+                // This resolve info may appear in the chooser UI, so let us make it
+                // look as the one it replaces as far as the user is concerned which
+                // requires loading the correct label and icon for the resolve info.
                 installerInfo.resolvePackageName = info.getComponentInfo().packageName;
+                installerInfo.labelRes = info.resolveLabelResId();
+                installerInfo.icon = info.resolveIconResId();
+
+                // propagate priority/preferred order/default
+                installerInfo.priority = info.priority;
+                installerInfo.preferredOrder = info.preferredOrder;
+                installerInfo.isDefault = info.isDefault;
                 resolveInfos.set(i, installerInfo);
                 continue;
             }
@@ -9124,10 +9112,30 @@
                         // package and APK names.
                         String systemProfilePath =
                                 getPrebuildProfilePath(disabledPs.pkg).replace(STUB_SUFFIX, "");
-                        File systemProfile = new File(systemProfilePath);
-                        // Use the profile for compilation if there exists one for the same package
-                        // in the system partition.
-                        useProfileForDexopt = systemProfile.exists();
+                        profileFile = new File(systemProfilePath);
+                        // If we have a profile for a compressed APK, copy it to the reference
+                        // location.
+                        // Note that copying the profile here will cause it to override the
+                        // reference profile every OTA even though the existing reference profile
+                        // may have more data. We can't copy during decompression since the
+                        // directories are not set up at that point.
+                        if (profileFile.exists()) {
+                            try {
+                                // We could also do this lazily before calling dexopt in
+                                // PackageDexOptimizer to prevent this happening on first boot. The
+                                // issue is that we don't have a good way to say "do this only
+                                // once".
+                                if (!mInstaller.copySystemProfile(profileFile.getAbsolutePath(),
+                                        pkg.applicationInfo.uid, pkg.packageName)) {
+                                    Log.e(TAG, "Failed to copy system profile for stub package!");
+                                } else {
+                                    useProfileForDexopt = true;
+                                }
+                            } catch (Exception e) {
+                                Log.e(TAG, "Failed to copy profile " +
+                                        profileFile.getAbsolutePath() + " ", e);
+                            }
+                        }
                     }
                 }
             }
@@ -10346,7 +10354,7 @@
                             Slog.i(TAG, "Adopting permissions from " + origName + " to "
                                     + pkg.packageName);
                             // SIDE EFFECTS; updates permissions system state; move elsewhere
-                            mSettings.transferPermissionsLPw(origName, pkg.packageName);
+                            mSettings.mPermissions.transferPermissions(origName, pkg.packageName);
                         }
                     }
                 }
@@ -11194,54 +11202,13 @@
                 if (DEBUG_PACKAGE_SCANNING) Log.d(TAG, "  Permission Groups: " + r);
             }
 
-            N = pkg.permissions.size();
-            r = null;
-            for (i=0; i<N; i++) {
-                PackageParser.Permission p = pkg.permissions.get(i);
 
-                // Dont allow ephemeral apps to define new permissions.
-                if ((scanFlags & SCAN_AS_INSTANT_APP) != 0) {
-                    Slog.w(TAG, "Permission " + p.info.name + " from package "
-                            + p.info.packageName
-                            + " ignored: instant apps cannot define new permissions.");
-                    continue;
-                }
-
-                // Assume by default that we did not install this permission into the system.
-                p.info.flags &= ~PermissionInfo.FLAG_INSTALLED;
-
-                // Now that permission groups have a special meaning, we ignore permission
-                // groups for legacy apps to prevent unexpected behavior. In particular,
-                // permissions for one app being granted to someone just because they happen
-                // to be in a group defined by another app (before this had no implications).
-                if (pkg.applicationInfo.targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1) {
-                    p.group = mPermissionGroups.get(p.info.group);
-                    // Warn for a permission in an unknown group.
-                    if (DEBUG_PERMISSIONS && p.info.group != null && p.group == null) {
-                        Slog.i(TAG, "Permission " + p.info.name + " from package "
-                                + p.info.packageName + " in an unknown group " + p.info.group);
-                    }
-                }
-
-                // TODO Move to PermissionManager once mPermissionTrees moves there.
-//                        p.tree ? mSettings.mPermissionTrees
-//                                : mSettings.mPermissions;
-//                final BasePermission bp = BasePermission.createOrUpdate(
-//                        permissionMap.get(p.info.name), p, pkg, mSettings.mPermissionTrees, chatty);
-//                permissionMap.put(p.info.name, bp);
-                if (p.tree) {
-                    final ArrayMap<String, BasePermission> permissionMap =
-                            mSettings.mPermissionTrees;
-                    final BasePermission bp = BasePermission.createOrUpdate(
-                            permissionMap.get(p.info.name), p, pkg, mSettings.mPermissionTrees,
-                            chatty);
-                    permissionMap.put(p.info.name, bp);
-                } else {
-                    final BasePermission bp = BasePermission.createOrUpdate(
-                            (BasePermission) mPermissionManager.getPermissionTEMP(p.info.name),
-                            p, pkg, mSettings.mPermissionTrees, chatty);
-                    mPermissionManager.putPermissionTEMP(p.info.name, bp);
-                }
+            // Dont allow ephemeral apps to define new permissions.
+            if ((scanFlags & SCAN_AS_INSTANT_APP) != 0) {
+                Slog.w(TAG, "Permissions from package " + pkg.packageName
+                        + " ignored: instant apps cannot define new permissions.");
+            } else {
+                mPermissionManager.addAllPermissions(pkg, chatty);
             }
 
             N = pkg.instrumentation.size();
@@ -11967,53 +11934,7 @@
             if (DEBUG_REMOVE) Log.d(TAG, "  Activities: " + r);
         }
 
-        N = pkg.permissions.size();
-        r = null;
-        for (i=0; i<N; i++) {
-            PackageParser.Permission p = pkg.permissions.get(i);
-            BasePermission bp = (BasePermission) mPermissionManager.getPermissionTEMP(p.info.name);
-            if (bp == null) {
-                bp = mSettings.mPermissionTrees.get(p.info.name);
-            }
-            if (bp != null && bp.isPermission(p)) {
-                bp.setPermission(null);
-                if (DEBUG_REMOVE && chatty) {
-                    if (r == null) {
-                        r = new StringBuilder(256);
-                    } else {
-                        r.append(' ');
-                    }
-                    r.append(p.info.name);
-                }
-            }
-            if ((p.info.protectionLevel&PermissionInfo.PROTECTION_FLAG_APPOP) != 0) {
-                ArraySet<String> appOpPkgs = mAppOpPermissionPackages.get(p.info.name);
-                if (appOpPkgs != null) {
-                    appOpPkgs.remove(pkg.packageName);
-                }
-            }
-        }
-        if (r != null) {
-            if (DEBUG_REMOVE) Log.d(TAG, "  Permissions: " + r);
-        }
-
-        N = pkg.requestedPermissions.size();
-        r = null;
-        for (i=0; i<N; i++) {
-            String perm = pkg.requestedPermissions.get(i);
-            if (mPermissionManager.isPermissionAppOp(perm)) {
-                ArraySet<String> appOpPkgs = mAppOpPermissionPackages.get(perm);
-                if (appOpPkgs != null) {
-                    appOpPkgs.remove(pkg.packageName);
-                    if (appOpPkgs.isEmpty()) {
-                        mAppOpPermissionPackages.remove(perm);
-                    }
-                }
-            }
-        }
-        if (r != null) {
-            if (DEBUG_REMOVE) Log.d(TAG, "  Permissions: " + r);
-        }
+        mPermissionManager.removeAllPermissions(pkg, chatty);
 
         N = pkg.instrumentation.size();
         r = null;
@@ -12074,18 +11995,9 @@
         }
     }
 
-    private static boolean hasPermission(PackageParser.Package pkgInfo, String perm) {
-        for (int i=pkgInfo.permissions.size()-1; i>=0; i--) {
-            if (pkgInfo.permissions.get(i).info.name.equals(perm)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    static final int UPDATE_PERMISSIONS_ALL = 1<<0;
-    static final int UPDATE_PERMISSIONS_REPLACE_PKG = 1<<1;
-    static final int UPDATE_PERMISSIONS_REPLACE_ALL = 1<<2;
+    public static final int UPDATE_PERMISSIONS_ALL = 1<<0;
+    public static final int UPDATE_PERMISSIONS_REPLACE_PKG = 1<<1;
+    public static final int UPDATE_PERMISSIONS_REPLACE_ALL = 1<<2;
 
     private void updatePermissionsLPw(PackageParser.Package pkg, int flags) {
         // Update the parent permissions
@@ -12101,10 +12013,10 @@
     private void updatePermissionsLPw(String changingPkg, PackageParser.Package pkgInfo,
             int flags) {
         final String volumeUuid = (pkgInfo != null) ? getVolumeUuidForPackage(pkgInfo) : null;
-        updatePermissionsLPw(changingPkg, pkgInfo, volumeUuid, flags);
+        updatePermissionsLocked(changingPkg, pkgInfo, volumeUuid, flags);
     }
 
-    private void updatePermissionsLPw(String changingPkg,
+    private void updatePermissionsLocked(String changingPkg,
             PackageParser.Package pkgInfo, String replaceVolumeUuid, int flags) {
         // TODO: Most of the methods exposing BasePermission internals [source package name,
         // etc..] shouldn't be needed. Instead, when we've parsed a permission that doesn't
@@ -12116,55 +12028,11 @@
         // normal permissions. Today, we need two separate loops because these BasePermission
         // objects are stored separately.
         // Make sure there are no dangling permission trees.
-        Iterator<BasePermission> it = mSettings.mPermissionTrees.values().iterator();
-        while (it.hasNext()) {
-            final BasePermission bp = it.next();
-            if (bp.getSourcePackageSetting() == null) {
-                // We may not yet have parsed the package, so just see if
-                // we still know about its settings.
-                bp.setSourcePackageSetting(mSettings.mPackages.get(bp.getSourcePackageName()));
-            }
-            if (bp.getSourcePackageSetting() == null) {
-                Slog.w(TAG, "Removing dangling permission tree: " + bp.getName()
-                        + " from package " + bp.getSourcePackageName());
-                it.remove();
-            } else if (changingPkg != null && changingPkg.equals(bp.getSourcePackageName())) {
-                if (pkgInfo == null || !hasPermission(pkgInfo, bp.getName())) {
-                    Slog.i(TAG, "Removing old permission tree: " + bp.getName()
-                            + " from package " + bp.getSourcePackageName());
-                    flags |= UPDATE_PERMISSIONS_ALL;
-                    it.remove();
-                }
-            }
-        }
+        flags = mPermissionManager.updatePermissionTrees(changingPkg, pkgInfo, flags);
 
         // Make sure all dynamic permissions have been assigned to a package,
         // and make sure there are no dangling permissions.
-        final Iterator<BasePermission> permissionIter =
-                mPermissionManager.getPermissionIteratorTEMP();
-        while (permissionIter.hasNext()) {
-            final BasePermission bp = permissionIter.next();
-            if (bp.isDynamic()) {
-                bp.updateDynamicPermission(mSettings.mPermissionTrees);
-            }
-            if (bp.getSourcePackageSetting() == null) {
-                // We may not yet have parsed the package, so just see if
-                // we still know about its settings.
-                bp.setSourcePackageSetting(mSettings.mPackages.get(bp.getSourcePackageName()));
-            }
-            if (bp.getSourcePackageSetting() == null) {
-                Slog.w(TAG, "Removing dangling permission: " + bp.getName()
-                        + " from package " + bp.getSourcePackageName());
-                permissionIter.remove();
-            } else if (changingPkg != null && changingPkg.equals(bp.getSourcePackageName())) {
-                if (pkgInfo == null || !hasPermission(pkgInfo, bp.getName())) {
-                    Slog.i(TAG, "Removing old permission: " + bp.getName()
-                            + " from package " + bp.getSourcePackageName());
-                    flags |= UPDATE_PERMISSIONS_ALL;
-                    permissionIter.remove();
-                }
-            }
-        }
+        flags = mPermissionManager.updatePermissions(changingPkg, pkgInfo, flags);
 
         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "grantPermissions");
         // Now update the permissions for all packages, in particular
@@ -12286,12 +12154,7 @@
 
             // Keep track of app op permissions.
             if (bp.isAppOp()) {
-                ArraySet<String> pkgs = mAppOpPermissionPackages.get(perm);
-                if (pkgs == null) {
-                    pkgs = new ArraySet<>();
-                    mAppOpPermissionPackages.put(perm, pkgs);
-                }
-                pkgs.add(pkg.packageName);
+                mSettings.addAppOpPackage(perm, pkg.packageName);
             }
 
             if (bp.isNormal()) {
@@ -12580,7 +12443,8 @@
                             .getPrivAppDenyPermissions(pkg.packageName);
                     final boolean permissionViolation =
                             deniedPermissions == null || !deniedPermissions.contains(perm);
-                    if (permissionViolation) {
+                    if (permissionViolation
+                            && RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE) {
                         if (mPrivappPermissionsViolations == null) {
                             mPrivappPermissionsViolations = new ArraySet<>();
                         }
@@ -21238,7 +21102,7 @@
         // permissions, ensure permissions are updated. Beware of dragons if you
         // try optimizing this.
         synchronized (mPackages) {
-            updatePermissionsLPw(null, null, StorageManager.UUID_PRIVATE_INTERNAL,
+            updatePermissionsLocked(null, null, StorageManager.UUID_PRIVATE_INTERNAL,
                     UPDATE_PERMISSIONS_ALL);
         }
 
@@ -21289,9 +21153,8 @@
         reconcileApps(StorageManager.UUID_PRIVATE_INTERNAL);
 
         if (mPrivappPermissionsViolations != null) {
-            Slog.wtf(TAG,"Signature|privileged permissions not in "
+            throw new IllegalStateException("Signature|privileged permissions not in "
                     + "privapp-permissions whitelist: " + mPrivappPermissionsViolations);
-            mPrivappPermissionsViolations = null;
         }
     }
 
@@ -21784,22 +21647,6 @@
 
             if (!checkin && dumpState.isDumping(DumpState.DUMP_PERMISSIONS)) {
                 mSettings.dumpPermissionsLPr(pw, packageName, permissionNames, dumpState);
-                if (packageName == null && permissionNames == null) {
-                    for (int iperm=0; iperm<mAppOpPermissionPackages.size(); iperm++) {
-                        if (iperm == 0) {
-                            if (dumpState.onTitlePrinted())
-                                pw.println();
-                            pw.println("AppOp Permissions:");
-                        }
-                        pw.print("  AppOp Permission ");
-                        pw.print(mAppOpPermissionPackages.keyAt(iperm));
-                        pw.println(":");
-                        ArraySet<String> pkgs = mAppOpPermissionPackages.valueAt(iperm);
-                        for (int ipkg=0; ipkg<pkgs.size(); ipkg++) {
-                            pw.print("    "); pw.println(pkgs.valueAt(ipkg));
-                        }
-                    }
-                }
             }
 
             if (!checkin && dumpState.isDumping(DumpState.DUMP_PROVIDERS)) {
@@ -22029,11 +21876,7 @@
         synchronized (mAvailableFeatures) {
             final int count = mAvailableFeatures.size();
             for (int i = 0; i < count; i++) {
-                final FeatureInfo feat = mAvailableFeatures.valueAt(i);
-                final long featureToken = proto.start(PackageServiceDumpProto.FEATURES);
-                proto.write(PackageServiceDumpProto.FeatureProto.NAME, feat.name);
-                proto.write(PackageServiceDumpProto.FeatureProto.VERSION, feat.version);
-                proto.end(featureToken);
+                mAvailableFeatures.valueAt(i).writeToProto(proto, PackageServiceDumpProto.FEATURES);
             }
         }
     }
@@ -22065,7 +21908,7 @@
     }
 
     private void dumpDexoptStateLPr(PrintWriter pw, String packageName) {
-        final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ", 120);
+        final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");
         ipw.println();
         ipw.println("Dexopt state:");
         ipw.increaseIndent();
@@ -22092,7 +21935,7 @@
     }
 
     private void dumpCompilerStatsLPr(PrintWriter pw, String packageName) {
-        final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ", 120);
+        final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");
         ipw.println();
         ipw.println("Compiler stats:");
         ipw.increaseIndent();
@@ -22303,7 +22146,7 @@
                         + mSdkVersion + "; regranting permissions for " + volumeUuid);
                 updateFlags |= UPDATE_PERMISSIONS_REPLACE_PKG | UPDATE_PERMISSIONS_REPLACE_ALL;
             }
-            updatePermissionsLPw(null, null, volumeUuid, updateFlags);
+            updatePermissionsLocked(null, null, volumeUuid, updateFlags);
 
             // Yay, everything is now upgraded
             ver.forceCurrent();
@@ -23311,15 +23154,15 @@
     void onNewUserCreated(final int userId) {
         synchronized(mPackages) {
             mDefaultPermissionPolicy.grantDefaultPermissions(mPackages.values(), userId);
-        }
-        // If permission review for legacy apps is required, we represent
-        // dagerous permissions for such apps as always granted runtime
-        // permissions to keep per user flag state whether review is needed.
-        // Hence, if a new user is added we have to propagate dangerous
-        // permission grants for these legacy apps.
-        if (mPermissionReviewRequired) {
-            updatePermissionsLPw(null, null, UPDATE_PERMISSIONS_ALL
-                    | UPDATE_PERMISSIONS_REPLACE_ALL);
+            // If permission review for legacy apps is required, we represent
+            // dagerous permissions for such apps as always granted runtime
+            // permissions to keep per user flag state whether review is needed.
+            // Hence, if a new user is added we have to propagate dangerous
+            // permission grants for these legacy apps.
+            if (mPermissionReviewRequired) {
+                updatePermissionsLPw(null, null, UPDATE_PERMISSIONS_ALL
+                        | UPDATE_PERMISSIONS_REPLACE_ALL);
+            }
         }
     }
 
@@ -23763,12 +23606,12 @@
         }
 
         @Override
-        public Object enforcePermissionTreeTEMP(String permName, int callingUid) {
+        public PackageParser.PermissionGroup getPermissionGroupTEMP(String groupName) {
             synchronized (mPackages) {
-                return BasePermission.enforcePermissionTreeLP(
-                        mSettings.mPermissionTrees, permName, callingUid);
+                return mPermissionGroups.get(groupName);
             }
         }
+
         @Override
         public boolean isInstantApp(String packageName, int userId) {
             return PackageManagerService.this.isInstantApp(packageName, userId);
diff --git a/com/android/server/pm/PackageManagerServiceUtils.java b/com/android/server/pm/PackageManagerServiceUtils.java
index 8f7971e..67e06dd 100644
--- a/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/com/android/server/pm/PackageManagerServiceUtils.java
@@ -35,6 +35,7 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.system.ErrnoException;
+import android.system.Os;
 import android.util.ArraySet;
 import android.util.Log;
 import android.util.Slog;
@@ -232,7 +233,7 @@
      */
     public static String realpath(File path) throws IOException {
         try {
-            return Libcore.os.realpath(path.getAbsolutePath());
+            return Os.realpath(path.getAbsolutePath());
         } catch (ErrnoException ee) {
             throw ee.rethrowAsIOException();
         }
diff --git a/com/android/server/pm/Settings.java b/com/android/server/pm/Settings.java
index 0084411..191b43a 100644
--- a/com/android/server/pm/Settings.java
+++ b/com/android/server/pm/Settings.java
@@ -378,10 +378,6 @@
     private final ArrayMap<Long, Integer> mKeySetRefs =
             new ArrayMap<Long, Integer>();
 
-    // Mapping from permission tree names to info about them.
-    final ArrayMap<String, BasePermission> mPermissionTrees =
-            new ArrayMap<String, BasePermission>();
-
     // Packages that have been uninstalled and still need their external
     // storage data deleted.
     final ArrayList<PackageCleanItem> mPackagesToBeCleaned = new ArrayList<PackageCleanItem>();
@@ -416,7 +412,7 @@
 
     public final KeySetManagerService mKeySetManagerService = new KeySetManagerService(mPackages);
     /** Settings and other information about permissions */
-    private final PermissionSettings mPermissions;
+    final PermissionSettings mPermissions;
 
     Settings(PermissionSettings permissions, Object lock) {
         this(Environment.getDataDirectory(), permissions, lock);
@@ -622,6 +618,10 @@
         return null;
     }
 
+    void addAppOpPackage(String permName, String packageName) {
+        mPermissions.addAppOpPackage(permName, packageName);
+    }
+
     SharedUserSetting addSharedUserLPw(String name, int uid, int pkgFlags, int pkgPrivateFlags) {
         SharedUserSetting s = mSharedUsers.get(name);
         if (s != null) {
@@ -666,13 +666,6 @@
     }
 
     /**
-     * Transfers ownership of permissions from one package to another.
-     */
-    void transferPermissionsLPw(String origPackageName, String newPackageName) {
-        mPermissions.transferPermissions(origPackageName, newPackageName, mPermissionTrees);
-    }
-
-    /**
      * Creates a new {@code PackageSetting} object.
      * Use this method instead of the constructor to ensure a settings object is created
      * with the correct base.
@@ -2496,9 +2489,7 @@
             }
 
             serializer.startTag(null, "permission-trees");
-            for (BasePermission bp : mPermissionTrees.values()) {
-                writePermissionLPr(serializer, bp);
-            }
+            mPermissions.writePermissionTrees(serializer);
             serializer.endTag(null, "permission-trees");
 
             serializer.startTag(null, "permissions");
@@ -3042,7 +3033,7 @@
                 } else if (tagName.equals("permissions")) {
                     mPermissions.readPermissions(parser);
                 } else if (tagName.equals("permission-trees")) {
-                    PermissionSettings.readPermissions(mPermissionTrees, parser);
+                    mPermissions.readPermissionTrees(parser);
                 } else if (tagName.equals("shared-user")) {
                     readSharedUserLPw(parser);
                 } else if (tagName.equals("preferred-packages")) {
@@ -4938,11 +4929,7 @@
     void dumpSharedUsersProto(ProtoOutputStream proto) {
         final int count = mSharedUsers.size();
         for (int i = 0; i < count; i++) {
-            final SharedUserSetting su = mSharedUsers.valueAt(i);
-            final long sharedUserToken = proto.start(PackageServiceDumpProto.SHARED_USERS);
-            proto.write(PackageServiceDumpProto.SharedUserProto.USER_ID, su.userId);
-            proto.write(PackageServiceDumpProto.SharedUserProto.NAME, su.name);
-            proto.end(sharedUserToken);
+            mSharedUsers.valueAt(i).writeToProto(proto, PackageServiceDumpProto.SHARED_USERS);
         }
     }
 
diff --git a/com/android/server/pm/SharedUserSetting.java b/com/android/server/pm/SharedUserSetting.java
index a0dadae..877da14 100644
--- a/com/android/server/pm/SharedUserSetting.java
+++ b/com/android/server/pm/SharedUserSetting.java
@@ -18,7 +18,9 @@
 
 import android.annotation.Nullable;
 import android.content.pm.PackageParser;
+import android.service.pm.PackageServiceDumpProto;
 import android.util.ArraySet;
+import android.util.proto.ProtoOutputStream;
 
 import java.util.ArrayList;
 import java.util.Collection;
@@ -53,6 +55,13 @@
                 + name + "/" + userId + "}";
     }
 
+    public void writeToProto(ProtoOutputStream proto, long fieldId) {
+        long token = proto.start(fieldId);
+        proto.write(PackageServiceDumpProto.SharedUserProto.USER_ID, userId);
+        proto.write(PackageServiceDumpProto.SharedUserProto.NAME, name);
+        proto.end(token);
+    }
+
     void removePackage(PackageSetting packageSetting) {
         if (packages.remove(packageSetting)) {
             // recalculate the pkgFlags for this shared user if needed
diff --git a/com/android/server/pm/ShortcutBitmapSaver.java b/com/android/server/pm/ShortcutBitmapSaver.java
index 4f5d156..815f885 100644
--- a/com/android/server/pm/ShortcutBitmapSaver.java
+++ b/com/android/server/pm/ShortcutBitmapSaver.java
@@ -21,6 +21,8 @@
 import android.graphics.Bitmap;
 import android.graphics.Bitmap.CompressFormat;
 import android.graphics.drawable.Icon;
+import android.os.StrictMode;
+import android.os.StrictMode.ThreadPolicy;
 import android.os.SystemClock;
 import android.util.Log;
 import android.util.Slog;
@@ -165,7 +167,13 @@
 
         // Compress it and enqueue to the requests.
         final byte[] bytes;
+        final StrictMode.ThreadPolicy oldPolicy = StrictMode.getThreadPolicy();
         try {
+            // compress() triggers a slow call, but in this case it's needed to save RAM and also
+            // the target bitmap is of an icon size, so let's just permit it.
+            StrictMode.setThreadPolicy(new ThreadPolicy.Builder(oldPolicy)
+                    .permitCustomSlowCalls()
+                    .build());
             final Bitmap shrunk = mService.shrinkBitmap(original, maxDimension);
             try {
                 try (final ByteArrayOutputStream out = new ByteArrayOutputStream(64 * 1024)) {
@@ -184,6 +192,8 @@
         } catch (IOException | RuntimeException | OutOfMemoryError e) {
             Slog.wtf(ShortcutService.TAG, "Unable to write bitmap to file", e);
             return;
+        } finally {
+            StrictMode.setThreadPolicy(oldPolicy);
         }
 
         shortcut.addFlags(
diff --git a/com/android/server/pm/ShortcutLauncher.java b/com/android/server/pm/ShortcutLauncher.java
index f922ad1..cedf476 100644
--- a/com/android/server/pm/ShortcutLauncher.java
+++ b/com/android/server/pm/ShortcutLauncher.java
@@ -83,11 +83,16 @@
         return mOwnerUserId;
     }
 
+    @Override
+    protected boolean canRestoreAnyVersion() {
+        // Launcher's pinned shortcuts can be restored to an older version.
+        return true;
+    }
+
     /**
      * Called when the new package can't receive the backup, due to signature or version mismatch.
      */
-    @Override
-    protected void onRestoreBlocked() {
+    private void onRestoreBlocked() {
         final ArrayList<PackageWithUser> pinnedPackages =
                 new ArrayList<>(mPinnedShortcuts.keySet());
         mPinnedShortcuts.clear();
@@ -101,15 +106,21 @@
     }
 
     @Override
-    protected void onRestored() {
-        // Nothing to do.
+    protected void onRestored(int restoreBlockReason) {
+        // For launcher, possible reasons here are DISABLED_REASON_SIGNATURE_MISMATCH or
+        // DISABLED_REASON_BACKUP_NOT_SUPPORTED.
+        // DISABLED_REASON_VERSION_LOWER will NOT happen because we don't check version
+        // code for launchers.
+        if (restoreBlockReason != ShortcutInfo.DISABLED_REASON_NOT_DISABLED) {
+            onRestoreBlocked();
+        }
     }
 
     /**
      * Pin the given shortcuts, replacing the current pinned ones.
      */
     public void pinShortcuts(@UserIdInt int packageUserId,
-            @NonNull String packageName, @NonNull List<String> ids) {
+            @NonNull String packageName, @NonNull List<String> ids, boolean forPinRequest) {
         final ShortcutPackage packageShortcuts =
                 mShortcutUser.getPackageShortcutsIfExists(packageName);
         if (packageShortcuts == null) {
@@ -124,8 +135,12 @@
         } else {
             final ArraySet<String> prevSet = mPinnedShortcuts.get(pu);
 
-            // Pin shortcuts.  Make sure only pin the ones that were visible to the caller.
-            // i.e. a non-dynamic, pinned shortcut by *other launchers* shouldn't be pinned here.
+            // Actually pin shortcuts.
+            // This logic here is to make sure a launcher cannot pin a shortcut that is floating
+            // (i.e. not dynamic nor manifest but is pinned) and pinned by another launcher.
+            // In this case, technically the shortcut doesn't exist to this launcher, so it can't
+            // pin it.
+            // (Maybe unnecessarily strict...)
 
             final ArraySet<String> newSet = new ArraySet<>();
 
@@ -135,8 +150,10 @@
                 if (si == null) {
                     continue;
                 }
-                if (si.isDynamic() || si.isManifestShortcut()
-                        || (prevSet != null && prevSet.contains(id))) {
+                if (si.isDynamic()
+                        || si.isManifestShortcut()
+                        || (prevSet != null && prevSet.contains(id))
+                        || forPinRequest) {
                     newSet.add(id);
                 }
             }
@@ -155,7 +172,7 @@
     }
 
     /**
-     * Return true if the given shortcut is pinned by this launcher.
+     * Return true if the given shortcut is pinned by this launcher.<code></code>
      */
     public boolean hasPinned(ShortcutInfo shortcut) {
         final ArraySet<String> pinned =
@@ -164,10 +181,10 @@
     }
 
     /**
-     * Additionally pin a shortcut. c.f. {@link #pinShortcuts(int, String, List)}
+     * Additionally pin a shortcut. c.f. {@link #pinShortcuts(int, String, List, boolean)}
      */
     public void addPinnedShortcut(@NonNull String packageName, @UserIdInt int packageUserId,
-            String id) {
+            String id, boolean forPinRequest) {
         final ArraySet<String> pinnedSet = getPinnedShortcutIds(packageName, packageUserId);
         final ArrayList<String> pinnedList;
         if (pinnedSet != null) {
@@ -178,21 +195,21 @@
         }
         pinnedList.add(id);
 
-        pinShortcuts(packageUserId, packageName, pinnedList);
+        pinShortcuts(packageUserId, packageName, pinnedList, forPinRequest);
     }
 
     boolean cleanUpPackage(String packageName, @UserIdInt int packageUserId) {
         return mPinnedShortcuts.remove(PackageWithUser.of(packageUserId, packageName)) != null;
     }
 
-    public void ensureVersionInfo() {
+    public void ensurePackageInfo() {
         final PackageInfo pi = mShortcutUser.mService.getPackageInfoWithSignatures(
                 getPackageName(), getPackageUserId());
         if (pi == null) {
             Slog.w(TAG, "Package not found: " + getPackageName());
             return;
         }
-        getPackageInfo().updateVersionInfo(pi);
+        getPackageInfo().updateFromPackageInfo(pi);
     }
 
     /**
@@ -201,6 +218,10 @@
     @Override
     public void saveToXml(XmlSerializer out, boolean forBackup)
             throws IOException {
+        if (forBackup && !getPackageInfo().isBackupAllowed()) {
+            // If an launcher app doesn't support backup&restore, then nothing to do.
+            return;
+        }
         final int size = mPinnedShortcuts.size();
         if (size == 0) {
             return; // Nothing to write.
@@ -209,7 +230,7 @@
         out.startTag(null, TAG_ROOT);
         ShortcutService.writeAttr(out, ATTR_PACKAGE_NAME, getPackageName());
         ShortcutService.writeAttr(out, ATTR_LAUNCHER_USER_ID, getPackageUserId());
-        getPackageInfo().saveToXml(out);
+        getPackageInfo().saveToXml(out, forBackup);
 
         for (int i = 0; i < size; i++) {
             final PackageWithUser pu = mPinnedShortcuts.keyAt(i);
diff --git a/com/android/server/pm/ShortcutPackage.java b/com/android/server/pm/ShortcutPackage.java
index 6fc1e73..a3585bc 100644
--- a/com/android/server/pm/ShortcutPackage.java
+++ b/com/android/server/pm/ShortcutPackage.java
@@ -84,6 +84,7 @@
     private static final String ATTR_DISABLED_MESSAGE = "dmessage";
     private static final String ATTR_DISABLED_MESSAGE_RES_ID = "dmessageid";
     private static final String ATTR_DISABLED_MESSAGE_RES_NAME = "dmessagename";
+    private static final String ATTR_DISABLED_REASON = "disabled-reason";
     private static final String ATTR_INTENT_LEGACY = "intent";
     private static final String ATTR_INTENT_NO_EXTRA = "intent-base";
     private static final String ATTR_RANK = "rank";
@@ -156,13 +157,25 @@
     }
 
     @Override
-    protected void onRestoreBlocked() {
-        // Can't restore due to version/signature mismatch.  Remove all shortcuts.
-        mShortcuts.clear();
+    protected boolean canRestoreAnyVersion() {
+        return false;
     }
 
     @Override
-    protected void onRestored() {
+    protected void onRestored(int restoreBlockReason) {
+        // Shortcuts have been restored.
+        // - Unshadow all shortcuts.
+        // - Set disabled reason.
+        // - Disable if needed.
+        for (int i = mShortcuts.size() - 1; i >= 0; i--) {
+            ShortcutInfo si = mShortcuts.valueAt(i);
+            si.clearFlags(ShortcutInfo.FLAG_SHADOW);
+
+            si.setDisabledReason(restoreBlockReason);
+            if (restoreBlockReason != ShortcutInfo.DISABLED_REASON_NOT_DISABLED) {
+                si.addFlags(ShortcutInfo.FLAG_DISABLED);
+            }
+        }
         // Because some launchers may not have been restored (e.g. allowBackup=false),
         // we need to re-calculate the pinned shortcuts.
         refreshPinnedFlags();
@@ -176,31 +189,47 @@
         return mShortcuts.get(id);
     }
 
-    private void ensureNotImmutable(@Nullable ShortcutInfo shortcut) {
-        if (shortcut != null && shortcut.isImmutable()) {
+    public boolean isShortcutExistsAndInvisibleToPublisher(String id) {
+        ShortcutInfo si = findShortcutById(id);
+        return si != null && !si.isVisibleToPublisher();
+    }
+
+    public boolean isShortcutExistsAndVisibleToPublisher(String id) {
+        ShortcutInfo si = findShortcutById(id);
+        return si != null && si.isVisibleToPublisher();
+    }
+
+    private void ensureNotImmutable(@Nullable ShortcutInfo shortcut, boolean ignoreInvisible) {
+        if (shortcut != null && shortcut.isImmutable()
+                && (!ignoreInvisible || shortcut.isVisibleToPublisher())) {
             throw new IllegalArgumentException(
                     "Manifest shortcut ID=" + shortcut.getId()
                             + " may not be manipulated via APIs");
         }
     }
 
-    public void ensureNotImmutable(@NonNull String id) {
-        ensureNotImmutable(mShortcuts.get(id));
+    public void ensureNotImmutable(@NonNull String id, boolean ignoreInvisible) {
+        ensureNotImmutable(mShortcuts.get(id), ignoreInvisible);
     }
 
-    public void ensureImmutableShortcutsNotIncludedWithIds(@NonNull List<String> shortcutIds) {
+    public void ensureImmutableShortcutsNotIncludedWithIds(@NonNull List<String> shortcutIds,
+            boolean ignoreInvisible) {
         for (int i = shortcutIds.size() - 1; i >= 0; i--) {
-            ensureNotImmutable(shortcutIds.get(i));
+            ensureNotImmutable(shortcutIds.get(i), ignoreInvisible);
         }
     }
 
-    public void ensureImmutableShortcutsNotIncluded(@NonNull List<ShortcutInfo> shortcuts) {
+    public void ensureImmutableShortcutsNotIncluded(@NonNull List<ShortcutInfo> shortcuts,
+            boolean ignoreInvisible) {
         for (int i = shortcuts.size() - 1; i >= 0; i--) {
-            ensureNotImmutable(shortcuts.get(i).getId());
+            ensureNotImmutable(shortcuts.get(i).getId(), ignoreInvisible);
         }
     }
 
-    private ShortcutInfo deleteShortcutInner(@NonNull String id) {
+    /**
+     * Delete a shortcut by ID. This will *always* remove it even if it's immutable or invisible.
+     */
+    private ShortcutInfo forceDeleteShortcutInner(@NonNull String id) {
         final ShortcutInfo shortcut = mShortcuts.remove(id);
         if (shortcut != null) {
             mShortcutUser.mService.removeIconLocked(shortcut);
@@ -210,10 +239,14 @@
         return shortcut;
     }
 
-    private void addShortcutInner(@NonNull ShortcutInfo newShortcut) {
+    /**
+     * Force replace a shortcut. If there's already a shortcut with the same ID, it'll be removed,
+     * even if it's invisible.
+     */
+    private void forceReplaceShortcutInner(@NonNull ShortcutInfo newShortcut) {
         final ShortcutService s = mShortcutUser.mService;
 
-        deleteShortcutInner(newShortcut.getId());
+        forceDeleteShortcutInner(newShortcut.getId());
 
         // Extract Icon and update the icon res ID and the bitmap path.
         s.saveIconAndFixUpShortcutLocked(newShortcut);
@@ -222,11 +255,12 @@
     }
 
     /**
-     * Add a shortcut, or update one with the same ID, with taking over existing flags.
+     * Add a shortcut. If there's already a one with the same ID, it'll be removed, even if it's
+     * invisible.
      *
      * It checks the max number of dynamic shortcuts.
      */
-    public void addOrUpdateDynamicShortcut(@NonNull ShortcutInfo newShortcut) {
+    public void addOrReplaceDynamicShortcut(@NonNull ShortcutInfo newShortcut) {
 
         Preconditions.checkArgument(newShortcut.isEnabled(),
                 "add/setDynamicShortcuts() cannot publish disabled shortcuts");
@@ -242,7 +276,7 @@
         } else {
             // It's an update case.
             // Make sure the target is updatable. (i.e. should be mutable.)
-            oldShortcut.ensureUpdatableWith(newShortcut);
+            oldShortcut.ensureUpdatableWith(newShortcut, /*isUpdating=*/ false);
 
             wasPinned = oldShortcut.isPinned();
         }
@@ -252,7 +286,7 @@
             newShortcut.addFlags(ShortcutInfo.FLAG_PINNED);
         }
 
-        addShortcutInner(newShortcut);
+        forceReplaceShortcutInner(newShortcut);
     }
 
     /**
@@ -273,7 +307,7 @@
         }
         if (removeList != null) {
             for (int i = removeList.size() - 1; i >= 0; i--) {
-                deleteShortcutInner(removeList.get(i));
+                forceDeleteShortcutInner(removeList.get(i));
             }
         }
     }
@@ -281,13 +315,13 @@
     /**
      * Remove all dynamic shortcuts.
      */
-    public void deleteAllDynamicShortcuts() {
+    public void deleteAllDynamicShortcuts(boolean ignoreInvisible) {
         final long now = mShortcutUser.mService.injectCurrentTimeMillis();
 
         boolean changed = false;
         for (int i = mShortcuts.size() - 1; i >= 0; i--) {
             final ShortcutInfo si = mShortcuts.valueAt(i);
-            if (si.isDynamic()) {
+            if (si.isDynamic() && (!ignoreInvisible || si.isVisibleToPublisher())) {
                 changed = true;
 
                 si.setTimestamp(now);
@@ -307,9 +341,10 @@
      * @return true if it's actually removed because it wasn't pinned, or false if it's still
      * pinned.
      */
-    public boolean deleteDynamicWithId(@NonNull String shortcutId) {
+    public boolean deleteDynamicWithId(@NonNull String shortcutId, boolean ignoreInvisible) {
         final ShortcutInfo removed = deleteOrDisableWithId(
-                shortcutId, /* disable =*/ false, /* overrideImmutable=*/ false);
+                shortcutId, /* disable =*/ false, /* overrideImmutable=*/ false, ignoreInvisible,
+                ShortcutInfo.DISABLED_REASON_NOT_DISABLED);
         return removed == null;
     }
 
@@ -320,9 +355,11 @@
      * @return true if it's actually removed because it wasn't pinned, or false if it's still
      * pinned.
      */
-    private boolean disableDynamicWithId(@NonNull String shortcutId) {
+    private boolean disableDynamicWithId(@NonNull String shortcutId, boolean ignoreInvisible,
+            int disabledReason) {
         final ShortcutInfo disabled = deleteOrDisableWithId(
-                shortcutId, /* disable =*/ true, /* overrideImmutable=*/ false);
+                shortcutId, /* disable =*/ true, /* overrideImmutable=*/ false, ignoreInvisible,
+                disabledReason);
         return disabled == null;
     }
 
@@ -331,9 +368,10 @@
      * is pinned, it'll remain as a pinned shortcut but will be disabled.
      */
     public void disableWithId(@NonNull String shortcutId, String disabledMessage,
-            int disabledMessageResId, boolean overrideImmutable) {
+            int disabledMessageResId, boolean overrideImmutable, boolean ignoreInvisible,
+            int disabledReason) {
         final ShortcutInfo disabled = deleteOrDisableWithId(shortcutId, /* disable =*/ true,
-                overrideImmutable);
+                overrideImmutable, ignoreInvisible, disabledReason);
 
         if (disabled != null) {
             if (disabledMessage != null) {
@@ -348,14 +386,18 @@
 
     @Nullable
     private ShortcutInfo deleteOrDisableWithId(@NonNull String shortcutId, boolean disable,
-            boolean overrideImmutable) {
+            boolean overrideImmutable, boolean ignoreInvisible, int disabledReason) {
+        Preconditions.checkState(
+                (disable == (disabledReason != ShortcutInfo.DISABLED_REASON_NOT_DISABLED)),
+                "disable and disabledReason disagree: " + disable + " vs " + disabledReason);
         final ShortcutInfo oldShortcut = mShortcuts.get(shortcutId);
 
-        if (oldShortcut == null || !oldShortcut.isEnabled()) {
+        if (oldShortcut == null || !oldShortcut.isEnabled()
+                && (ignoreInvisible && !oldShortcut.isVisibleToPublisher())) {
             return null; // Doesn't exist or already disabled.
         }
         if (!overrideImmutable) {
-            ensureNotImmutable(oldShortcut);
+            ensureNotImmutable(oldShortcut, /*ignoreInvisible=*/ true);
         }
         if (oldShortcut.isPinned()) {
 
@@ -363,6 +405,10 @@
             oldShortcut.clearFlags(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_MANIFEST);
             if (disable) {
                 oldShortcut.addFlags(ShortcutInfo.FLAG_DISABLED);
+                // Do not overwrite the disabled reason if one is alreay set.
+                if (oldShortcut.getDisabledReason() == ShortcutInfo.DISABLED_REASON_NOT_DISABLED) {
+                    oldShortcut.setDisabledReason(disabledReason);
+                }
             }
             oldShortcut.setTimestamp(mShortcutUser.mService.injectCurrentTimeMillis());
 
@@ -373,7 +419,7 @@
 
             return oldShortcut;
         } else {
-            deleteShortcutInner(shortcutId);
+            forceDeleteShortcutInner(shortcutId);
             return null;
         }
     }
@@ -381,11 +427,25 @@
     public void enableWithId(@NonNull String shortcutId) {
         final ShortcutInfo shortcut = mShortcuts.get(shortcutId);
         if (shortcut != null) {
-            ensureNotImmutable(shortcut);
+            ensureNotImmutable(shortcut, /*ignoreInvisible=*/ true);
             shortcut.clearFlags(ShortcutInfo.FLAG_DISABLED);
+            shortcut.setDisabledReason(ShortcutInfo.DISABLED_REASON_NOT_DISABLED);
         }
     }
 
+    public void updateInvisibleShortcutForPinRequestWith(@NonNull ShortcutInfo shortcut) {
+        final ShortcutInfo source = mShortcuts.get(shortcut.getId());
+        Preconditions.checkNotNull(source);
+
+        mShortcutUser.mService.validateShortcutForPinRequest(shortcut);
+
+        shortcut.addFlags(ShortcutInfo.FLAG_PINNED);
+
+        forceReplaceShortcutInner(shortcut);
+
+        adjustRanks();
+    }
+
     /**
      * Called after a launcher updates the pinned set.  For each shortcut in this package,
      * set FLAG_PINNED if any launcher has pinned it.  Otherwise, clear it.
@@ -510,7 +570,7 @@
      */
     public void findAll(@NonNull List<ShortcutInfo> result,
             @Nullable Predicate<ShortcutInfo> query, int cloneFlag) {
-        findAll(result, query, cloneFlag, null, 0);
+        findAll(result, query, cloneFlag, null, 0, /*getPinnedByAnyLauncher=*/ false);
     }
 
     /**
@@ -522,7 +582,7 @@
      */
     public void findAll(@NonNull List<ShortcutInfo> result,
             @Nullable Predicate<ShortcutInfo> query, int cloneFlag,
-            @Nullable String callingLauncher, int launcherUserId) {
+            @Nullable String callingLauncher, int launcherUserId, boolean getPinnedByAnyLauncher) {
         if (getPackageInfo().isShadow()) {
             // Restored and the app not installed yet, so don't return any.
             return;
@@ -544,9 +604,11 @@
             final boolean isPinnedByCaller = (callingLauncher == null)
                     || ((pinnedByCallerSet != null) && pinnedByCallerSet.contains(si.getId()));
 
-            if (si.isFloating()) {
-                if (!isPinnedByCaller) {
-                    continue;
+            if (!getPinnedByAnyLauncher) {
+                if (si.isFloating()) {
+                    if (!isPinnedByCaller) {
+                        continue;
+                    }
                 }
             }
             final ShortcutInfo clone = si.clone(cloneFlag);
@@ -612,7 +674,8 @@
             }
             checked.add(activity);
 
-            if (!s.injectIsActivityEnabledAndExported(activity, getOwnerUserId())) {
+            if ((activity != null)
+                    && !s.injectIsActivityEnabledAndExported(activity, getOwnerUserId())) {
                 return false;
             }
         }
@@ -693,7 +756,27 @@
                     getPackageInfo().getVersionCode(), pi.versionCode));
         }
 
-        getPackageInfo().updateVersionInfo(pi);
+        getPackageInfo().updateFromPackageInfo(pi);
+        final int newVersionCode = getPackageInfo().getVersionCode();
+
+        // See if there are any shortcuts that were prevented restoring because the app was of a
+        // lower version, and re-enable them.
+        for (int i = mShortcuts.size() - 1; i >= 0; i--) {
+            final ShortcutInfo si = mShortcuts.valueAt(i);
+            if (si.getDisabledReason() != ShortcutInfo.DISABLED_REASON_VERSION_LOWER) {
+                continue;
+            }
+            if (getPackageInfo().getBackupSourceVersionCode() > newVersionCode) {
+                if (ShortcutService.DEBUG) {
+                    Slog.d(TAG, String.format("Shortcut %s require version %s, still not restored.",
+                            si.getId(), getPackageInfo().getBackupSourceVersionCode()));
+                }
+                continue;
+            }
+            Slog.i(TAG, String.format("Restoring shortcut: %s", si.getId()));
+            si.clearFlags(ShortcutInfo.FLAG_DISABLED);
+            si.setDisabledReason(ShortcutInfo.DISABLED_REASON_NOT_DISABLED);
+        }
 
         // For existing shortcuts, update timestamps if they have any resources.
         // Also check if shortcuts' activities are still main activities.  Otherwise, disable them.
@@ -713,7 +796,8 @@
                         Slog.w(TAG, String.format(
                                 "%s is no longer main activity. Disabling shorcut %s.",
                                 getPackageName(), si.getId()));
-                        if (disableDynamicWithId(si.getId())) {
+                        if (disableDynamicWithId(si.getId(), /*ignoreInvisible*/ false,
+                                ShortcutInfo.DISABLED_REASON_APP_CHANGED)) {
                             continue; // Actually removed.
                         }
                         // Still pinned, so fall-through and possibly update the resources.
@@ -809,7 +893,7 @@
 
                 // Note even if enabled=false, we still need to update all fields, so do it
                 // regardless.
-                addShortcutInner(newShortcut); // This will clean up the old one too.
+                forceReplaceShortcutInner(newShortcut); // This will clean up the old one too.
 
                 if (!newDisabled && toDisableList != null) {
                     // Still alive, don't remove.
@@ -831,7 +915,8 @@
                 final String id = toDisableList.valueAt(i);
 
                 disableWithId(id, /* disable message =*/ null, /* disable message resid */ 0,
-                        /* overrideImmutable=*/ true);
+                        /* overrideImmutable=*/ true, /*ignoreInvisible=*/ false,
+                        ShortcutInfo.DISABLED_REASON_APP_CHANGED);
             }
             removeOrphans();
         }
@@ -869,7 +954,7 @@
                     service.wtf("Found manifest shortcuts in excess list.");
                     continue;
                 }
-                deleteDynamicWithId(shortcut.getId());
+                deleteDynamicWithId(shortcut.getId(), /*ignoreInvisible=*/ true);
             }
         }
 
@@ -1075,7 +1160,7 @@
         if (ret != 0) {
             return ret;
         }
-        // If they're stil tie, just sort by their IDs.
+        // If they're still tie, just sort by their IDs.
         // This may happen with updateShortcuts() -- see
         // the testUpdateShortcuts_noManifestShortcuts() test.
         return a.getId().compareTo(b.getId());
@@ -1257,25 +1342,34 @@
         ShortcutService.writeAttr(out, ATTR_NAME, getPackageName());
         ShortcutService.writeAttr(out, ATTR_CALL_COUNT, mApiCallCount);
         ShortcutService.writeAttr(out, ATTR_LAST_RESET, mLastResetTime);
-        getPackageInfo().saveToXml(out);
+        getPackageInfo().saveToXml(out, forBackup);
 
         for (int j = 0; j < size; j++) {
-            saveShortcut(out, mShortcuts.valueAt(j), forBackup);
+            saveShortcut(out, mShortcuts.valueAt(j), forBackup,
+                    getPackageInfo().isBackupAllowed());
         }
 
         out.endTag(null, TAG_ROOT);
     }
 
-    private void saveShortcut(XmlSerializer out, ShortcutInfo si, boolean forBackup)
+    private void saveShortcut(XmlSerializer out, ShortcutInfo si, boolean forBackup,
+            boolean appSupportsBackup)
             throws IOException, XmlPullParserException {
 
         final ShortcutService s = mShortcutUser.mService;
 
         if (forBackup) {
             if (!(si.isPinned() && si.isEnabled())) {
-                return; // We only backup pinned shortcuts that are enabled.
+                // We only backup pinned shortcuts that are enabled.
+                // Note, this means, shortcuts that are restored but are blocked restore, e.g. due
+                // to a lower version code, will not be ported to a new device.
+                return;
             }
         }
+        final boolean shouldBackupDetails =
+                !forBackup // It's not backup
+                || appSupportsBackup; // Or, it's a backup and app supports backup.
+
         // Note: at this point no shortcuts should have bitmaps pending save, but if they do,
         // just remove the bitmap.
         if (si.isIconPendingSave()) {
@@ -1292,20 +1386,31 @@
         ShortcutService.writeAttr(out, ATTR_TEXT, si.getText());
         ShortcutService.writeAttr(out, ATTR_TEXT_RES_ID, si.getTextResId());
         ShortcutService.writeAttr(out, ATTR_TEXT_RES_NAME, si.getTextResName());
-        ShortcutService.writeAttr(out, ATTR_DISABLED_MESSAGE, si.getDisabledMessage());
-        ShortcutService.writeAttr(out, ATTR_DISABLED_MESSAGE_RES_ID,
-                si.getDisabledMessageResourceId());
-        ShortcutService.writeAttr(out, ATTR_DISABLED_MESSAGE_RES_NAME,
-                si.getDisabledMessageResName());
+        if (shouldBackupDetails) {
+            ShortcutService.writeAttr(out, ATTR_DISABLED_MESSAGE, si.getDisabledMessage());
+            ShortcutService.writeAttr(out, ATTR_DISABLED_MESSAGE_RES_ID,
+                    si.getDisabledMessageResourceId());
+            ShortcutService.writeAttr(out, ATTR_DISABLED_MESSAGE_RES_NAME,
+                    si.getDisabledMessageResName());
+        }
+        ShortcutService.writeAttr(out, ATTR_DISABLED_REASON, si.getDisabledReason());
         ShortcutService.writeAttr(out, ATTR_TIMESTAMP,
                 si.getLastChangedTimestamp());
         if (forBackup) {
             // Don't write icon information.  Also drop the dynamic flag.
-            ShortcutService.writeAttr(out, ATTR_FLAGS,
-                    si.getFlags() &
-                            ~(ShortcutInfo.FLAG_HAS_ICON_FILE | ShortcutInfo.FLAG_HAS_ICON_RES
+
+            int flags = si.getFlags() &
+                    ~(ShortcutInfo.FLAG_HAS_ICON_FILE | ShortcutInfo.FLAG_HAS_ICON_RES
                             | ShortcutInfo.FLAG_ICON_FILE_PENDING_SAVE
-                            | ShortcutInfo.FLAG_DYNAMIC));
+                            | ShortcutInfo.FLAG_DYNAMIC);
+            ShortcutService.writeAttr(out, ATTR_FLAGS, flags);
+
+            // Set the publisher version code at every backup.
+            final int packageVersionCode = getPackageInfo().getVersionCode();
+            if (packageVersionCode == 0) {
+                s.wtf("Package version code should be available at this point.");
+                // However, 0 is a valid version code, so we just go ahead with it...
+            }
         } else {
             // When writing for backup, ranks shouldn't be saved, since shortcuts won't be restored
             // as dynamic.
@@ -1317,26 +1422,28 @@
             ShortcutService.writeAttr(out, ATTR_BITMAP_PATH, si.getBitmapPath());
         }
 
-        {
-            final Set<String> cat = si.getCategories();
-            if (cat != null && cat.size() > 0) {
-                out.startTag(null, TAG_CATEGORIES);
-                XmlUtils.writeStringArrayXml(cat.toArray(new String[cat.size()]),
-                        NAME_CATEGORIES, out);
-                out.endTag(null, TAG_CATEGORIES);
+        if (shouldBackupDetails) {
+            {
+                final Set<String> cat = si.getCategories();
+                if (cat != null && cat.size() > 0) {
+                    out.startTag(null, TAG_CATEGORIES);
+                    XmlUtils.writeStringArrayXml(cat.toArray(new String[cat.size()]),
+                            NAME_CATEGORIES, out);
+                    out.endTag(null, TAG_CATEGORIES);
+                }
             }
-        }
-        final Intent[] intentsNoExtras = si.getIntentsNoExtras();
-        final PersistableBundle[] intentsExtras = si.getIntentPersistableExtrases();
-        final int numIntents = intentsNoExtras.length;
-        for (int i = 0; i < numIntents; i++) {
-            out.startTag(null, TAG_INTENT);
-            ShortcutService.writeAttr(out, ATTR_INTENT_NO_EXTRA, intentsNoExtras[i]);
-            ShortcutService.writeTagExtra(out, TAG_EXTRAS, intentsExtras[i]);
-            out.endTag(null, TAG_INTENT);
-        }
+            final Intent[] intentsNoExtras = si.getIntentsNoExtras();
+            final PersistableBundle[] intentsExtras = si.getIntentPersistableExtrases();
+            final int numIntents = intentsNoExtras.length;
+            for (int i = 0; i < numIntents; i++) {
+                out.startTag(null, TAG_INTENT);
+                ShortcutService.writeAttr(out, ATTR_INTENT_NO_EXTRA, intentsNoExtras[i]);
+                ShortcutService.writeTagExtra(out, TAG_EXTRAS, intentsExtras[i]);
+                out.endTag(null, TAG_INTENT);
+            }
 
-        ShortcutService.writeTagExtra(out, TAG_EXTRAS, si.getExtras());
+            ShortcutService.writeTagExtra(out, TAG_EXTRAS, si.getExtras());
+        }
 
         out.endTag(null, TAG_SHORTCUT);
     }
@@ -1356,6 +1463,7 @@
         ret.mLastResetTime =
                 ShortcutService.parseLongAttribute(parser, ATTR_LAST_RESET);
 
+
         final int outerDepth = parser.getDepth();
         int type;
         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
@@ -1369,10 +1477,11 @@
                 switch (tag) {
                     case ShortcutPackageInfo.TAG_ROOT:
                         ret.getPackageInfo().loadFromXml(parser, fromBackup);
+
                         continue;
                     case TAG_SHORTCUT:
                         final ShortcutInfo si = parseShortcut(parser, packageName,
-                                shortcutUser.getUserId());
+                                shortcutUser.getUserId(), fromBackup);
 
                         // Don't use addShortcut(), we don't need to save the icon.
                         ret.mShortcuts.put(si.getId(), si);
@@ -1385,7 +1494,8 @@
     }
 
     private static ShortcutInfo parseShortcut(XmlPullParser parser, String packageName,
-            @UserIdInt int userId) throws IOException, XmlPullParserException {
+            @UserIdInt int userId, boolean fromBackup)
+            throws IOException, XmlPullParserException {
         String id;
         ComponentName activityComponent;
         // Icon icon;
@@ -1398,6 +1508,7 @@
         String disabledMessage;
         int disabledMessageResId;
         String disabledMessageResName;
+        int disabledReason;
         Intent intentLegacy;
         PersistableBundle intentPersistableExtrasLegacy = null;
         ArrayList<Intent> intents = new ArrayList<>();
@@ -1408,6 +1519,7 @@
         int iconResId;
         String iconResName;
         String bitmapPath;
+        int backupVersionCode;
         ArraySet<String> categories = null;
 
         id = ShortcutService.parseStringAttribute(parser, ATTR_ID);
@@ -1424,6 +1536,7 @@
                 ATTR_DISABLED_MESSAGE_RES_ID);
         disabledMessageResName = ShortcutService.parseStringAttribute(parser,
                 ATTR_DISABLED_MESSAGE_RES_NAME);
+        disabledReason = ShortcutService.parseIntAttribute(parser, ATTR_DISABLED_REASON);
         intentLegacy = ShortcutService.parseIntentAttributeNoDefault(parser, ATTR_INTENT_LEGACY);
         rank = (int) ShortcutService.parseLongAttribute(parser, ATTR_RANK);
         lastChangedTimestamp = ShortcutService.parseLongAttribute(parser, ATTR_TIMESTAMP);
@@ -1480,6 +1593,19 @@
             intents.add(intentLegacy);
         }
 
+
+        if ((disabledReason == ShortcutInfo.DISABLED_REASON_NOT_DISABLED)
+                && ((flags & ShortcutInfo.FLAG_DISABLED) != 0)) {
+            // We didn't used to have the disabled reason, so if a shortcut is disabled
+            // and has no reason, we assume it was disabled by publisher.
+            disabledReason = ShortcutInfo.DISABLED_REASON_BY_APP;
+        }
+
+        // All restored shortcuts are initially "shadow".
+        if (fromBackup) {
+            flags |= ShortcutInfo.FLAG_SHADOW;
+        }
+
         return new ShortcutInfo(
                 userId, id, packageName, activityComponent, /* icon =*/ null,
                 title, titleResId, titleResName, text, textResId, textResName,
@@ -1487,7 +1613,7 @@
                 categories,
                 intents.toArray(new Intent[intents.size()]),
                 rank, extras, lastChangedTimestamp, flags,
-                iconResId, iconResName, bitmapPath);
+                iconResId, iconResName, bitmapPath, disabledReason);
     }
 
     private static Intent parseIntent(XmlPullParser parser)
@@ -1602,6 +1728,20 @@
                 Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
                         + " has both resource and bitmap icons");
             }
+            if (si.isEnabled()
+                    != (si.getDisabledReason() == ShortcutInfo.DISABLED_REASON_NOT_DISABLED)) {
+                failed = true;
+                Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
+                        + " isEnabled() and getDisabledReason() disagree: "
+                        + si.isEnabled() + " vs " + si.getDisabledReason());
+            }
+            if ((si.getDisabledReason() == ShortcutInfo.DISABLED_REASON_VERSION_LOWER)
+                    && (getPackageInfo().getBackupSourceVersionCode()
+                    == ShortcutInfo.VERSION_CODE_UNKNOWN)) {
+                failed = true;
+                Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
+                        + " RESTORED_VERSION_LOWER with no backup source version code.");
+            }
             if (s.isDummyMainActivity(si.getActivity())) {
                 failed = true;
                 Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
diff --git a/com/android/server/pm/ShortcutPackageInfo.java b/com/android/server/pm/ShortcutPackageInfo.java
index e5a2f5a..3a9bbc8 100644
--- a/com/android/server/pm/ShortcutPackageInfo.java
+++ b/com/android/server/pm/ShortcutPackageInfo.java
@@ -18,6 +18,7 @@
 import android.annotation.NonNull;
 import android.annotation.UserIdInt;
 import android.content.pm.PackageInfo;
+import android.content.pm.ShortcutInfo;
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -45,32 +46,45 @@
     static final String TAG_ROOT = "package-info";
     private static final String ATTR_VERSION = "version";
     private static final String ATTR_LAST_UPDATE_TIME = "last_udpate_time";
+    private static final String ATTR_BACKUP_SOURCE_VERSION = "bk_src_version";
+    private static final String ATTR_BACKUP_ALLOWED = "allow-backup";
+    private static final String ATTR_BACKUP_SOURCE_BACKUP_ALLOWED = "bk_src_backup-allowed";
     private static final String ATTR_SHADOW = "shadow";
 
     private static final String TAG_SIGNATURE = "signature";
     private static final String ATTR_SIGNATURE_HASH = "hash";
 
-    private static final int VERSION_UNKNOWN = -1;
-
     /**
      * When true, this package information was restored from the previous device, and the app hasn't
      * been installed yet.
      */
     private boolean mIsShadow;
-    private int mVersionCode = VERSION_UNKNOWN;
+    private int mVersionCode = ShortcutInfo.VERSION_CODE_UNKNOWN;
+    private int mBackupSourceVersionCode = ShortcutInfo.VERSION_CODE_UNKNOWN;
     private long mLastUpdateTime;
     private ArrayList<byte[]> mSigHashes;
 
+    // mBackupAllowed didn't used to be parsisted, so we don't restore it from a file.
+    // mBackupAllowed will always start with false, and will have been updated before making a
+    // backup next time, which works file.
+    // We just don't want to print an uninitialzied mBackupAlldowed value on dumpsys, so
+    // we use this boolean to control dumpsys.
+    private boolean mBackupAllowedInitialized;
+    private boolean mBackupAllowed;
+    private boolean mBackupSourceBackupAllowed;
+
     private ShortcutPackageInfo(int versionCode, long lastUpdateTime,
             ArrayList<byte[]> sigHashes, boolean isShadow) {
         mVersionCode = versionCode;
         mLastUpdateTime = lastUpdateTime;
         mIsShadow = isShadow;
         mSigHashes = sigHashes;
+        mBackupAllowed = false; // By default, we assume false.
+        mBackupSourceBackupAllowed = false;
     }
 
     public static ShortcutPackageInfo newEmpty() {
-        return new ShortcutPackageInfo(VERSION_UNKNOWN, /* last update time =*/ 0,
+        return new ShortcutPackageInfo(ShortcutInfo.VERSION_CODE_UNKNOWN, /* last update time =*/ 0,
                 new ArrayList<>(0), /* isShadow */ false);
     }
 
@@ -86,15 +100,33 @@
         return mVersionCode;
     }
 
+    public int getBackupSourceVersionCode() {
+        return mBackupSourceVersionCode;
+    }
+
+    @VisibleForTesting
+    public boolean isBackupSourceBackupAllowed() {
+        return mBackupSourceBackupAllowed;
+    }
+
     public long getLastUpdateTime() {
         return mLastUpdateTime;
     }
 
-    /** Set {@link #mVersionCode} and {@link #mLastUpdateTime} from a {@link PackageInfo}. */
-    public void updateVersionInfo(@NonNull PackageInfo pi) {
+    public boolean isBackupAllowed() {
+        return mBackupAllowed;
+    }
+
+    /**
+     * Set {@link #mVersionCode}, {@link #mLastUpdateTime} and {@link #mBackupAllowed}
+     * from a {@link PackageInfo}.
+     */
+    public void updateFromPackageInfo(@NonNull PackageInfo pi) {
         if (pi != null) {
             mVersionCode = pi.versionCode;
             mLastUpdateTime = pi.lastUpdateTime;
+            mBackupAllowed = ShortcutService.shouldBackupApp(pi);
+            mBackupAllowedInitialized = true;
         }
     }
 
@@ -102,23 +134,24 @@
         return mSigHashes.size() > 0;
     }
 
-    public boolean canRestoreTo(ShortcutService s, PackageInfo target) {
-        if (!s.shouldBackupApp(target)) {
-            // "allowBackup" was true when backed up, but now false.
-            Slog.w(TAG, "Can't restore: package no longer allows backup");
-            return false;
+    //@DisabledReason
+    public int canRestoreTo(ShortcutService s, PackageInfo currentPackage, boolean anyVersionOkay) {
+        if (!BackupUtils.signaturesMatch(mSigHashes, currentPackage)) {
+            Slog.w(TAG, "Can't restore: Package signature mismatch");
+            return ShortcutInfo.DISABLED_REASON_SIGNATURE_MISMATCH;
         }
-        if (target.versionCode < mVersionCode) {
+        if (!ShortcutService.shouldBackupApp(currentPackage) || !mBackupSourceBackupAllowed) {
+            // "allowBackup" was true when backed up, but now false.
+            Slog.w(TAG, "Can't restore: package didn't or doesn't allow backup");
+            return ShortcutInfo.DISABLED_REASON_BACKUP_NOT_SUPPORTED;
+        }
+        if (!anyVersionOkay && (currentPackage.versionCode < mBackupSourceVersionCode)) {
             Slog.w(TAG, String.format(
                     "Can't restore: package current version %d < backed up version %d",
-                    target.versionCode, mVersionCode));
-            return false;
+                    currentPackage.versionCode, mBackupSourceVersionCode));
+            return ShortcutInfo.DISABLED_REASON_VERSION_LOWER;
         }
-        if (!BackupUtils.signaturesMatch(mSigHashes, target)) {
-            Slog.w(TAG, "Can't restore: Package signature mismatch");
-            return false;
-        }
-        return true;
+        return ShortcutInfo.DISABLED_REASON_NOT_DISABLED;
     }
 
     @VisibleForTesting
@@ -132,6 +165,8 @@
         final ShortcutPackageInfo ret = new ShortcutPackageInfo(pi.versionCode, pi.lastUpdateTime,
                 BackupUtils.hashSignatureArray(pi.signatures), /* shadow=*/ false);
 
+        ret.mBackupSourceBackupAllowed = s.shouldBackupApp(pi);
+        ret.mBackupSourceVersionCode = pi.versionCode;
         return ret;
     }
 
@@ -151,13 +186,19 @@
         mSigHashes = BackupUtils.hashSignatureArray(pi.signatures);
     }
 
-    public void saveToXml(XmlSerializer out) throws IOException {
+    public void saveToXml(XmlSerializer out, boolean forBackup) throws IOException {
 
         out.startTag(null, TAG_ROOT);
 
         ShortcutService.writeAttr(out, ATTR_VERSION, mVersionCode);
         ShortcutService.writeAttr(out, ATTR_LAST_UPDATE_TIME, mLastUpdateTime);
         ShortcutService.writeAttr(out, ATTR_SHADOW, mIsShadow);
+        ShortcutService.writeAttr(out, ATTR_BACKUP_ALLOWED, mBackupAllowed);
+
+        ShortcutService.writeAttr(out, ATTR_BACKUP_SOURCE_VERSION, mBackupSourceVersionCode);
+        ShortcutService.writeAttr(out,
+                ATTR_BACKUP_SOURCE_BACKUP_ALLOWED, mBackupSourceBackupAllowed);
+
 
         for (int i = 0; i < mSigHashes.size(); i++) {
             out.startTag(null, TAG_SIGNATURE);
@@ -171,7 +212,9 @@
     public void loadFromXml(XmlPullParser parser, boolean fromBackup)
             throws IOException, XmlPullParserException {
 
-        final int versionCode = ShortcutService.parseIntAttribute(parser, ATTR_VERSION);
+        // Don't use the version code from the backup file.
+        final int versionCode = ShortcutService.parseIntAttribute(parser, ATTR_VERSION,
+                ShortcutInfo.VERSION_CODE_UNKNOWN);
 
         final long lastUpdateTime = ShortcutService.parseLongAttribute(
                 parser, ATTR_LAST_UPDATE_TIME);
@@ -180,6 +223,20 @@
         final boolean shadow =
                 fromBackup || ShortcutService.parseBooleanAttribute(parser, ATTR_SHADOW);
 
+        // We didn't used to save these attributes, and all backed up shortcuts were from
+        // apps that support backups, so the default values take this fact into consideration.
+        final int backupSourceVersion = ShortcutService.parseIntAttribute(parser,
+                ATTR_BACKUP_SOURCE_VERSION, ShortcutInfo.VERSION_CODE_UNKNOWN);
+
+        // Note the only time these "true" default value is used is when restoring from an old
+        // build that didn't save ATTR_BACKUP_ALLOWED, and that means all the data included in
+        // a backup file were from apps that support backup, so we can just use "true" as the
+        // default.
+        final boolean backupAllowed = ShortcutService.parseBooleanAttribute(
+                parser, ATTR_BACKUP_ALLOWED, true);
+        final boolean backupSourceBackupAllowed = ShortcutService.parseBooleanAttribute(
+                parser, ATTR_BACKUP_SOURCE_BACKUP_ALLOWED, true);
+
         final ArrayList<byte[]> hashes = new ArrayList<>();
 
         final int outerDepth = parser.getDepth();
@@ -207,11 +264,28 @@
             ShortcutService.warnForInvalidTag(depth, tag);
         }
 
-        // Successfully loaded; replace the feilds.
-        mVersionCode = versionCode;
+        // Successfully loaded; replace the fields.
+        if (fromBackup) {
+            mVersionCode = ShortcutInfo.VERSION_CODE_UNKNOWN;
+            mBackupSourceVersionCode = versionCode;
+            mBackupSourceBackupAllowed = backupAllowed;
+        } else {
+            mVersionCode = versionCode;
+            mBackupSourceVersionCode = backupSourceVersion;
+            mBackupSourceBackupAllowed = backupSourceBackupAllowed;
+        }
         mLastUpdateTime = lastUpdateTime;
         mIsShadow = shadow;
         mSigHashes = hashes;
+
+        // Note we don't restore it from the file because it didn't used to be saved.
+        // We always start by assuming backup is disabled for the current package,
+        // and this field will have been updated before we actually create a backup, at the same
+        // time when we update the version code.
+        // Until then, the value of mBackupAllowed shouldn't matter, but we don't want to print
+        // a false flag on dumpsys, so set mBackupAllowedInitialized to false.
+        mBackupAllowed = false;
+        mBackupAllowedInitialized = false;
     }
 
     public void dump(PrintWriter pw, String prefix) {
@@ -223,6 +297,7 @@
         pw.print(prefix);
         pw.print("  IsShadow: ");
         pw.print(mIsShadow);
+        pw.print(mIsShadow ? " (not installed)" : " (installed)");
         pw.println();
 
         pw.print(prefix);
@@ -230,6 +305,25 @@
         pw.print(mVersionCode);
         pw.println();
 
+        if (mBackupAllowedInitialized) {
+            pw.print(prefix);
+            pw.print("  Backup Allowed: ");
+            pw.print(mBackupAllowed);
+            pw.println();
+        }
+
+        if (mBackupSourceVersionCode != ShortcutInfo.VERSION_CODE_UNKNOWN) {
+            pw.print(prefix);
+            pw.print("  Backup source version: ");
+            pw.print(mBackupSourceVersionCode);
+            pw.println();
+
+            pw.print(prefix);
+            pw.print("  Backup source backup allowed: ");
+            pw.print(mBackupSourceBackupAllowed);
+            pw.println();
+        }
+
         pw.print(prefix);
         pw.print("  Last package update time: ");
         pw.print(mLastUpdateTime);
diff --git a/com/android/server/pm/ShortcutPackageItem.java b/com/android/server/pm/ShortcutPackageItem.java
index e59d69f..689099c 100644
--- a/com/android/server/pm/ShortcutPackageItem.java
+++ b/com/android/server/pm/ShortcutPackageItem.java
@@ -17,6 +17,7 @@
 
 import android.annotation.NonNull;
 import android.content.pm.PackageInfo;
+import android.content.pm.ShortcutInfo;
 import android.util.Slog;
 
 import com.android.internal.util.Preconditions;
@@ -101,51 +102,42 @@
         final ShortcutService s = mShortcutUser.mService;
         if (!s.isPackageInstalled(mPackageName, mPackageUserId)) {
             if (ShortcutService.DEBUG) {
-                Slog.d(TAG, String.format("Package still not installed: %s user=%d",
+                Slog.d(TAG, String.format("Package still not installed: %s/u%d",
                         mPackageName, mPackageUserId));
             }
             return; // Not installed, no need to restore yet.
         }
-        boolean blockRestore = false;
-        if (!mPackageInfo.hasSignatures()) {
-            s.wtf("Attempted to restore package " + mPackageName + ", user=" + mPackageUserId
-                    + " but signatures not found in the restore data.");
-            blockRestore = true;
-        }
-        if (!blockRestore) {
-            final PackageInfo pi = s.getPackageInfoWithSignatures(mPackageName, mPackageUserId);
-            if (!mPackageInfo.canRestoreTo(s, pi)) {
-                // Package is now installed, but can't restore.  Let the subclass do the cleanup.
-                blockRestore = true;
-            }
-        }
-        if (blockRestore) {
-            onRestoreBlocked();
-        } else {
-            if (ShortcutService.DEBUG) {
-                Slog.d(TAG, String.format("Restored package: %s/%d on user %d", mPackageName,
-                        mPackageUserId, getOwnerUserId()));
-            }
+        int restoreBlockReason;
+        int currentVersionCode = ShortcutInfo.VERSION_CODE_UNKNOWN;
 
-            onRestored();
+        if (!mPackageInfo.hasSignatures()) {
+            s.wtf("Attempted to restore package " + mPackageName + "/u" + mPackageUserId
+                    + " but signatures not found in the restore data.");
+            restoreBlockReason = ShortcutInfo.DISABLED_REASON_SIGNATURE_MISMATCH;
+        } else {
+            final PackageInfo pi = s.getPackageInfoWithSignatures(mPackageName, mPackageUserId);
+            currentVersionCode = pi.versionCode;
+            restoreBlockReason = mPackageInfo.canRestoreTo(s, pi, canRestoreAnyVersion());
         }
 
+        if (ShortcutService.DEBUG) {
+            Slog.d(TAG, String.format("Restoring package: %s/u%d (version=%d) %s for u%d",
+                    mPackageName, mPackageUserId, currentVersionCode,
+                    ShortcutInfo.getDisabledReasonDebugString(restoreBlockReason),
+                    getOwnerUserId()));
+        }
+
+        onRestored(restoreBlockReason);
+
         // Either way, it's no longer a shadow.
         mPackageInfo.setShadow(false);
 
         s.scheduleSaveUser(mPackageUserId);
     }
 
-    /**
-     * Called when the new package can't be restored because it has a lower version number
-     * or different signatures.
-     */
-    protected abstract void onRestoreBlocked();
+    protected abstract boolean canRestoreAnyVersion();
 
-    /**
-     * Called when the new package is successfully restored.
-     */
-    protected abstract void onRestored();
+    protected abstract void onRestored(int restoreBlockReason);
 
     public abstract void saveToXml(@NonNull XmlSerializer out, boolean forBackup)
             throws IOException, XmlPullParserException;
diff --git a/com/android/server/pm/ShortcutParser.java b/com/android/server/pm/ShortcutParser.java
index 3cf4200..866c46c 100644
--- a/com/android/server/pm/ShortcutParser.java
+++ b/com/android/server/pm/ShortcutParser.java
@@ -337,6 +337,9 @@
                 (enabled ? ShortcutInfo.FLAG_MANIFEST : ShortcutInfo.FLAG_DISABLED)
                 | ShortcutInfo.FLAG_IMMUTABLE
                 | ((iconResId != 0) ? ShortcutInfo.FLAG_HAS_ICON_RES : 0);
+        final int disabledReason =
+                enabled ? ShortcutInfo.DISABLED_REASON_NOT_DISABLED
+                        : ShortcutInfo.DISABLED_REASON_BY_APP;
 
         // Note we don't need to set resource names here yet.  They'll be set when they're about
         // to be published.
@@ -363,6 +366,7 @@
                 flags,
                 iconResId,
                 null, // icon res name
-                null); // bitmap path
+                null, // bitmap path
+                disabledReason);
     }
 }
diff --git a/com/android/server/pm/ShortcutRequestPinProcessor.java b/com/android/server/pm/ShortcutRequestPinProcessor.java
index 8a8128d..3e44de9 100644
--- a/com/android/server/pm/ShortcutRequestPinProcessor.java
+++ b/com/android/server/pm/ShortcutRequestPinProcessor.java
@@ -300,10 +300,12 @@
 
         final ShortcutInfo existing = ps.findShortcutById(inShortcut.getId());
         final boolean existsAlready = existing != null;
+        final boolean existingIsVisible = existsAlready && existing.isVisibleToPublisher();
 
         if (DEBUG) {
             Slog.d(TAG, "requestPinnedShortcut: package=" + inShortcut.getPackage()
                     + " existsAlready=" + existsAlready
+                    + " existingIsVisible=" + existingIsVisible
                     + " shortcut=" + inShortcut.toInsecureString());
         }
 
@@ -378,7 +380,6 @@
         // manifest shortcut.)
         Preconditions.checkArgument(shortcutInfo.isEnabled(),
                 "Shortcut ID=" + shortcutInfo + " already exists but disabled.");
-
     }
 
     private boolean startRequestConfirmActivity(ComponentName activity, int launcherUserId,
@@ -463,7 +464,7 @@
             launcher.attemptToRestoreIfNeededAndSave();
             if (launcher.hasPinned(original)) {
                 if (DEBUG) {
-                    Slog.d(TAG, "Shortcut " + original + " already pinned.");
+                    Slog.d(TAG, "Shortcut " + original + " already pinned.");                       // This too.
                 }
                 return true;
             }
@@ -497,7 +498,7 @@
                 if (original.getActivity() == null) {
                     original.setActivity(mService.getDummyMainActivity(appPackageName));
                 }
-                ps.addOrUpdateDynamicShortcut(original);
+                ps.addOrReplaceDynamicShortcut(original);
             }
 
             // Pin the shortcut.
@@ -505,13 +506,14 @@
                 Slog.d(TAG, "Pinning " + shortcutId);
             }
 
-            launcher.addPinnedShortcut(appPackageName, appUserId, shortcutId);
+            launcher.addPinnedShortcut(appPackageName, appUserId, shortcutId,
+                    /*forPinRequest=*/ true);
 
             if (current == null) {
                 if (DEBUG) {
                     Slog.d(TAG, "Removing " + shortcutId + " as dynamic");
                 }
-                ps.deleteDynamicWithId(shortcutId);
+                ps.deleteDynamicWithId(shortcutId, /*ignoreInvisible=*/ false);
             }
 
             ps.adjustRanks(); // Shouldn't be needed, but just in case.
diff --git a/com/android/server/pm/ShortcutService.java b/com/android/server/pm/ShortcutService.java
index 27560c5..1c002aa 100644
--- a/com/android/server/pm/ShortcutService.java
+++ b/com/android/server/pm/ShortcutService.java
@@ -553,6 +553,9 @@
 
         public Lifecycle(Context context) {
             super(context);
+            if (DEBUG) {
+                Binder.LOG_RUNTIME_EXCEPTION = true;
+            }
             mService = new ShortcutService(context);
         }
 
@@ -738,6 +741,10 @@
         return parseLongAttribute(parser, attribute) == 1;
     }
 
+    static boolean parseBooleanAttribute(XmlPullParser parser, String attribute, boolean def) {
+        return parseLongAttribute(parser, attribute, (def ? 1 : 0)) == 1;
+    }
+
     static int parseIntAttribute(XmlPullParser parser, String attribute) {
         return (int) parseLongAttribute(parser, attribute);
     }
@@ -835,6 +842,8 @@
     static void writeAttr(XmlSerializer out, String name, boolean value) throws IOException {
         if (value) {
             writeAttr(out, name, "1");
+        } else {
+            writeAttr(out, name, "0");
         }
     }
 
@@ -1689,7 +1698,7 @@
 
             final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId);
 
-            ps.ensureImmutableShortcutsNotIncluded(newShortcuts);
+            ps.ensureImmutableShortcutsNotIncluded(newShortcuts, /*ignoreInvisible=*/ true);
 
             fillInDefaultActivity(newShortcuts);
 
@@ -1709,12 +1718,12 @@
             }
 
             // First, remove all un-pinned; dynamic shortcuts
-            ps.deleteAllDynamicShortcuts();
+            ps.deleteAllDynamicShortcuts(/*ignoreInvisible=*/ true);
 
             // Then, add/update all.  We need to make sure to take over "pinned" flag.
             for (int i = 0; i < size; i++) {
                 final ShortcutInfo newShortcut = newShortcuts.get(i);
-                ps.addOrUpdateDynamicShortcut(newShortcut);
+                ps.addOrReplaceDynamicShortcut(newShortcut);
             }
 
             // Lastly, adjust the ranks.
@@ -1740,7 +1749,7 @@
 
             final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId);
 
-            ps.ensureImmutableShortcutsNotIncluded(newShortcuts);
+            ps.ensureImmutableShortcutsNotIncluded(newShortcuts, /*ignoreInvisible=*/ true);
 
             // For update, don't fill in the default activity.  Having null activity means
             // "don't update the activity" here.
@@ -1761,7 +1770,9 @@
                 fixUpIncomingShortcutInfo(source, /* forUpdate= */ true);
 
                 final ShortcutInfo target = ps.findShortcutById(source.getId());
-                if (target == null) {
+
+                // Invisible shortcuts can't be updated.
+                if (target == null || !target.isVisibleToPublisher()) {
                     continue;
                 }
 
@@ -1808,7 +1819,7 @@
     }
 
     @Override
-    public boolean addDynamicShortcuts(String packageName, ParceledListSlice shortcutInfoList,
+    public boolean  addDynamicShortcuts(String packageName, ParceledListSlice shortcutInfoList,
             @UserIdInt int userId) {
         verifyCaller(packageName, userId);
 
@@ -1820,7 +1831,7 @@
 
             final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId);
 
-            ps.ensureImmutableShortcutsNotIncluded(newShortcuts);
+            ps.ensureImmutableShortcutsNotIncluded(newShortcuts, /*ignoreInvisible=*/ true);
 
             fillInDefaultActivity(newShortcuts);
 
@@ -1845,7 +1856,7 @@
                 newShortcut.setRankChanged();
 
                 // Add it.
-                ps.addOrUpdateDynamicShortcut(newShortcut);
+                ps.addOrReplaceDynamicShortcut(newShortcut);
             }
 
             // Lastly, adjust the ranks.
@@ -1901,6 +1912,22 @@
             Preconditions.checkState(isUidForegroundLocked(injectBinderCallingUid()),
                     "Calling application must have a foreground activity or a foreground service");
 
+            // If it's a pin shortcut request, and there's already a shortcut with the same ID
+            // that's not visible to the caller (i.e. restore-blocked; meaning it's pinned by
+            // someone already), then we just replace the existing one with this new one,
+            // and then proceed the rest of the process.
+            if (shortcut != null) {
+                final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(
+                        packageName, userId);
+                final String id = shortcut.getId();
+                if (ps.isShortcutExistsAndInvisibleToPublisher(id)) {
+
+                    ps.updateInvisibleShortcutForPinRequestWith(shortcut);
+
+                    packageShortcutsChanged(packageName, userId);
+                }
+            }
+
             // Send request to the launcher, if supported.
             ret = mShortcutRequestPinProcessor.requestPinItemLocked(shortcut, appWidget, extras,
                     userId, resultIntent);
@@ -1922,15 +1949,21 @@
 
             final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId);
 
-            ps.ensureImmutableShortcutsNotIncludedWithIds((List<String>) shortcutIds);
+            ps.ensureImmutableShortcutsNotIncludedWithIds((List<String>) shortcutIds,
+                    /*ignoreInvisible=*/ true);
 
             final String disabledMessageString =
                     (disabledMessage == null) ? null : disabledMessage.toString();
 
             for (int i = shortcutIds.size() - 1; i >= 0; i--) {
-                ps.disableWithId(Preconditions.checkStringNotEmpty((String) shortcutIds.get(i)),
+                final String id = Preconditions.checkStringNotEmpty((String) shortcutIds.get(i));
+                if (!ps.isShortcutExistsAndVisibleToPublisher(id)) {
+                    continue;
+                }
+                ps.disableWithId(id,
                         disabledMessageString, disabledMessageResId,
-                        /* overrideImmutable=*/ false);
+                        /* overrideImmutable=*/ false, /*ignoreInvisible=*/ true,
+                        ShortcutInfo.DISABLED_REASON_BY_APP);
             }
 
             // We may have removed dynamic shortcuts which may have left a gap, so adjust the ranks.
@@ -1951,10 +1984,15 @@
 
             final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId);
 
-            ps.ensureImmutableShortcutsNotIncludedWithIds((List<String>) shortcutIds);
+            ps.ensureImmutableShortcutsNotIncludedWithIds((List<String>) shortcutIds,
+                    /*ignoreInvisible=*/ true);
 
             for (int i = shortcutIds.size() - 1; i >= 0; i--) {
-                ps.enableWithId((String) shortcutIds.get(i));
+                final String id = Preconditions.checkStringNotEmpty((String) shortcutIds.get(i));
+                if (!ps.isShortcutExistsAndVisibleToPublisher(id)) {
+                    continue;
+                }
+                ps.enableWithId(id);
             }
         }
         packageShortcutsChanged(packageName, userId);
@@ -1973,11 +2011,15 @@
 
             final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId);
 
-            ps.ensureImmutableShortcutsNotIncludedWithIds((List<String>) shortcutIds);
+            ps.ensureImmutableShortcutsNotIncludedWithIds((List<String>) shortcutIds,
+                    /*ignoreInvisible=*/ true);
 
             for (int i = shortcutIds.size() - 1; i >= 0; i--) {
-                ps.deleteDynamicWithId(
-                        Preconditions.checkStringNotEmpty((String) shortcutIds.get(i)));
+                final String id = Preconditions.checkStringNotEmpty((String) shortcutIds.get(i));
+                if (!ps.isShortcutExistsAndVisibleToPublisher(id)) {
+                    continue;
+                }
+                ps.deleteDynamicWithId(id, /*ignoreInvisible=*/ true);
             }
 
             // We may have removed dynamic shortcuts which may have left a gap, so adjust the ranks.
@@ -1996,7 +2038,7 @@
             throwIfUserLockedL(userId);
 
             final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId);
-            ps.deleteAllDynamicShortcuts();
+            ps.deleteAllDynamicShortcuts(/*ignoreInvisible=*/ true);
         }
         packageShortcutsChanged(packageName, userId);
 
@@ -2013,7 +2055,7 @@
 
             return getShortcutsWithQueryLocked(
                     packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR,
-                    ShortcutInfo::isDynamic);
+                    ShortcutInfo::isDynamicVisible);
         }
     }
 
@@ -2027,7 +2069,7 @@
 
             return getShortcutsWithQueryLocked(
                     packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR,
-                    ShortcutInfo::isManifestShortcut);
+                    ShortcutInfo::isManifestVisible);
         }
     }
 
@@ -2041,7 +2083,7 @@
 
             return getShortcutsWithQueryLocked(
                     packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR,
-                    ShortcutInfo::isPinned);
+                    ShortcutInfo::isPinnedVisible);
         }
     }
 
@@ -2190,7 +2232,11 @@
     }
 
     // We override this method in unit tests to do a simpler check.
-    boolean hasShortcutHostPermission(@NonNull String callingPackage, int userId) {
+    boolean hasShortcutHostPermission(@NonNull String callingPackage, int userId,
+            int callingPid, int callingUid) {
+        if (injectCheckAccessShortcutsPermission(callingPid, callingUid)) {
+            return true;
+        }
         final long start = injectElapsedRealtime();
         try {
             return hasShortcutHostPermissionInner(callingPackage, userId);
@@ -2199,6 +2245,14 @@
         }
     }
 
+    /**
+     * Returns true if the caller has the "ACCESS_SHORTCUTS" permission.
+     */
+    boolean injectCheckAccessShortcutsPermission(int callingPid, int callingUid) {
+        return mContext.checkPermission(android.Manifest.permission.ACCESS_SHORTCUTS,
+                callingPid, callingUid) == PackageManager.PERMISSION_GRANTED;
+    }
+
     // This method is extracted so we can directly call this method from unit tests,
     // even when hasShortcutPermission() is overridden.
     @VisibleForTesting
@@ -2379,7 +2433,7 @@
                 @NonNull String callingPackage, long changedSince,
                 @Nullable String packageName, @Nullable List<String> shortcutIds,
                 @Nullable ComponentName componentName,
-                int queryFlags, int userId) {
+                int queryFlags, int userId, int callingPid, int callingUid) {
             final ArrayList<ShortcutInfo> ret = new ArrayList<>();
 
             final boolean cloneKeyFieldOnly =
@@ -2400,13 +2454,15 @@
                 if (packageName != null) {
                     getShortcutsInnerLocked(launcherUserId,
                             callingPackage, packageName, shortcutIds, changedSince,
-                            componentName, queryFlags, userId, ret, cloneFlag);
+                            componentName, queryFlags, userId, ret, cloneFlag,
+                            callingPid, callingUid);
                 } else {
                     final List<String> shortcutIdsF = shortcutIds;
                     getUserShortcutsLocked(userId).forAllPackages(p -> {
                         getShortcutsInnerLocked(launcherUserId,
                                 callingPackage, p.getPackageName(), shortcutIdsF, changedSince,
-                                componentName, queryFlags, userId, ret, cloneFlag);
+                                componentName, queryFlags, userId, ret, cloneFlag,
+                                callingPid, callingUid);
                     });
                 }
             }
@@ -2416,7 +2472,8 @@
         private void getShortcutsInnerLocked(int launcherUserId, @NonNull String callingPackage,
                 @Nullable String packageName, @Nullable List<String> shortcutIds, long changedSince,
                 @Nullable ComponentName componentName, int queryFlags,
-                int userId, ArrayList<ShortcutInfo> ret, int cloneFlag) {
+                int userId, ArrayList<ShortcutInfo> ret, int cloneFlag,
+                int callingPid, int callingUid) {
             final ArraySet<String> ids = shortcutIds == null ? null
                     : new ArraySet<>(shortcutIds);
 
@@ -2425,6 +2482,13 @@
             if (p == null) {
                 return; // No need to instantiate ShortcutPackage.
             }
+            final boolean matchDynamic = (queryFlags & ShortcutQuery.FLAG_MATCH_DYNAMIC) != 0;
+            final boolean matchPinned = (queryFlags & ShortcutQuery.FLAG_MATCH_PINNED) != 0;
+            final boolean matchManifest = (queryFlags & ShortcutQuery.FLAG_MATCH_MANIFEST) != 0;
+
+            final boolean getPinnedByAnyLauncher =
+                    ((queryFlags & ShortcutQuery.FLAG_MATCH_ALL_PINNED) != 0)
+                    && injectCheckAccessShortcutsPermission(callingPid, callingUid);
 
             p.findAll(ret,
                     (ShortcutInfo si) -> {
@@ -2440,20 +2504,17 @@
                                 return false;
                             }
                         }
-                        if (((queryFlags & ShortcutQuery.FLAG_GET_DYNAMIC) != 0)
-                                && si.isDynamic()) {
+                        if (matchDynamic && si.isDynamic()) {
                             return true;
                         }
-                        if (((queryFlags & ShortcutQuery.FLAG_GET_PINNED) != 0)
-                                && si.isPinned()) {
+                        if ((matchPinned && si.isPinned()) || getPinnedByAnyLauncher) {
                             return true;
                         }
-                        if (((queryFlags & ShortcutQuery.FLAG_GET_MANIFEST) != 0)
-                                && si.isManifestShortcut()) {
+                        if (matchManifest && si.isDeclaredInManifest()) {
                             return true;
                         }
                         return false;
-                    }, cloneFlag, callingPackage, launcherUserId);
+                    }, cloneFlag, callingPackage, launcherUserId, getPinnedByAnyLauncher);
         }
 
         @Override
@@ -2470,14 +2531,16 @@
                         .attemptToRestoreIfNeededAndSave();
 
                 final ShortcutInfo si = getShortcutInfoLocked(
-                        launcherUserId, callingPackage, packageName, shortcutId, userId);
+                        launcherUserId, callingPackage, packageName, shortcutId, userId,
+                        /*getPinnedByAnyLauncher=*/ false);
                 return si != null && si.isPinned();
             }
         }
 
         private ShortcutInfo getShortcutInfoLocked(
                 int launcherUserId, @NonNull String callingPackage,
-                @NonNull String packageName, @NonNull String shortcutId, int userId) {
+                @NonNull String packageName, @NonNull String shortcutId, int userId,
+                boolean getPinnedByAnyLauncher) {
             Preconditions.checkStringNotEmpty(packageName, "packageName");
             Preconditions.checkStringNotEmpty(shortcutId, "shortcutId");
 
@@ -2493,7 +2556,7 @@
             final ArrayList<ShortcutInfo> list = new ArrayList<>(1);
             p.findAll(list,
                     (ShortcutInfo si) -> shortcutId.equals(si.getId()),
-                    /* clone flags=*/ 0, callingPackage, launcherUserId);
+                    /* clone flags=*/ 0, callingPackage, launcherUserId, getPinnedByAnyLauncher);
             return list.size() == 0 ? null : list.get(0);
         }
 
@@ -2513,7 +2576,7 @@
                         getLauncherShortcutsLocked(callingPackage, userId, launcherUserId);
                 launcher.attemptToRestoreIfNeededAndSave();
 
-                launcher.pinShortcuts(userId, packageName, shortcutIds);
+                launcher.pinShortcuts(userId, packageName, shortcutIds, /*forPinRequest=*/ false);
             }
             packageShortcutsChanged(packageName, userId);
 
@@ -2523,7 +2586,8 @@
         @Override
         public Intent[] createShortcutIntents(int launcherUserId,
                 @NonNull String callingPackage,
-                @NonNull String packageName, @NonNull String shortcutId, int userId) {
+                @NonNull String packageName, @NonNull String shortcutId, int userId,
+                int callingPid, int callingUid) {
             // Calling permission must be checked by LauncherAppsImpl.
             Preconditions.checkStringNotEmpty(packageName, "packageName can't be empty");
             Preconditions.checkStringNotEmpty(shortcutId, "shortcutId can't be empty");
@@ -2535,9 +2599,13 @@
                 getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
                         .attemptToRestoreIfNeededAndSave();
 
+                final boolean getPinnedByAnyLauncher =
+                        injectCheckAccessShortcutsPermission(callingPid, callingUid);
+
                 // Make sure the shortcut is actually visible to the launcher.
                 final ShortcutInfo si = getShortcutInfoLocked(
-                        launcherUserId, callingPackage, packageName, shortcutId, userId);
+                        launcherUserId, callingPackage, packageName, shortcutId, userId,
+                        getPinnedByAnyLauncher);
                 // "si == null" should suffice here, but check the flags too just to make sure.
                 if (si == null || !si.isEnabled() || !si.isAlive()) {
                     Log.e(TAG, "Shortcut " + shortcutId + " does not exist or disabled");
@@ -2623,8 +2691,9 @@
 
         @Override
         public boolean hasShortcutHostPermission(int launcherUserId,
-                @NonNull String callingPackage) {
-            return ShortcutService.this.hasShortcutHostPermission(callingPackage, launcherUserId);
+                @NonNull String callingPackage, int callingPid, int callingUid) {
+            return ShortcutService.this.hasShortcutHostPermission(callingPackage, launcherUserId,
+                    callingPid, callingUid);
         }
 
         @Override
@@ -3343,7 +3412,7 @@
         return isApplicationFlagSet(packageName, userId, ApplicationInfo.FLAG_ALLOW_BACKUP);
     }
 
-    boolean shouldBackupApp(PackageInfo pi) {
+    static boolean shouldBackupApp(PackageInfo pi) {
         return (pi.applicationInfo.flags & ApplicationInfo.FLAG_ALLOW_BACKUP) != 0;
     }
 
@@ -3371,7 +3440,7 @@
             // Set the version code for the launchers.
             // We shouldn't do this for publisher packages, because we don't want to update the
             // version code without rescanning the manifest.
-            user.forAllLaunchers(launcher -> launcher.ensureVersionInfo());
+            user.forAllLaunchers(launcher -> launcher.ensurePackageInfo());
 
             // Save to the filesystem.
             scheduleSaveUser(userId);
diff --git a/com/android/server/pm/ShortcutUser.java b/com/android/server/pm/ShortcutUser.java
index 55e6d28..48eccd0 100644
--- a/com/android/server/pm/ShortcutUser.java
+++ b/com/android/server/pm/ShortcutUser.java
@@ -364,9 +364,6 @@
     private void saveShortcutPackageItem(XmlSerializer out,
             ShortcutPackageItem spi, boolean forBackup) throws IOException, XmlPullParserException {
         if (forBackup) {
-            if (!mService.shouldBackupApp(spi.getPackageName(), spi.getPackageUserId())) {
-                return; // Don't save.
-            }
             if (spi.getPackageUserId() != spi.getOwnerUserId()) {
                 return; // Don't save cross-user information.
             }
diff --git a/com/android/server/pm/UserRestrictionsUtils.java b/com/android/server/pm/UserRestrictionsUtils.java
index a6b05d7..c18a71d 100644
--- a/com/android/server/pm/UserRestrictionsUtils.java
+++ b/com/android/server/pm/UserRestrictionsUtils.java
@@ -96,6 +96,7 @@
             UserManager.DISALLOW_SMS,
             UserManager.DISALLOW_FUN,
             UserManager.DISALLOW_CREATE_WINDOWS,
+            UserManager.DISALLOW_SYSTEM_ERROR_DIALOGS,
             UserManager.DISALLOW_CROSS_PROFILE_COPY_PASTE,
             UserManager.DISALLOW_OUTGOING_BEAM,
             UserManager.DISALLOW_WALLPAPER,
@@ -156,6 +157,7 @@
     private static final Set<String> GLOBAL_RESTRICTIONS = Sets.newArraySet(
             UserManager.DISALLOW_ADJUST_VOLUME,
             UserManager.DISALLOW_BLUETOOTH_SHARING,
+            UserManager.DISALLOW_SYSTEM_ERROR_DIALOGS,
             UserManager.DISALLOW_RUN_IN_BACKGROUND,
             UserManager.DISALLOW_UNMUTE_MICROPHONE,
             UserManager.DISALLOW_UNMUTE_DEVICE
diff --git a/com/android/server/pm/permission/BasePermission.java b/com/android/server/pm/permission/BasePermission.java
index 09a6e9c..71d3202 100644
--- a/com/android/server/pm/permission/BasePermission.java
+++ b/com/android/server/pm/permission/BasePermission.java
@@ -48,6 +48,7 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
@@ -77,7 +78,7 @@
 
     final String name;
 
-    @PermissionType final int type;
+    final @PermissionType int type;
 
     String sourcePackageName;
 
@@ -252,12 +253,12 @@
         return changed;
     }
 
-    public void updateDynamicPermission(Map<String, BasePermission> permissionTrees) {
+    public void updateDynamicPermission(Collection<BasePermission> permissionTrees) {
         if (PackageManagerService.DEBUG_SETTINGS) Log.v(TAG, "Dynamic permission: name="
                 + getName() + " pkg=" + getSourcePackageName()
                 + " info=" + pendingPermissionInfo);
         if (sourcePackageSetting == null && pendingPermissionInfo != null) {
-            final BasePermission tree = findPermissionTreeLP(permissionTrees, name);
+            final BasePermission tree = findPermissionTree(permissionTrees, name);
             if (tree != null && tree.perm != null) {
                 sourcePackageSetting = tree.sourcePackageSetting;
                 perm = new PackageParser.Permission(tree.perm.owner,
@@ -269,8 +270,8 @@
         }
     }
 
-    public static BasePermission createOrUpdate(@Nullable BasePermission bp, @NonNull Permission p,
-            @NonNull PackageParser.Package pkg, Map<String, BasePermission> permissionTrees,
+    static BasePermission createOrUpdate(@Nullable BasePermission bp, @NonNull Permission p,
+            @NonNull PackageParser.Package pkg, Collection<BasePermission> permissionTrees,
             boolean chatty) {
         final PackageSettingBase pkgSetting = (PackageSettingBase) pkg.mExtras;
         // Allow system apps to redefine non-system permissions
@@ -300,7 +301,7 @@
         if (bp.perm == null) {
             if (bp.sourcePackageName == null
                     || bp.sourcePackageName.equals(p.info.packageName)) {
-                final BasePermission tree = findPermissionTreeLP(permissionTrees, p.info.name);
+                final BasePermission tree = findPermissionTree(permissionTrees, p.info.name);
                 if (tree == null
                         || tree.sourcePackageName.equals(p.info.packageName)) {
                     bp.sourcePackageSetting = pkgSetting;
@@ -345,12 +346,12 @@
         return bp;
     }
 
-    public static BasePermission enforcePermissionTreeLP(
-            Map<String, BasePermission> permissionTrees, String permName, int callingUid) {
+    static BasePermission enforcePermissionTree(
+            Collection<BasePermission> permissionTrees, String permName, int callingUid) {
         if (permName != null) {
-            BasePermission bp = findPermissionTreeLP(permissionTrees, permName);
+            BasePermission bp = findPermissionTree(permissionTrees, permName);
             if (bp != null) {
-                if (bp.uid == UserHandle.getAppId(callingUid)) {//UserHandle.getAppId(Binder.getCallingUid())) {
+                if (bp.uid == UserHandle.getAppId(callingUid)) {
                     return bp;
                 }
                 throw new SecurityException("Calling uid " + callingUid
@@ -373,9 +374,9 @@
         }
     }
 
-    private static BasePermission findPermissionTreeLP(
-            Map<String, BasePermission> permissionTrees, String permName) {
-        for (BasePermission bp : permissionTrees.values()) {
+    private static BasePermission findPermissionTree(
+            Collection<BasePermission> permissionTrees, String permName) {
+        for (BasePermission bp : permissionTrees) {
             if (permName.startsWith(bp.name) &&
                     permName.length() > bp.name.length() &&
                     permName.charAt(bp.name.length()) == '.') {
diff --git a/com/android/server/pm/permission/PermissionManagerInternal.java b/com/android/server/pm/permission/PermissionManagerInternal.java
index 3b20b42..8aac52a 100644
--- a/com/android/server/pm/permission/PermissionManagerInternal.java
+++ b/com/android/server/pm/permission/PermissionManagerInternal.java
@@ -31,6 +31,7 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 /**
  * Internal interfaces to be used by other components within the system server.
@@ -81,11 +82,26 @@
             @NonNull int[] allUserIds);
 
 
-    public abstract boolean addPermission(@NonNull PermissionInfo info, boolean async,
+    /**
+     * Add all permissions in the given package.
+     * <p>
+     * NOTE: argument {@code groupTEMP} is temporary until mPermissionGroups is moved to
+     * the permission settings.
+     */
+    public abstract void addAllPermissions(@NonNull PackageParser.Package pkg, boolean chatty);
+    public abstract void removeAllPermissions(@NonNull PackageParser.Package pkg, boolean chatty);
+    public abstract boolean addDynamicPermission(@NonNull PermissionInfo info, boolean async,
             int callingUid, @Nullable PermissionCallback callback);
-    public abstract void removePermission(@NonNull String permName, int callingUid,
+    public abstract void removeDynamicPermission(@NonNull String permName, int callingUid,
             @Nullable PermissionCallback callback);
 
+    public abstract int updatePermissions(@Nullable String changingPkg,
+            @Nullable PackageParser.Package pkgInfo, int flags);
+    public abstract int updatePermissionTrees(@Nullable String changingPkg,
+            @Nullable PackageParser.Package pkgInfo, int flags);
+
+    public abstract @Nullable String[] getAppOpPermissionPackages(@NonNull String permName);
+
     public abstract int getPermissionFlags(@NonNull String permName,
             @NonNull String packageName, int callingUid, int userId);
     /**
@@ -98,8 +114,6 @@
      */
     public abstract @Nullable List<PermissionInfo> getPermissionInfoByGroup(@NonNull String group,
             @PermissionInfoFlags int flags, int callingUid);
-    public abstract boolean isPermissionAppOp(@NonNull String permName);
-    public abstract boolean isPermissionInstant(@NonNull String permName);
 
     /**
      * Updates the flags associated with a permission by replacing the flags in
diff --git a/com/android/server/pm/permission/PermissionManagerService.java b/com/android/server/pm/permission/PermissionManagerService.java
index 6c031a6..d2d857c 100644
--- a/com/android/server/pm/permission/PermissionManagerService.java
+++ b/com/android/server/pm/permission/PermissionManagerService.java
@@ -69,6 +69,7 @@
 import java.util.Collection;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Set;
 
 /**
  * Manages all permissions and handles permissions related tasks.
@@ -260,7 +261,7 @@
 //            }
 
             final ArrayList<PermissionInfo> out = new ArrayList<PermissionInfo>(10);
-            for (BasePermission bp : mSettings.getAllPermissionsLocked()) {
+            for (BasePermission bp : mSettings.mPermissions.values()) {
                 final PermissionInfo pi = bp.generatePermissionInfo(groupName, flags);
                 if (pi != null) {
                     out.add(pi);
@@ -305,7 +306,98 @@
         return protectionLevel;
     }
 
-    private boolean addPermission(
+    private void addAllPermissions(PackageParser.Package pkg, boolean chatty) {
+        final int N = pkg.permissions.size();
+        for (int i=0; i<N; i++) {
+            PackageParser.Permission p = pkg.permissions.get(i);
+
+            // Assume by default that we did not install this permission into the system.
+            p.info.flags &= ~PermissionInfo.FLAG_INSTALLED;
+
+            // Now that permission groups have a special meaning, we ignore permission
+            // groups for legacy apps to prevent unexpected behavior. In particular,
+            // permissions for one app being granted to someone just because they happen
+            // to be in a group defined by another app (before this had no implications).
+            if (pkg.applicationInfo.targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1) {
+                p.group = mPackageManagerInt.getPermissionGroupTEMP(p.info.group);
+                // Warn for a permission in an unknown group.
+                if (PackageManagerService.DEBUG_PERMISSIONS
+                        && p.info.group != null && p.group == null) {
+                    Slog.i(TAG, "Permission " + p.info.name + " from package "
+                            + p.info.packageName + " in an unknown group " + p.info.group);
+                }
+            }
+
+            synchronized (PermissionManagerService.this.mLock) {
+                if (p.tree) {
+                    final BasePermission bp = BasePermission.createOrUpdate(
+                            mSettings.getPermissionTreeLocked(p.info.name), p, pkg,
+                            mSettings.getAllPermissionTreesLocked(), chatty);
+                    mSettings.putPermissionTreeLocked(p.info.name, bp);
+                } else {
+                    final BasePermission bp = BasePermission.createOrUpdate(
+                            mSettings.getPermissionLocked(p.info.name),
+                            p, pkg, mSettings.getAllPermissionTreesLocked(), chatty);
+                    mSettings.putPermissionLocked(p.info.name, bp);
+                }
+            }
+        }
+    }
+
+    private void removeAllPermissions(PackageParser.Package pkg, boolean chatty) {
+        synchronized (mLock) {
+            int N = pkg.permissions.size();
+            StringBuilder r = null;
+            for (int i=0; i<N; i++) {
+                PackageParser.Permission p = pkg.permissions.get(i);
+                BasePermission bp = (BasePermission) mSettings.mPermissions.get(p.info.name);
+                if (bp == null) {
+                    bp = mSettings.mPermissionTrees.get(p.info.name);
+                }
+                if (bp != null && bp.isPermission(p)) {
+                    bp.setPermission(null);
+                    if (PackageManagerService.DEBUG_REMOVE && chatty) {
+                        if (r == null) {
+                            r = new StringBuilder(256);
+                        } else {
+                            r.append(' ');
+                        }
+                        r.append(p.info.name);
+                    }
+                }
+                if (p.isAppOp()) {
+                    ArraySet<String> appOpPkgs =
+                            mSettings.mAppOpPermissionPackages.get(p.info.name);
+                    if (appOpPkgs != null) {
+                        appOpPkgs.remove(pkg.packageName);
+                    }
+                }
+            }
+            if (r != null) {
+                if (PackageManagerService.DEBUG_REMOVE) Log.d(TAG, "  Permissions: " + r);
+            }
+
+            N = pkg.requestedPermissions.size();
+            r = null;
+            for (int i=0; i<N; i++) {
+                String perm = pkg.requestedPermissions.get(i);
+                if (mSettings.isPermissionAppOp(perm)) {
+                    ArraySet<String> appOpPkgs = mSettings.mAppOpPermissionPackages.get(perm);
+                    if (appOpPkgs != null) {
+                        appOpPkgs.remove(pkg.packageName);
+                        if (appOpPkgs.isEmpty()) {
+                            mSettings.mAppOpPermissionPackages.remove(perm);
+                        }
+                    }
+                }
+            }
+            if (r != null) {
+                if (PackageManagerService.DEBUG_REMOVE) Log.d(TAG, "  Permissions: " + r);
+            }
+        }
+    }
+
+    private boolean addDynamicPermission(
             PermissionInfo info, int callingUid, PermissionCallback callback) {
         if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) {
             throw new SecurityException("Instant apps can't add permissions");
@@ -313,8 +405,7 @@
         if (info.labelRes == 0 && info.nonLocalizedLabel == null) {
             throw new SecurityException("Label must be specified in permission");
         }
-        final BasePermission tree = (BasePermission) mPackageManagerInt.enforcePermissionTreeTEMP(
-                info.name, callingUid);
+        final BasePermission tree = mSettings.enforcePermissionTree(info.name, callingUid);
         final boolean added;
         final boolean changed;
         synchronized (mLock) {
@@ -326,8 +417,8 @@
                 bp = new BasePermission(info.name, tree.getSourcePackageName(),
                         BasePermission.TYPE_DYNAMIC);
             } else if (bp.isDynamic()) {
-                throw new SecurityException(
-                        "Not allowed to modify non-dynamic permission "
+                // TODO: switch this back to SecurityException
+                Slog.wtf(TAG, "Not allowed to modify non-dynamic permission "
                         + info.name);
             }
             changed = bp.addToTree(fixedLevel, info, tree);
@@ -341,21 +432,20 @@
         return added;
     }
 
-    private void removePermission(
+    private void removeDynamicPermission(
             String permName, int callingUid, PermissionCallback callback) {
         if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) {
             throw new SecurityException("Instant applications don't have access to this method");
         }
-        final BasePermission tree = (BasePermission) mPackageManagerInt.enforcePermissionTreeTEMP(
-                permName, callingUid);
+        final BasePermission tree = mSettings.enforcePermissionTree(permName, callingUid);
         synchronized (mLock) {
             final BasePermission bp = mSettings.getPermissionLocked(permName);
             if (bp == null) {
                 return;
             }
             if (bp.isDynamic()) {
-                throw new SecurityException(
-                        "Not allowed to modify non-dynamic permission "
+                // TODO: switch this back to SecurityException
+                Slog.wtf(TAG, "Not allowed to modify non-dynamic permission "
                         + permName);
             }
             mSettings.removePermissionLocked(permName);
@@ -713,7 +803,21 @@
         return runtimePermissionChangedUserIds;
     }
 
-    private int getPermissionFlags(String permName, String packageName, int callingUid, int userId) {
+    private String[] getAppOpPermissionPackages(String permName) {
+        if (mPackageManagerInt.getInstantAppPackageName(Binder.getCallingUid()) != null) {
+            return null;
+        }
+        synchronized (mLock) {
+            final ArraySet<String> pkgs = mSettings.mAppOpPermissionPackages.get(permName);
+            if (pkgs == null) {
+                return null;
+            }
+            return pkgs.toArray(new String[pkgs.size()]);
+        }
+    }
+
+    private int getPermissionFlags(
+            String permName, String packageName, int callingUid, int userId) {
         if (!mUserManagerInt.exists(userId)) {
             return 0;
         }
@@ -741,6 +845,96 @@
         return permissionsState.getPermissionFlags(permName, userId);
     }
 
+    private int updatePermissions(String packageName, PackageParser.Package pkgInfo, int flags) {
+        Set<BasePermission> needsUpdate = null;
+        synchronized (mLock) {
+            final Iterator<BasePermission> it = mSettings.mPermissions.values().iterator();
+            while (it.hasNext()) {
+                final BasePermission bp = it.next();
+                if (bp.isDynamic()) {
+                    bp.updateDynamicPermission(mSettings.mPermissionTrees.values());
+                }
+                if (bp.getSourcePackageSetting() != null) {
+                    if (packageName != null && packageName.equals(bp.getSourcePackageName())
+                        && (pkgInfo == null || !hasPermission(pkgInfo, bp.getName()))) {
+                        Slog.i(TAG, "Removing old permission tree: " + bp.getName()
+                                + " from package " + bp.getSourcePackageName());
+                        flags |= PackageManagerService.UPDATE_PERMISSIONS_ALL;
+                        it.remove();
+                    }
+                    continue;
+                }
+                if (needsUpdate == null) {
+                    needsUpdate = new ArraySet<>(mSettings.mPermissions.size());
+                }
+                needsUpdate.add(bp);
+            }
+        }
+        if (needsUpdate != null) {
+            for (final BasePermission bp : needsUpdate) {
+                final PackageParser.Package pkg =
+                        mPackageManagerInt.getPackage(bp.getSourcePackageName());
+                synchronized (mLock) {
+                    if (pkg != null && pkg.mExtras != null) {
+                        final PackageSetting ps = (PackageSetting) pkg.mExtras;
+                        if (bp.getSourcePackageSetting() == null) {
+                            bp.setSourcePackageSetting(ps);
+                        }
+                        continue;
+                    }
+                    Slog.w(TAG, "Removing dangling permission: " + bp.getName()
+                            + " from package " + bp.getSourcePackageName());
+                    mSettings.removePermissionLocked(bp.getName());
+                }
+            }
+        }
+        return flags;
+    }
+
+    private int updatePermissionTrees(String packageName, PackageParser.Package pkgInfo,
+            int flags) {
+        Set<BasePermission> needsUpdate = null;
+        synchronized (mLock) {
+            final Iterator<BasePermission> it = mSettings.mPermissionTrees.values().iterator();
+            while (it.hasNext()) {
+                final BasePermission bp = it.next();
+                if (bp.getSourcePackageSetting() != null) {
+                    if (packageName != null && packageName.equals(bp.getSourcePackageName())
+                        && (pkgInfo == null || !hasPermission(pkgInfo, bp.getName()))) {
+                        Slog.i(TAG, "Removing old permission tree: " + bp.getName()
+                                + " from package " + bp.getSourcePackageName());
+                        flags |= PackageManagerService.UPDATE_PERMISSIONS_ALL;
+                        it.remove();
+                    }
+                    continue;
+                }
+                if (needsUpdate == null) {
+                    needsUpdate = new ArraySet<>(mSettings.mPermissionTrees.size());
+                }
+                needsUpdate.add(bp);
+            }
+        }
+        if (needsUpdate != null) {
+            for (final BasePermission bp : needsUpdate) {
+                final PackageParser.Package pkg =
+                        mPackageManagerInt.getPackage(bp.getSourcePackageName());
+                synchronized (mLock) {
+                    if (pkg != null && pkg.mExtras != null) {
+                        final PackageSetting ps = (PackageSetting) pkg.mExtras;
+                        if (bp.getSourcePackageSetting() == null) {
+                            bp.setSourcePackageSetting(ps);
+                        }
+                        continue;
+                    }
+                    Slog.w(TAG, "Removing dangling permission tree: " + bp.getName()
+                            + " from package " + bp.getSourcePackageName());
+                    mSettings.removePermissionLocked(bp.getName());
+                }
+            }
+        }
+        return flags;
+    }
+
     private void updatePermissionFlags(String permName, String packageName, int flagMask,
             int flagValues, int callingUid, int userId, PermissionCallback callback) {
         if (!mUserManagerInt.exists(userId)) {
@@ -872,7 +1066,7 @@
 
     private int calculateCurrentPermissionFootprintLocked(BasePermission tree) {
         int size = 0;
-        for (BasePermission perm : mSettings.getAllPermissionsLocked()) {
+        for (BasePermission perm : mSettings.mPermissions.values()) {
             size += tree.calculateFootprint(perm);
         }
         return size;
@@ -889,6 +1083,15 @@
         }
     }
 
+    private static boolean hasPermission(PackageParser.Package pkgInfo, String permName) {
+        for (int i=pkgInfo.permissions.size()-1; i>=0; i--) {
+            if (pkgInfo.permissions.get(i).info.name.equals(permName)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     /**
      * Get the first event id for the permission.
      *
@@ -951,14 +1154,22 @@
 
     private class PermissionManagerInternalImpl extends PermissionManagerInternal {
         @Override
-        public boolean addPermission(PermissionInfo info, boolean async, int callingUid,
-                PermissionCallback callback) {
-            return PermissionManagerService.this.addPermission(info, callingUid, callback);
+        public void addAllPermissions(Package pkg, boolean chatty) {
+            PermissionManagerService.this.addAllPermissions(pkg, chatty);
         }
         @Override
-        public void removePermission(String permName, int callingUid,
+        public void removeAllPermissions(Package pkg, boolean chatty) {
+            PermissionManagerService.this.removeAllPermissions(pkg, chatty);
+        }
+        @Override
+        public boolean addDynamicPermission(PermissionInfo info, boolean async, int callingUid,
                 PermissionCallback callback) {
-            PermissionManagerService.this.removePermission(permName, callingUid, callback);
+            return PermissionManagerService.this.addDynamicPermission(info, callingUid, callback);
+        }
+        @Override
+        public void removeDynamicPermission(String permName, int callingUid,
+                PermissionCallback callback) {
+            PermissionManagerService.this.removeDynamicPermission(permName, callingUid, callback);
         }
         @Override
         public void grantRuntimePermission(String permName, String packageName,
@@ -993,12 +1204,26 @@
                     (SharedUserSetting) suSetting, allUserIds);
         }
         @Override
+        public String[] getAppOpPermissionPackages(String permName) {
+            return PermissionManagerService.this.getAppOpPermissionPackages(permName);
+        }
+        @Override
         public int getPermissionFlags(String permName, String packageName, int callingUid,
                 int userId) {
             return PermissionManagerService.this.getPermissionFlags(permName, packageName,
                     callingUid, userId);
         }
         @Override
+        public int updatePermissions(String packageName,
+                PackageParser.Package pkgInfo, int flags) {
+            return PermissionManagerService.this.updatePermissions(packageName, pkgInfo, flags);
+        }
+        @Override
+        public int updatePermissionTrees(String packageName,
+                PackageParser.Package pkgInfo, int flags) {
+            return PermissionManagerService.this.updatePermissionTrees(packageName, pkgInfo, flags);
+        }
+        @Override
         public void updatePermissionFlags(String permName, String packageName, int flagMask,
                 int flagValues, int callingUid, int userId, PermissionCallback callback) {
             PermissionManagerService.this.updatePermissionFlags(
@@ -1038,20 +1263,6 @@
             return PermissionManagerService.this.getPermissionInfoByGroup(group, flags, callingUid);
         }
         @Override
-        public boolean isPermissionInstant(String permName) {
-            synchronized (PermissionManagerService.this.mLock) {
-                final BasePermission bp = mSettings.getPermissionLocked(permName);
-                return (bp != null && bp.isInstant());
-            }
-        }
-        @Override
-        public boolean isPermissionAppOp(String permName) {
-            synchronized (PermissionManagerService.this.mLock) {
-                final BasePermission bp = mSettings.getPermissionLocked(permName);
-                return (bp != null && bp.isAppOp());
-            }
-        }
-        @Override
         public PermissionSettings getPermissionSettings() {
             return mSettings;
         }
diff --git a/com/android/server/pm/permission/PermissionSettings.java b/com/android/server/pm/permission/PermissionSettings.java
index 7a2e5ec..7d125c9 100644
--- a/com/android/server/pm/permission/PermissionSettings.java
+++ b/com/android/server/pm/permission/PermissionSettings.java
@@ -24,6 +24,7 @@
 import android.util.Log;
 
 import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.XmlUtils;
 import com.android.server.pm.DumpState;
 import com.android.server.pm.PackageManagerService;
@@ -35,6 +36,7 @@
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.Collection;
+import java.util.Set;
 
 /**
  * Permissions and other related data. This class is not meant for
@@ -49,8 +51,25 @@
      * All of the permissions known to the system. The mapping is from permission
      * name to permission object.
      */
-    private final ArrayMap<String, BasePermission> mPermissions =
+    @GuardedBy("mLock")
+    final ArrayMap<String, BasePermission> mPermissions =
             new ArrayMap<String, BasePermission>();
+
+    /**
+     * All permission trees known to the system. The mapping is from permission tree
+     * name to permission object.
+     */
+    @GuardedBy("mLock")
+    final ArrayMap<String, BasePermission> mPermissionTrees =
+            new ArrayMap<String, BasePermission>();
+
+    /**
+     * Set of packages that request a particular app op. The mapping is from permission
+     * name to package names.
+     */
+    @GuardedBy("mLock")
+    final ArrayMap<String, ArraySet<String>> mAppOpPermissionPackages = new ArrayMap<>();
+
     private final Object mLock;
 
     PermissionSettings(@NonNull Context context, @NonNull Object lock) {
@@ -65,15 +84,23 @@
         }
     }
 
+    public void addAppOpPackage(String permName, String packageName) {
+        ArraySet<String> pkgs = mAppOpPermissionPackages.get(permName);
+        if (pkgs == null) {
+            pkgs = new ArraySet<>();
+            mAppOpPermissionPackages.put(permName, pkgs);
+        }
+        pkgs.add(packageName);
+    }
+
     /**
      * Transfers ownership of permissions from one package to another.
      */
-    public void transferPermissions(String origPackageName, String newPackageName,
-            ArrayMap<String, BasePermission> permissionTrees) {
+    public void transferPermissions(String origPackageName, String newPackageName) {
         synchronized (mLock) {
             for (int i=0; i<2; i++) {
                 ArrayMap<String, BasePermission> permissions =
-                        i == 0 ? permissionTrees : mPermissions;
+                        i == 0 ? mPermissionTrees : mPermissions;
                 for (BasePermission bp : permissions.values()) {
                     bp.transfer(origPackageName, newPackageName);
                 }
@@ -94,9 +121,26 @@
         }
     }
 
+    public void readPermissionTrees(XmlPullParser parser)
+            throws IOException, XmlPullParserException {
+        synchronized (mLock) {
+            readPermissions(mPermissionTrees, parser);
+        }
+    }
+
     public void writePermissions(XmlSerializer serializer) throws IOException {
-        for (BasePermission bp : mPermissions.values()) {
-            bp.writeLPr(serializer);
+        synchronized (mLock) {
+            for (BasePermission bp : mPermissions.values()) {
+                bp.writeLPr(serializer);
+            }
+        }
+    }
+
+    public void writePermissionTrees(XmlSerializer serializer) throws IOException {
+        synchronized (mLock) {
+            for (BasePermission bp : mPermissionTrees.values()) {
+                bp.writeLPr(serializer);
+            }
         }
     }
 
@@ -128,6 +172,22 @@
                 printedSomething = bp.dumpPermissionsLPr(pw, packageName, permissionNames,
                         externalStorageEnforced, printedSomething, dumpState);
             }
+            if (packageName == null && permissionNames == null) {
+                for (int iperm = 0; iperm<mAppOpPermissionPackages.size(); iperm++) {
+                    if (iperm == 0) {
+                        if (dumpState.onTitlePrinted())
+                            pw.println();
+                        pw.println("AppOp Permissions:");
+                    }
+                    pw.print("  AppOp Permission ");
+                    pw.print(mAppOpPermissionPackages.keyAt(iperm));
+                    pw.println(":");
+                    ArraySet<String> pkgs = mAppOpPermissionPackages.valueAt(iperm);
+                    for (int ipkg=0; ipkg<pkgs.size(); ipkg++) {
+                        pw.print("    "); pw.println(pkgs.valueAt(ipkg));
+                    }
+                }
+            }
         }
     }
 
@@ -135,15 +195,58 @@
         return mPermissions.get(permName);
     }
 
+    @Nullable BasePermission getPermissionTreeLocked(@NonNull String permName) {
+        return mPermissionTrees.get(permName);
+    }
+
     void putPermissionLocked(@NonNull String permName, @NonNull BasePermission permission) {
         mPermissions.put(permName, permission);
     }
 
+    void putPermissionTreeLocked(@NonNull String permName, @NonNull BasePermission permission) {
+        mPermissionTrees.put(permName, permission);
+    }
+
     void removePermissionLocked(@NonNull String permName) {
         mPermissions.remove(permName);
     }
 
-    Collection<BasePermission> getAllPermissionsLocked() {
+    void removePermissionTreeLocked(@NonNull String permName) {
+        mPermissionTrees.remove(permName);
+    }
+
+    @NonNull Collection<BasePermission> getAllPermissionsLocked() {
         return mPermissions.values();
     }
+
+    @NonNull Collection<BasePermission> getAllPermissionTreesLocked() {
+        return mPermissionTrees.values();
+    }
+
+    /**
+     * Returns the permission tree for the given permission.
+     * @throws SecurityException If the calling UID is not allowed to add permissions to the
+     * found permission tree.
+     */
+    @Nullable BasePermission enforcePermissionTree(@NonNull String permName, int callingUid) {
+        synchronized (mLock) {
+            return BasePermission.enforcePermissionTree(
+                    mPermissionTrees.values(), permName, callingUid);
+        }
+    }
+
+    public boolean isPermissionInstant(String permName) {
+        synchronized (mLock) {
+            final BasePermission bp = mPermissions.get(permName);
+            return (bp != null && bp.isInstant());
+        }
+    }
+
+    boolean isPermissionAppOp(String permName) {
+        synchronized (mLock) {
+            final BasePermission bp = mPermissions.get(permName);
+            return (bp != null && bp.isAppOp());
+        }
+    }
+
 }
diff --git a/com/android/server/policy/GlobalActions.java b/com/android/server/policy/GlobalActions.java
index 342ec4b..7a2e630 100644
--- a/com/android/server/policy/GlobalActions.java
+++ b/com/android/server/policy/GlobalActions.java
@@ -58,6 +58,9 @@
 
     public void showDialog(boolean keyguardShowing, boolean deviceProvisioned) {
         if (DEBUG) Slog.d(TAG, "showDialog " + keyguardShowing + " " + deviceProvisioned);
+        if (mStatusBarInternal.isGlobalActionsDisabled()) {
+            return;
+        }
         mKeyguardShowing = keyguardShowing;
         mDeviceProvisioned = deviceProvisioned;
         mShowing = true;
diff --git a/com/android/server/policy/PhoneWindowManager.java b/com/android/server/policy/PhoneWindowManager.java
index db7817e..6520dc9 100644
--- a/com/android/server/policy/PhoneWindowManager.java
+++ b/com/android/server/policy/PhoneWindowManager.java
@@ -18,13 +18,14 @@
 
 import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
 import static android.Manifest.permission.SYSTEM_ALERT_WINDOW;
-import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
-import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
 import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW;
 import static android.app.AppOpsManager.OP_TOAST_WINDOW;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.content.Context.CONTEXT_RESTRICTED;
 import static android.content.Context.DISPLAY_SERVICE;
@@ -552,7 +553,6 @@
 
     int mUserRotationMode = WindowManagerPolicy.USER_ROTATION_FREE;
     int mUserRotation = Surface.ROTATION_0;
-    boolean mAccelerometerDefault;
 
     boolean mSupportAutoRotation;
     int mAllowAllRotations = -1;
@@ -707,7 +707,6 @@
     Intent mVrHeadsetHomeIntent;
     boolean mSearchKeyShortcutPending;
     boolean mConsumeSearchKeyUp;
-    boolean mAssistKeyLongPressed;
     boolean mPendingMetaAction;
     boolean mPendingCapsLockToggle;
     int mMetaState;
@@ -838,6 +837,8 @@
     private static final int MSG_DISPATCH_BACK_KEY_TO_AUTOFILL = 24;
     private static final int MSG_SYSTEM_KEY_PRESS = 25;
     private static final int MSG_HANDLE_ALL_APPS = 26;
+    private static final int MSG_LAUNCH_ASSIST = 27;
+    private static final int MSG_LAUNCH_ASSIST_LONG_PRESS = 28;
 
     private static final int MSG_REQUEST_TRANSIENT_BARS_ARG_STATUS = 0;
     private static final int MSG_REQUEST_TRANSIENT_BARS_ARG_NAVIGATION = 1;
@@ -879,8 +880,16 @@
                 case MSG_HIDE_BOOT_MESSAGE:
                     handleHideBootMessage();
                     break;
+                case MSG_LAUNCH_ASSIST:
+                    final int deviceId = msg.arg1;
+                    final String hint = (String) msg.obj;
+                    launchAssistAction(hint, deviceId);
+                    break;
+                case MSG_LAUNCH_ASSIST_LONG_PRESS:
+                    launchAssistLongPressAction();
+                    break;
                 case MSG_LAUNCH_VOICE_ASSIST_WITH_WAKE_LOCK:
-                    launchVoiceAssistWithWakeLock(msg.arg1 != 0);
+                    launchVoiceAssistWithWakeLock();
                     break;
                 case MSG_POWER_DELAYED_PRESS:
                     powerPress((Long)msg.obj, msg.arg1 != 0, msg.arg2);
@@ -910,7 +919,7 @@
                     disposeInputConsumer((InputConsumer) msg.obj);
                     break;
                 case MSG_BACK_DELAYED_PRESS:
-                    backMultiPressAction((Long) msg.obj, msg.arg1);
+                    backMultiPressAction(msg.arg1);
                     finishBackKeyPress();
                     break;
                 case MSG_ACCESSIBILITY_SHORTCUT:
@@ -1414,7 +1423,7 @@
         }
     }
 
-    private void backMultiPressAction(long eventTime, int count) {
+    private void backMultiPressAction(int count) {
         if (count >= PANIC_PRESS_BACK_COUNT) {
             switch (mPanicPressOnBackBehavior) {
                 case PANIC_PRESS_BACK_NOTHING:
@@ -1583,7 +1592,7 @@
         }
     }
 
-    private void sleepPress(long eventTime) {
+    private void sleepPress() {
         if (mShortPressOnSleepBehavior == SHORT_PRESS_SLEEP_GO_TO_SLEEP_AND_GO_HOME) {
             launchHomeFromHotKey(false /* awakenDreams */, true /*respectKeyguard*/);
         }
@@ -1645,11 +1654,23 @@
                 mScreenshotChordVolumeDownKeyConsumed = true;
                 mA11yShortcutChordVolumeUpKeyConsumed = true;
                 mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_ACCESSIBILITY_SHORTCUT),
-                        ViewConfiguration.get(mContext).getAccessibilityShortcutKeyTimeout());
+                        getAccessibilityShortcutTimeout());
             }
         }
     }
 
+    private long getAccessibilityShortcutTimeout() {
+        ViewConfiguration config = ViewConfiguration.get(mContext);
+        try {
+            return Settings.Secure.getIntForUser(mContext.getContentResolver(),
+                    Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, mCurrentUserId) == 0
+                    ? config.getAccessibilityShortcutKeyTimeout()
+                    : config.getAccessibilityShortcutKeyTimeoutAfterConfirmation();
+        } catch (Settings.SettingNotFoundException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
     private long getScreenshotChordLongPressDelay() {
         if (mKeyguardDelegate.isShowing()) {
             // Double the time it takes to take a screenshot from the keyguard
@@ -2270,7 +2291,11 @@
 
         // Only force the default orientation if the screen is xlarge, at least 960dp x 720dp, per
         // http://developer.android.com/guide/practices/screens_support.html#range
-        mForceDefaultOrientation = longSizeDp >= 960 && shortSizeDp >= 720 &&
+        // For car, ignore the dp limitation. It's physically impossible to rotate the car's screen
+        // so if the orientation is forced, we need to respect that no matter what.
+        boolean isCar = mContext.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_AUTOMOTIVE);
+        mForceDefaultOrientation = ((longSizeDp >= 960 && shortSizeDp >= 720) || isCar) &&
                 res.getBoolean(com.android.internal.R.bool.config_forceDefaultOrientation) &&
                 // For debug purposes the next line turns this feature off with:
                 // $ adb shell setprop config.override_forced_orient true
@@ -2844,7 +2869,7 @@
 
         boolean keyguardLocked = isKeyguardLocked();
         boolean hideDockDivider = attrs.type == TYPE_DOCK_DIVIDER
-                && !mWindowManagerInternal.isStackVisible(DOCKED_STACK_ID);
+                && !mWindowManagerInternal.isStackVisible(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
         return (keyguardLocked && !allowWhenLocked && win.getDisplayId() == DEFAULT_DISPLAY)
                 || hideDockDivider;
     }
@@ -3537,44 +3562,11 @@
                 toggleKeyboardShortcutsMenu(event.getDeviceId());
             }
         } else if (keyCode == KeyEvent.KEYCODE_ASSIST) {
-            if (down) {
-                if (repeatCount == 0) {
-                    mAssistKeyLongPressed = false;
-                } else if (repeatCount == 1) {
-                    mAssistKeyLongPressed = true;
-                    if (!keyguardOn) {
-                         launchAssistLongPressAction();
-                    }
-                }
-            } else {
-                if (mAssistKeyLongPressed) {
-                    mAssistKeyLongPressed = false;
-                } else {
-                    if (!keyguardOn) {
-                        launchAssistAction(null, event.getDeviceId());
-                    }
-                }
-            }
+            Slog.wtf(TAG, "KEYCODE_ASSIST should be handled in interceptKeyBeforeQueueing");
             return -1;
         } else if (keyCode == KeyEvent.KEYCODE_VOICE_ASSIST) {
-            if (!down) {
-                Intent voiceIntent;
-                if (!keyguardOn) {
-                    voiceIntent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH);
-                } else {
-                    IDeviceIdleController dic = IDeviceIdleController.Stub.asInterface(
-                            ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER));
-                    if (dic != null) {
-                        try {
-                            dic.exitIdle("voice-search");
-                        } catch (RemoteException e) {
-                        }
-                    }
-                    voiceIntent = new Intent(RecognizerIntent.ACTION_VOICE_SEARCH_HANDS_FREE);
-                    voiceIntent.putExtra(RecognizerIntent.EXTRA_SECURE, true);
-                }
-                startActivityAsUser(voiceIntent, UserHandle.CURRENT_OR_SELF);
-            }
+            Slog.wtf(TAG, "KEYCODE_VOICE_ASSIST should be handled in interceptKeyBeforeQueueing");
+            return -1;
         } else if (keyCode == KeyEvent.KEYCODE_SYSRQ) {
             if (down && repeatCount == 0) {
                 mScreenshotRunnable.setScreenshotType(TAKE_SCREENSHOT_FULLSCREEN);
@@ -3886,8 +3878,7 @@
                 mAccessibilityTvScheduled = true;
                 Message msg = Message.obtain(mHandler, MSG_ACCESSIBILITY_TV);
                 msg.setAsynchronous(true);
-                mHandler.sendMessageDelayed(msg,
-                        ViewConfiguration.get(mContext).getAccessibilityShortcutKeyTimeout());
+                mHandler.sendMessageDelayed(msg, getAccessibilityShortcutTimeout());
             }
         } else if (mAccessibilityTvScheduled) {
             mHandler.removeMessages(MSG_ACCESSIBILITY_TV);
@@ -5445,7 +5436,10 @@
 
         boolean appWindow = attrs.type >= FIRST_APPLICATION_WINDOW
                 && attrs.type < FIRST_SYSTEM_WINDOW;
-        final int stackId = win.getStackId();
+        final int windowingMode = win.getWindowingMode();
+        final boolean inFullScreenOrSplitScreenSecondaryWindowingMode =
+                windowingMode == WINDOWING_MODE_FULLSCREEN
+                        || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
         if (mTopFullscreenOpaqueWindowState == null && affectsSystemUi) {
             if ((fl & FLAG_FORCE_NOT_FULLSCREEN) != 0) {
                 mForceStatusBar = true;
@@ -5464,7 +5458,7 @@
             // represent should be hidden or if we should hide the lockscreen. For attached app
             // windows we defer the decision to the window it is attached to.
             if (appWindow && attached == null) {
-                if (attrs.isFullscreen() && StackId.normallyFullscreenWindows(stackId)) {
+                if (attrs.isFullscreen() && inFullScreenOrSplitScreenSecondaryWindowingMode) {
                     if (DEBUG_LAYOUT) Slog.v(TAG, "Fullscreen window: " + win);
                     mTopFullscreenOpaqueWindowState = win;
                     if (mTopFullscreenOpaqueOrDimmingWindowState == null) {
@@ -5495,7 +5489,7 @@
 
         // Keep track of the window if it's dimming but not necessarily fullscreen.
         if (mTopFullscreenOpaqueOrDimmingWindowState == null && affectsSystemUi
-                && win.isDimming() && StackId.normallyFullscreenWindows(stackId)) {
+                && win.isDimming() && inFullScreenOrSplitScreenSecondaryWindowingMode) {
             mTopFullscreenOpaqueOrDimmingWindowState = win;
         }
 
@@ -5503,7 +5497,7 @@
         // separately, because both the "real fullscreen" opaque window and the one for the docked
         // stack can control View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR.
         if (mTopDockedOpaqueWindowState == null && affectsSystemUi && appWindow && attached == null
-                && attrs.isFullscreen() && stackId == DOCKED_STACK_ID) {
+                && attrs.isFullscreen() && windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
             mTopDockedOpaqueWindowState = win;
             if (mTopDockedOpaqueOrDimmingWindowState == null) {
                 mTopDockedOpaqueOrDimmingWindowState = win;
@@ -5513,7 +5507,7 @@
         // Also keep track of any windows that are dimming but not necessarily fullscreen in the
         // docked stack.
         if (mTopDockedOpaqueOrDimmingWindowState == null && affectsSystemUi && win.isDimming()
-                && stackId == DOCKED_STACK_ID) {
+                && windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
             mTopDockedOpaqueOrDimmingWindowState = win;
         }
 
@@ -5608,8 +5602,9 @@
                         changes |= FINISH_LAYOUT_REDO_LAYOUT;
                     }
                 } else if (topIsFullscreen
-                        && !mWindowManagerInternal.isStackVisible(FREEFORM_WORKSPACE_STACK_ID)
-                        && !mWindowManagerInternal.isStackVisible(DOCKED_STACK_ID)) {
+                        && !mWindowManagerInternal.isStackVisible(WINDOWING_MODE_FREEFORM)
+                        && !mWindowManagerInternal.isStackVisible(
+                                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY)) {
                     if (DEBUG_LAYOUT) Slog.v(TAG, "** HIDING status bar");
                     if (mStatusBarController.setBarShowingLw(false)) {
                         changes |= FINISH_LAYOUT_REDO_LAYOUT;
@@ -6191,7 +6186,7 @@
                     useHapticFeedback = false; // suppress feedback if already non-interactive
                 }
                 if (down) {
-                    sleepPress(event.getEventTime());
+                    sleepPress();
                 } else {
                     sleepRelease(event.getEventTime());
                 }
@@ -6262,18 +6257,30 @@
                 }
                 break;
             }
-            case KeyEvent.KEYCODE_VOICE_ASSIST: {
-                // Only do this if we would otherwise not pass it to the user. In that case,
-                // interceptKeyBeforeDispatching would apply a similar but different policy in
-                // order to invoke voice assist actions. Note that we need to make a copy of the
-                // key event here because the original key event will be recycled when we return.
-                if ((result & ACTION_PASS_TO_USER) == 0 && !down) {
-                    mBroadcastWakeLock.acquire();
-                    Message msg = mHandler.obtainMessage(MSG_LAUNCH_VOICE_ASSIST_WITH_WAKE_LOCK,
-                            keyguardActive ? 1 : 0, 0);
+            case KeyEvent.KEYCODE_ASSIST: {
+                final boolean longPressed = event.getRepeatCount() > 0;
+                if (down && longPressed) {
+                    Message msg = mHandler.obtainMessage(MSG_LAUNCH_ASSIST_LONG_PRESS);
                     msg.setAsynchronous(true);
                     msg.sendToTarget();
                 }
+                if (!down && !longPressed) {
+                    Message msg = mHandler.obtainMessage(MSG_LAUNCH_ASSIST, event.getDeviceId(),
+                            0 /* unused */, null /* hint */);
+                    msg.setAsynchronous(true);
+                    msg.sendToTarget();
+                }
+                result &= ~ACTION_PASS_TO_USER;
+                break;
+            }
+            case KeyEvent.KEYCODE_VOICE_ASSIST: {
+                if (!down) {
+                    mBroadcastWakeLock.acquire();
+                    Message msg = mHandler.obtainMessage(MSG_LAUNCH_VOICE_ASSIST_WITH_WAKE_LOCK);
+                    msg.setAsynchronous(true);
+                    msg.sendToTarget();
+                }
+                result &= ~ACTION_PASS_TO_USER;
                 break;
             }
             case KeyEvent.KEYCODE_WINDOW: {
@@ -6542,18 +6549,22 @@
         }
     }
 
-    void launchVoiceAssistWithWakeLock(boolean keyguardActive) {
-        IDeviceIdleController dic = IDeviceIdleController.Stub.asInterface(
-                ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER));
-        if (dic != null) {
-            try {
-                dic.exitIdle("voice-search");
-            } catch (RemoteException e) {
+    void launchVoiceAssistWithWakeLock() {
+        final Intent voiceIntent;
+        if (!keyguardOn()) {
+            voiceIntent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH);
+        } else {
+            IDeviceIdleController dic = IDeviceIdleController.Stub.asInterface(
+                    ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER));
+            if (dic != null) {
+                try {
+                    dic.exitIdle("voice-search");
+                } catch (RemoteException e) {
+                }
             }
+            voiceIntent = new Intent(RecognizerIntent.ACTION_VOICE_SEARCH_HANDS_FREE);
+            voiceIntent.putExtra(RecognizerIntent.EXTRA_SECURE, true);
         }
-        Intent voiceIntent =
-            new Intent(RecognizerIntent.ACTION_VOICE_SEARCH_HANDS_FREE);
-        voiceIntent.putExtra(RecognizerIntent.EXTRA_SECURE, keyguardActive);
         startActivityAsUser(voiceIntent, UserHandle.CURRENT_OR_SELF);
         mBroadcastWakeLock.release();
     }
@@ -8009,9 +8020,10 @@
     }
 
     private int updateSystemBarsLw(WindowState win, int oldVis, int vis) {
-        final boolean dockedStackVisible = mWindowManagerInternal.isStackVisible(DOCKED_STACK_ID);
+        final boolean dockedStackVisible =
+                mWindowManagerInternal.isStackVisible(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
         final boolean freeformStackVisible =
-                mWindowManagerInternal.isStackVisible(FREEFORM_WORKSPACE_STACK_ID);
+                mWindowManagerInternal.isStackVisible(WINDOWING_MODE_FREEFORM);
         final boolean resizing = mWindowManagerInternal.isDockedDividerResizing();
 
         // We need to force system bars when the docked stack is visible, when the freeform stack
diff --git a/com/android/server/power/ShutdownThread.java b/com/android/server/power/ShutdownThread.java
index 515fa39..755c5f0 100644
--- a/com/android/server/power/ShutdownThread.java
+++ b/com/android/server/power/ShutdownThread.java
@@ -34,8 +34,6 @@
 import android.graphics.Color;
 import android.graphics.drawable.ColorDrawable;
 import android.media.AudioAttributes;
-import android.nfc.INfcAdapter;
-import android.nfc.NfcAdapter;
 import android.os.FileUtils;
 import android.os.Handler;
 import android.os.PowerManager;
@@ -124,7 +122,6 @@
     private static String METRIC_RADIOS = "shutdown_radios";
     private static String METRIC_BT = "shutdown_bt";
     private static String METRIC_RADIO = "shutdown_radio";
-    private static String METRIC_NFC = "shutdown_nfc";
     private static String METRIC_SM = "shutdown_storage_manager";
 
     private final Object mActionDoneSync = new Object();
@@ -629,29 +626,14 @@
         Thread t = new Thread() {
             public void run() {
                 TimingsTraceLog shutdownTimingsTraceLog = newTimingsLog();
-                boolean nfcOff;
                 boolean bluetoothReadyForShutdown;
                 boolean radioOff;
 
-                final INfcAdapter nfc =
-                        INfcAdapter.Stub.asInterface(ServiceManager.checkService("nfc"));
                 final ITelephony phone =
                         ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
                 final IBluetoothManager bluetooth =
                         IBluetoothManager.Stub.asInterface(ServiceManager.checkService(
                                 BluetoothAdapter.BLUETOOTH_MANAGER_SERVICE));
-                try {
-                    nfcOff = nfc == null ||
-                             nfc.getState() == NfcAdapter.STATE_OFF;
-                    if (!nfcOff) {
-                        Log.w(TAG, "Turning off NFC...");
-                        metricStarted(METRIC_NFC);
-                        nfc.disable(false); // Don't persist new state
-                    }
-                } catch (RemoteException ex) {
-                Log.e(TAG, "RemoteException during NFC shutdown", ex);
-                    nfcOff = true;
-                }
 
                 try {
                     bluetoothReadyForShutdown = bluetooth == null ||
@@ -678,7 +660,7 @@
                     radioOff = true;
                 }
 
-                Log.i(TAG, "Waiting for NFC, Bluetooth and Radio...");
+                Log.i(TAG, "Waiting for Bluetooth and Radio...");
 
                 long delay = endTime - SystemClock.elapsedRealtime();
                 while (delay > 0) {
@@ -722,23 +704,9 @@
                                     .logDuration("ShutdownRadio", TRON_METRICS.get(METRIC_RADIO));
                         }
                     }
-                    if (!nfcOff) {
-                        try {
-                            nfcOff = nfc.getState() == NfcAdapter.STATE_OFF;
-                        } catch (RemoteException ex) {
-                            Log.e(TAG, "RemoteException during NFC shutdown", ex);
-                            nfcOff = true;
-                        }
-                        if (nfcOff) {
-                            Log.i(TAG, "NFC turned off.");
-                            metricEnded(METRIC_NFC);
-                            shutdownTimingsTraceLog
-                                    .logDuration("ShutdownNfc", TRON_METRICS.get(METRIC_NFC));
-                        }
-                    }
 
-                    if (radioOff && bluetoothReadyForShutdown && nfcOff) {
-                        Log.i(TAG, "NFC, Radio and Bluetooth shutdown complete.");
+                    if (radioOff && bluetoothReadyForShutdown) {
+                        Log.i(TAG, "Radio and Bluetooth shutdown complete.");
                         done[0] = true;
                         break;
                     }
@@ -755,7 +723,7 @@
         } catch (InterruptedException ex) {
         }
         if (!done[0]) {
-            Log.w(TAG, "Timed out waiting for NFC, Radio and Bluetooth shutdown.");
+            Log.w(TAG, "Timed out waiting for Radio and Bluetooth shutdown.");
         }
     }
 
diff --git a/com/android/server/stats/StatsCompanionService.java b/com/android/server/stats/StatsCompanionService.java
index f1fb3e7..22d2bcf 100644
--- a/com/android/server/stats/StatsCompanionService.java
+++ b/com/android/server/stats/StatsCompanionService.java
@@ -20,15 +20,27 @@
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
+import android.content.IntentFilter;
 import android.os.Binder;
+import android.os.Bundle;
 import android.os.IBinder;
 import android.os.IStatsCompanionService;
 import android.os.IStatsManager;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.StatsLogEventWrapper;
+import android.os.UserHandle;
+import android.os.UserManager;
 import android.util.Slog;
 
+import java.util.ArrayList;
+import java.util.List;
+
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.os.KernelWakelockReader;
 import com.android.internal.os.KernelWakelockStats;
@@ -39,6 +51,7 @@
 /**
  * Helper service for statsd (the native stats management service in cmds/statsd/).
  * Used for registering and receiving alarms on behalf of statsd.
+ *
  * @hide
  */
 public class StatsCompanionService extends IStatsCompanionService.Stub {
@@ -53,6 +66,8 @@
 
     private final PendingIntent mAnomalyAlarmIntent;
     private final PendingIntent mPollingAlarmIntent;
+    private final BroadcastReceiver mAppUpdateReceiver;
+    private final BroadcastReceiver mUserUpdateReceiver;
 
     public StatsCompanionService(Context context) {
         super();
@@ -63,9 +78,114 @@
                 new Intent(mContext, AnomalyAlarmReceiver.class), 0);
         mPollingAlarmIntent = PendingIntent.getBroadcast(mContext, 0,
                 new Intent(mContext, PollingAlarmReceiver.class), 0);
+        mAppUpdateReceiver = new AppUpdateReceiver();
+        mUserUpdateReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                synchronized (sStatsdLock) {
+                    sStatsd = fetchStatsdService();
+                    if (sStatsd == null) {
+                        Slog.w(TAG, "Could not access statsd");
+                        return;
+                    }
+                    try {
+                        // Pull the latest state of UID->app name, version mapping.
+                        // Needed since the new user basically has a version of every app.
+                        informAllUidsLocked(context);
+                    } catch (RemoteException e) {
+                        Slog.e(TAG, "Failed to inform statsd latest update of all apps", e);
+                        forgetEverything();
+                    }
+                }
+            }
+        };
+        Slog.w(TAG, "Registered receiver for ACTION_PACKAGE_REPLACE AND ADDED.");
     }
 
-    public final static class AnomalyAlarmReceiver extends BroadcastReceiver  {
+    private final static int[] toIntArray(List<Integer> list) {
+        int[] ret = new int[list.size()];
+        for (int i = 0; i < ret.length; i++) {
+            ret[i] = list.get(i);
+        }
+        return ret;
+    }
+
+    // Assumes that sStatsdLock is held.
+    private final void informAllUidsLocked(Context context) throws RemoteException {
+        UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE);
+        PackageManager pm = context.getPackageManager();
+        final List<UserInfo> users = um.getUsers(true);
+        if (DEBUG) {
+            Slog.w(TAG, "Iterating over " + users.size() + " profiles.");
+        }
+
+        List<Integer> uids = new ArrayList();
+        List<Integer> versions = new ArrayList();
+        List<String> apps = new ArrayList();
+
+        // Add in all the apps for every user/profile.
+        for (UserInfo profile : users) {
+            List<PackageInfo> pi = pm.getInstalledPackagesAsUser(0, profile.id);
+            for (int j = 0; j < pi.size(); j++) {
+                if (pi.get(j).applicationInfo != null) {
+                    uids.add(pi.get(j).applicationInfo.uid);
+                    versions.add(pi.get(j).versionCode);
+                    apps.add(pi.get(j).packageName);
+                }
+            }
+        }
+        sStatsd.informAllUidData(toIntArray(uids), toIntArray(versions), apps.toArray(new
+                String[apps.size()]));
+        if (DEBUG) {
+            Slog.w(TAG, "Sent data for " + uids.size() + " apps");
+        }
+    }
+
+    public final static class AppUpdateReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            Slog.i(TAG, "StatsCompanionService noticed an app was updated.");
+            /**
+             * App updates actually consist of REMOVE, ADD, and then REPLACE broadcasts. To avoid
+             * waste, we ignore the REMOVE and ADD broadcasts that contain the replacing flag.
+             */
+            if (!intent.getAction().equals(Intent.ACTION_PACKAGE_REPLACED) &&
+                    intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
+                return; // Keep only replacing or normal add and remove.
+            }
+            synchronized (sStatsdLock) {
+                if (sStatsd == null) {
+                    Slog.w(TAG, "Could not access statsd to inform it of anomaly alarm firing");
+                    return;
+                }
+                try {
+                    if (intent.getAction().equals(Intent.ACTION_PACKAGE_REMOVED)) {
+                        Bundle b = intent.getExtras();
+                        int uid = b.getInt(Intent.EXTRA_UID);
+                        boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
+                        if (!replacing) {
+                            // Don't bother sending an update if we're right about to get another
+                            // intent for the new version that's added.
+                            PackageManager pm = context.getPackageManager();
+                            String app = intent.getData().getSchemeSpecificPart();
+                            sStatsd.informOnePackageRemoved(app, uid);
+                        }
+                    } else {
+                        PackageManager pm = context.getPackageManager();
+                        Bundle b = intent.getExtras();
+                        int uid = b.getInt(Intent.EXTRA_UID);
+                        String app = intent.getData().getSchemeSpecificPart();
+                        PackageInfo pi = pm.getPackageInfo(app, PackageManager.MATCH_ANY_USER);
+                        sStatsd.informOnePackage(app, uid, pi.versionCode);
+                    }
+                } catch (Exception e) {
+                    Slog.w(TAG, "Failed to inform statsd of an app update", e);
+                }
+            }
+        }
+    }
+
+    public final static class AnomalyAlarmReceiver extends BroadcastReceiver {
         @Override
         public void onReceive(Context context, Intent intent) {
             Slog.i(TAG, "StatsCompanionService believes an anomaly has occurred.");
@@ -83,7 +203,7 @@
             }
             // AlarmManager releases its own wakelock here.
         }
-    };
+    }
 
     public final static class PollingAlarmReceiver extends BroadcastReceiver {
         @Override
@@ -103,7 +223,7 @@
             }
             // AlarmManager releases its own wakelock here.
         }
-    };
+    }
 
     @Override // Binder call
     public void setAnomalyAlarm(long timestampMs) {
@@ -169,33 +289,34 @@
     private final KernelWakelockStats mTmpWakelockStats = new KernelWakelockStats();
 
     @Override // Binder call
-    public String pullData(int pullCode) {
+    public StatsLogEventWrapper[] pullData(int pullCode) {
         enforceCallingPermission();
-        if (DEBUG) Slog.d(TAG, "Fetching " + pullCode);
+        if (DEBUG) {
+            Slog.d(TAG, "Pulling " + pullCode);
+        }
 
-        StringBuilder s = new StringBuilder(); // TODO: use and return a Parcel instead of a string
+        List<StatsLogEventWrapper> ret = new ArrayList<>();
         switch (pullCode) {
-            case PULL_CODE_KERNEL_WAKELOCKS:
+            case PULL_CODE_KERNEL_WAKELOCKS: {
                 final KernelWakelockStats wakelockStats =
                         mKernelWakelockReader.readKernelWakelockStats(mTmpWakelockStats);
-
                 for (Map.Entry<String, KernelWakelockStats.Entry> ent : wakelockStats.entrySet()) {
                     String name = ent.getKey();
                     KernelWakelockStats.Entry kws = ent.getValue();
-                    s.append("Wakelock ")
-                            .append(name)
-                            .append(", time=")
-                            .append(kws.mTotalTime)
-                            .append(", count=")
-                            .append(kws.mCount)
-                            .append('\n');
+                    StatsLogEventWrapper e = new StatsLogEventWrapper(101, 4);
+                    e.writeInt(kws.mCount);
+                    e.writeInt(kws.mVersion);
+                    e.writeLong(kws.mTotalTime);
+                    e.writeString(name);
+                    ret.add(e);
                 }
                 break;
+            }
             default:
                 Slog.w(TAG, "No such pollable data as " + pullCode);
                 return null;
         }
-        return s.toString();
+        return ret.toArray(new StatsLogEventWrapper[ret.size()]);
     }
 
     @Override // Binder call
@@ -214,7 +335,9 @@
 
     // Lifecycle and related code
 
-    /** Fetches the statsd IBinder service */
+    /**
+     * Fetches the statsd IBinder service
+     */
     private static IStatsManager fetchStatsdService() {
         return IStatsManager.Stub.asInterface(ServiceManager.getService("stats"));
     }
@@ -246,13 +369,17 @@
         }
     }
 
-    /** Now that the android system is ready, StatsCompanion is ready too, so inform statsd. */
+    /**
+     * Now that the android system is ready, StatsCompanion is ready too, so inform statsd.
+     */
     private void systemReady() {
         if (DEBUG) Slog.d(TAG, "Learned that systemReady");
         sayHiToStatsd();
     }
 
-    /** Tells statsd that statscompanion is ready. If the binder call returns, link to statsd. */
+    /**
+     * Tells statsd that statscompanion is ready. If the binder call returns, link to statsd.
+     */
     private void sayHiToStatsd() {
         synchronized (sStatsdLock) {
             if (sStatsd != null) {
@@ -275,6 +402,23 @@
                     Slog.e(TAG, "linkToDeath(StatsdDeathRecipient) failed", e);
                     forgetEverything();
                 }
+                // Setup broadcast receiver for updates
+                IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_REPLACED);
+                filter.addAction(Intent.ACTION_PACKAGE_ADDED);
+                filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+                filter.addDataScheme("package");
+                mContext.registerReceiverAsUser(mAppUpdateReceiver, UserHandle.ALL, filter, null,
+                        null);
+
+                // Setup receiver for user initialize (which happens once for a new user) and
+                // if a user is removed.
+                filter = new IntentFilter(Intent.ACTION_USER_INITIALIZE);
+                filter.addAction(Intent.ACTION_USER_REMOVED);
+                mContext.registerReceiverAsUser(mUserUpdateReceiver, UserHandle.ALL,
+                        filter, null, null);
+
+                // Pull the latest state of UID->app name, version mapping when statsd starts.
+                informAllUidsLocked(mContext);
             } catch (RemoteException e) {
                 Slog.e(TAG, "Failed to inform statsd that statscompanion is ready", e);
                 forgetEverything();
@@ -293,6 +437,8 @@
     private void forgetEverything() {
         synchronized (sStatsdLock) {
             sStatsd = null;
+            mContext.unregisterReceiver(mAppUpdateReceiver);
+            mContext.unregisterReceiver(mUserUpdateReceiver);
             cancelAnomalyAlarm();
             cancelPollingAlarms();
         }
diff --git a/com/android/server/statusbar/StatusBarManagerInternal.java b/com/android/server/statusbar/StatusBarManagerInternal.java
index 0884678..b07fe98 100644
--- a/com/android/server/statusbar/StatusBarManagerInternal.java
+++ b/com/android/server/statusbar/StatusBarManagerInternal.java
@@ -77,6 +77,7 @@
 
     void setCurrentUser(int newUserId);
 
+    boolean isGlobalActionsDisabled();
     void setGlobalActionsListener(GlobalActionsListener listener);
     void showGlobalActions();
 
diff --git a/com/android/server/statusbar/StatusBarManagerService.java b/com/android/server/statusbar/StatusBarManagerService.java
index bdfbe48..c78a340 100644
--- a/com/android/server/statusbar/StatusBarManagerService.java
+++ b/com/android/server/statusbar/StatusBarManagerService.java
@@ -16,6 +16,8 @@
 
 package com.android.server.statusbar;
 
+import static android.app.StatusBarManager.DISABLE2_GLOBAL_ACTIONS;
+
 import android.app.ActivityThread;
 import android.app.StatusBarManager;
 import android.content.ComponentName;
@@ -363,6 +365,11 @@
         }
 
         @Override
+        public boolean isGlobalActionsDisabled() {
+            return (mDisabled2 & DISABLE2_GLOBAL_ACTIONS) != 0;
+        }
+
+        @Override
         public void setGlobalActionsListener(GlobalActionsListener listener) {
             mGlobalActionListener = listener;
             mGlobalActionListener.onStatusBarConnectedChanged(mBar != null);
diff --git a/com/android/server/tv/TvInputHardwareManager.java b/com/android/server/tv/TvInputHardwareManager.java
index 6117da7..c1607e9 100644
--- a/com/android/server/tv/TvInputHardwareManager.java
+++ b/com/android/server/tv/TvInputHardwareManager.java
@@ -1022,20 +1022,6 @@
             }
         }
 
-        @Override
-        public boolean dispatchKeyEventToHdmi(KeyEvent event) throws RemoteException {
-            synchronized (mImplLock) {
-                if (mReleased) {
-                    throw new IllegalStateException("Device already released.");
-                }
-            }
-            if (mInfo.getType() != TvInputHardwareInfo.TV_INPUT_TYPE_HDMI) {
-                return false;
-            }
-            // TODO(hdmi): mHdmiClient.sendKeyEvent(event);
-            return false;
-        }
-
         private boolean startCapture(Surface surface, TvStreamConfig config) {
             synchronized (mImplLock) {
                 if (mReleased) {
diff --git a/com/android/server/usb/UsbAlsaManager.java b/com/android/server/usb/UsbAlsaManager.java
index acc27be..d359b70 100644
--- a/com/android/server/usb/UsbAlsaManager.java
+++ b/com/android/server/usb/UsbAlsaManager.java
@@ -132,7 +132,9 @@
         mHasMidiFeature = context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_MIDI);
 
         // initial scan
-        mCardsParser.scan();
+        if (mCardsParser.scan() != AlsaCardsParser.SCANSTATUS_SUCCESS) {
+            Slog.e(TAG, "Error scanning ASLA cards file.");
+        }
     }
 
     public void systemReady() {
@@ -314,7 +316,7 @@
             return null;
         }
 
-        if (!mDevicesParser.scan()) {
+        if (mDevicesParser.scan() != AlsaDevicesParser.SCANSTATUS_SUCCESS) {
             Slog.e(TAG, "Error parsing ALSA devices file.");
             return null;
         }
@@ -530,6 +532,9 @@
     //
     // called by UsbService.dump
     public void dump(IndentingPrintWriter pw) {
+        pw.println("Parsers Scan Status:");
+        pw.println("  Cards Parser: " + mCardsParser.getScanStatus());
+        pw.println("  Devices Parser: " + mDevicesParser.getScanStatus());
         pw.println("USB Audio Devices:");
         for (UsbDevice device : mAudioDevices.keySet()) {
             pw.println("  " + device.getDeviceName() + ": " + mAudioDevices.get(device));
diff --git a/com/android/server/usb/UsbHostManager.java b/com/android/server/usb/UsbHostManager.java
index c657a1b..095fdc6 100644
--- a/com/android/server/usb/UsbHostManager.java
+++ b/com/android/server/usb/UsbHostManager.java
@@ -376,6 +376,8 @@
                 }
             }
         }
+
+        mUsbAlsaManager.dump(pw);
     }
 
     private native void monitorUsbHostBus();
diff --git a/com/android/server/wifi/SelfRecovery.java b/com/android/server/wifi/SelfRecovery.java
index 21a3e0a..e39e0d5 100644
--- a/com/android/server/wifi/SelfRecovery.java
+++ b/com/android/server/wifi/SelfRecovery.java
@@ -72,7 +72,7 @@
             Log.e(TAG, "Invalid trigger reason. Ignoring...");
             return;
         }
-        Log.wtf(TAG, "Triggering recovery for reason: " + REASON_STRINGS[reason]);
+        Log.e(TAG, "Triggering recovery for reason: " + REASON_STRINGS[reason]);
         if (reason == REASON_WIFICOND_CRASH || reason == REASON_HAL_CRASH) {
             trimPastRestartTimes();
             // Ensure there haven't been too many restarts within MAX_RESTARTS_TIME_WINDOW
diff --git a/com/android/server/wifi/WifiMetrics.java b/com/android/server/wifi/WifiMetrics.java
index 071b4f8..7254ad4 100644
--- a/com/android/server/wifi/WifiMetrics.java
+++ b/com/android/server/wifi/WifiMetrics.java
@@ -32,10 +32,12 @@
 import android.util.SparseIntArray;
 
 import com.android.server.wifi.aware.WifiAwareMetrics;
+import com.android.server.wifi.hotspot2.ANQPNetworkKey;
 import com.android.server.wifi.hotspot2.NetworkDetail;
 import com.android.server.wifi.hotspot2.PasspointManager;
 import com.android.server.wifi.hotspot2.PasspointMatch;
 import com.android.server.wifi.hotspot2.PasspointProvider;
+import com.android.server.wifi.hotspot2.Utils;
 import com.android.server.wifi.nano.WifiMetricsProto;
 import com.android.server.wifi.nano.WifiMetricsProto.ConnectToNetworkNotificationAndActionCount;
 import com.android.server.wifi.nano.WifiMetricsProto.PnoScanMetrics;
@@ -49,9 +51,11 @@
 import java.util.ArrayList;
 import java.util.BitSet;
 import java.util.Calendar;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 /**
@@ -84,6 +88,9 @@
     public static final int MAX_CONNECTABLE_BSSID_NETWORK_BUCKET = 50;
     public static final int MAX_TOTAL_SCAN_RESULT_SSIDS_BUCKET = 100;
     public static final int MAX_TOTAL_SCAN_RESULTS_BUCKET = 250;
+    public static final int MAX_TOTAL_PASSPOINT_APS_BUCKET = 50;
+    public static final int MAX_TOTAL_PASSPOINT_UNIQUE_ESS_BUCKET = 20;
+    public static final int MAX_PASSPOINT_APS_PER_UNIQUE_ESS_BUCKET = 50;
     private static final int CONNECT_TO_NETWORK_NOTIFICATION_ACTION_KEY_MULTIPLIER = 1000;
     private Clock mClock;
     private boolean mScreenOn;
@@ -161,6 +168,13 @@
     private int mNumOpenNetworkConnectMessageFailedToSend = 0;
     private int mNumOpenNetworkRecommendationUpdates = 0;
 
+    private final SparseIntArray mObservedHotspotR1ApInScanHistogram = new SparseIntArray();
+    private final SparseIntArray mObservedHotspotR2ApInScanHistogram = new SparseIntArray();
+    private final SparseIntArray mObservedHotspotR1EssInScanHistogram = new SparseIntArray();
+    private final SparseIntArray mObservedHotspotR2EssInScanHistogram = new SparseIntArray();
+    private final SparseIntArray mObservedHotspotR1ApsPerEssInScanHistogram = new SparseIntArray();
+    private final SparseIntArray mObservedHotspotR2ApsPerEssInScanHistogram = new SparseIntArray();
+
     class RouterFingerPrint {
         private WifiMetricsProto.RouterFingerPrint mRouterFingerPrintProto;
         RouterFingerPrint() {
@@ -1192,6 +1206,10 @@
             Set<PasspointProvider> savedPasspointProviderProfiles =
                     new HashSet<PasspointProvider>();
             int savedPasspointProviderBssids = 0;
+            int passpointR1Aps = 0;
+            int passpointR2Aps = 0;
+            Map<ANQPNetworkKey, Integer> passpointR1UniqueEss = new HashMap<>();
+            Map<ANQPNetworkKey, Integer> passpointR2UniqueEss = new HashMap<>();
             for (ScanDetail scanDetail : scanDetails) {
                 NetworkDetail networkDetail = scanDetail.getNetworkDetail();
                 ScanResult scanResult = scanDetail.getScanResult();
@@ -1205,6 +1223,36 @@
                     providerMatch =
                             mPasspointManager.matchProvider(scanResult);
                     passpointProvider = providerMatch != null ? providerMatch.first : null;
+
+                    if (networkDetail.getHSRelease() == NetworkDetail.HSRelease.R1) {
+                        passpointR1Aps++;
+                    } else if (networkDetail.getHSRelease() == NetworkDetail.HSRelease.R2) {
+                        passpointR2Aps++;
+                    }
+
+                    long bssid = 0;
+                    boolean validBssid = false;
+                    try {
+                        bssid = Utils.parseMac(scanResult.BSSID);
+                        validBssid = true;
+                    } catch (IllegalArgumentException e) {
+                        Log.e(TAG,
+                                "Invalid BSSID provided in the scan result: " + scanResult.BSSID);
+                    }
+                    if (validBssid) {
+                        ANQPNetworkKey uniqueEss = ANQPNetworkKey.buildKey(scanResult.SSID, bssid,
+                                scanResult.hessid, networkDetail.getAnqpDomainID());
+                        if (networkDetail.getHSRelease() == NetworkDetail.HSRelease.R1) {
+                            Integer countObj = passpointR1UniqueEss.get(uniqueEss);
+                            int count = countObj == null ? 0 : countObj;
+                            passpointR1UniqueEss.put(uniqueEss, count + 1);
+                        } else if (networkDetail.getHSRelease() == NetworkDetail.HSRelease.R2) {
+                            Integer countObj = passpointR2UniqueEss.get(uniqueEss);
+                            int count = countObj == null ? 0 : countObj;
+                            passpointR2UniqueEss.put(uniqueEss, count + 1);
+                        }
+                    }
+
                 }
                 ssids.add(matchInfo);
                 bssids++;
@@ -1245,6 +1293,18 @@
                     savedPasspointProviderProfiles.size());
             incrementBssid(mAvailableSavedPasspointProviderBssidsInScanHistogram,
                     savedPasspointProviderBssids);
+            incrementTotalPasspointAps(mObservedHotspotR1ApInScanHistogram, passpointR1Aps);
+            incrementTotalPasspointAps(mObservedHotspotR2ApInScanHistogram, passpointR2Aps);
+            incrementTotalUniquePasspointEss(mObservedHotspotR1EssInScanHistogram,
+                    passpointR1UniqueEss.size());
+            incrementTotalUniquePasspointEss(mObservedHotspotR2EssInScanHistogram,
+                    passpointR2UniqueEss.size());
+            for (Integer count : passpointR1UniqueEss.values()) {
+                incrementPasspointPerUniqueEss(mObservedHotspotR1ApsPerEssInScanHistogram, count);
+            }
+            for (Integer count : passpointR2UniqueEss.values()) {
+                incrementPasspointPerUniqueEss(mObservedHotspotR2ApsPerEssInScanHistogram, count);
+            }
         }
     }
 
@@ -1561,6 +1621,19 @@
                         + mNumOpenNetworkRecommendationUpdates);
                 pw.println("mWifiLogProto.numOpenNetworkConnectMessageFailedToSend="
                         + mNumOpenNetworkConnectMessageFailedToSend);
+
+                pw.println("mWifiLogProto.observedHotspotR1ApInScanHistogram="
+                        + mObservedHotspotR1ApInScanHistogram);
+                pw.println("mWifiLogProto.observedHotspotR2ApInScanHistogram="
+                        + mObservedHotspotR2ApInScanHistogram);
+                pw.println("mWifiLogProto.observedHotspotR1EssInScanHistogram="
+                        + mObservedHotspotR1EssInScanHistogram);
+                pw.println("mWifiLogProto.observedHotspotR2EssInScanHistogram="
+                        + mObservedHotspotR2EssInScanHistogram);
+                pw.println("mWifiLogProto.observedHotspotR1ApsPerEssInScanHistogram="
+                        + mObservedHotspotR1ApsPerEssInScanHistogram);
+                pw.println("mWifiLogProto.observedHotspotR2ApsPerEssInScanHistogram="
+                        + mObservedHotspotR2ApsPerEssInScanHistogram);
             }
         }
     }
@@ -1818,6 +1891,21 @@
                     mNumOpenNetworkRecommendationUpdates;
             mWifiLogProto.numOpenNetworkConnectMessageFailedToSend =
                     mNumOpenNetworkConnectMessageFailedToSend;
+
+            mWifiLogProto.observedHotspotR1ApsInScanHistogram =
+                    makeNumConnectableNetworksBucketArray(mObservedHotspotR1ApInScanHistogram);
+            mWifiLogProto.observedHotspotR2ApsInScanHistogram =
+                    makeNumConnectableNetworksBucketArray(mObservedHotspotR2ApInScanHistogram);
+            mWifiLogProto.observedHotspotR1EssInScanHistogram =
+                    makeNumConnectableNetworksBucketArray(mObservedHotspotR1EssInScanHistogram);
+            mWifiLogProto.observedHotspotR2EssInScanHistogram =
+                    makeNumConnectableNetworksBucketArray(mObservedHotspotR2EssInScanHistogram);
+            mWifiLogProto.observedHotspotR1ApsPerEssInScanHistogram =
+                    makeNumConnectableNetworksBucketArray(
+                            mObservedHotspotR1ApsPerEssInScanHistogram);
+            mWifiLogProto.observedHotspotR2ApsPerEssInScanHistogram =
+                    makeNumConnectableNetworksBucketArray(
+                            mObservedHotspotR2ApsPerEssInScanHistogram);
         }
     }
 
@@ -1872,6 +1960,12 @@
             mConnectToNetworkNotificationActionCount.clear();
             mNumOpenNetworkRecommendationUpdates = 0;
             mNumOpenNetworkConnectMessageFailedToSend = 0;
+            mObservedHotspotR1ApInScanHistogram.clear();
+            mObservedHotspotR2ApInScanHistogram.clear();
+            mObservedHotspotR1EssInScanHistogram.clear();
+            mObservedHotspotR2EssInScanHistogram.clear();
+            mObservedHotspotR1ApsPerEssInScanHistogram.clear();
+            mObservedHotspotR2ApsPerEssInScanHistogram.clear();
         }
     }
 
@@ -2272,6 +2366,15 @@
     private void incrementTotalScanSsids(SparseIntArray sia, int element) {
         increment(sia, Math.min(element, MAX_TOTAL_SCAN_RESULT_SSIDS_BUCKET));
     }
+    private void incrementTotalPasspointAps(SparseIntArray sia, int element) {
+        increment(sia, Math.min(element, MAX_TOTAL_PASSPOINT_APS_BUCKET));
+    }
+    private void incrementTotalUniquePasspointEss(SparseIntArray sia, int element) {
+        increment(sia, Math.min(element, MAX_TOTAL_PASSPOINT_UNIQUE_ESS_BUCKET));
+    }
+    private void incrementPasspointPerUniqueEss(SparseIntArray sia, int element) {
+        increment(sia, Math.min(element, MAX_PASSPOINT_APS_PER_UNIQUE_ESS_BUCKET));
+    }
     private void increment(SparseIntArray sia, int element) {
         int count = sia.get(element);
         sia.put(element, count + 1);
diff --git a/com/android/server/wifi/WifiNative.java b/com/android/server/wifi/WifiNative.java
index 0b1719d..35dec2e 100644
--- a/com/android/server/wifi/WifiNative.java
+++ b/com/android/server/wifi/WifiNative.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wifi;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.net.apf.ApfCapabilities;
 import android.net.wifi.IApInterface;
@@ -109,12 +110,12 @@
     * @return Pair of <Integer, IClientInterface> to indicate the status and the associated wificond
     * client interface binder handler (will be null on failure).
     */
-    public Pair<Integer, IClientInterface> setupForClientMode() {
+    public Pair<Integer, IClientInterface> setupForClientMode(@NonNull String ifaceName) {
         if (!startHalIfNecessary(true)) {
             Log.e(mTAG, "Failed to start HAL for client mode");
             return Pair.create(SETUP_FAILURE_HAL, null);
         }
-        IClientInterface iClientInterface = mWificondControl.setupDriverForClientMode();
+        IClientInterface iClientInterface = mWificondControl.setupDriverForClientMode(ifaceName);
         if (iClientInterface == null) {
             return Pair.create(SETUP_FAILURE_WIFICOND, null);
         }
@@ -130,12 +131,12 @@
      * @return Pair of <Integer, IApInterface> to indicate the status and the associated wificond
      * AP interface binder handler (will be null on failure).
      */
-    public Pair<Integer, IApInterface> setupForSoftApMode() {
+    public Pair<Integer, IApInterface> setupForSoftApMode(@NonNull String ifaceName) {
         if (!startHalIfNecessary(false)) {
             Log.e(mTAG, "Failed to start HAL for AP mode");
             return Pair.create(SETUP_FAILURE_HAL, null);
         }
-        IApInterface iApInterface = mWificondControl.setupDriverForSoftApMode();
+        IApInterface iApInterface = mWificondControl.setupDriverForSoftApMode(ifaceName);
         if (iApInterface == null) {
             return Pair.create(SETUP_FAILURE_WIFICOND, null);
         }
diff --git a/com/android/server/wifi/WifiServiceImpl.java b/com/android/server/wifi/WifiServiceImpl.java
index c83f6c6..0276ff4 100644
--- a/com/android/server/wifi/WifiServiceImpl.java
+++ b/com/android/server/wifi/WifiServiceImpl.java
@@ -1588,6 +1588,26 @@
     }
 
     /**
+     * Return the list of all matching Wifi configurations for this ScanResult.
+     *
+     * An empty list will be returned when no configurations are installed or if no configurations
+     * match the ScanResult.
+     *
+     * @param scanResult scanResult that represents the BSSID
+     * @return A list of {@link WifiConfiguration}
+     */
+    @Override
+    public List<WifiConfiguration> getAllMatchingWifiConfigs(ScanResult scanResult) {
+        enforceAccessPermission();
+        mLog.info("getMatchingPasspointConfigurations uid=%").c(Binder.getCallingUid()).flush();
+        if (!mContext.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_WIFI_PASSPOINT)) {
+            throw new UnsupportedOperationException("Passpoint not enabled");
+        }
+        return mWifiStateMachine.getAllMatchingWifiConfigs(scanResult, mWifiStateMachineChannel);
+    }
+
+    /**
      * Returns list of OSU (Online Sign-Up) providers associated with the given Passpoint network.
      *
      * @param scanResult scanResult of the Passpoint AP
diff --git a/com/android/server/wifi/WifiStateMachine.java b/com/android/server/wifi/WifiStateMachine.java
index 1f6cada..b7152ac 100644
--- a/com/android/server/wifi/WifiStateMachine.java
+++ b/com/android/server/wifi/WifiStateMachine.java
@@ -701,6 +701,9 @@
     /* Enable/Disable AutoJoin when associated */
     static final int CMD_ENABLE_AUTOJOIN_WHEN_ASSOCIATED                = BASE + 167;
 
+    /* Get all matching Passpoint configurations */
+    static final int CMD_GET_ALL_MATCHING_CONFIGS                       = BASE + 168;
+
     /**
      * Used to handle messages bounced between WifiStateMachine and IpManager.
      */
@@ -1937,6 +1940,15 @@
         return config;
     }
 
+    List<WifiConfiguration> getAllMatchingWifiConfigs(ScanResult scanResult,
+            AsyncChannel channel) {
+        Message resultMsg = channel.sendMessageSynchronously(CMD_GET_ALL_MATCHING_CONFIGS,
+                scanResult);
+        List<WifiConfiguration> configs = (List<WifiConfiguration>) resultMsg.obj;
+        resultMsg.recycle();
+        return configs;
+    }
+
     /**
      * Retrieve a list of {@link OsuProvider} associated with the given AP synchronously.
      *
@@ -4130,6 +4142,9 @@
                     mWifiDiagnostics.reportConnectionEvent(
                             (Long) message.obj, BaseWifiDiagnostics.CONNECTION_EVENT_FAILED);
                     break;
+                case CMD_GET_ALL_MATCHING_CONFIGS:
+                    replyToMessage(message, message.what, new ArrayList<WifiConfiguration>());
+                    break;
                 case 0:
                     // We want to notice any empty messages (with what == 0) that might crop up.
                     // For example, we may have recycled a message sent to multiple handlers.
@@ -4165,7 +4180,7 @@
             switch (message.what) {
                 case CMD_START_SUPPLICANT:
                     Pair<Integer, IClientInterface> statusAndInterface =
-                            mWifiNative.setupForClientMode();
+                            mWifiNative.setupForClientMode(mInterfaceName);
                     if (statusAndInterface.first == WifiNative.SETUP_SUCCESS) {
                         mClientInterface = statusAndInterface.second;
                     } else {
@@ -5493,6 +5508,10 @@
                 case CMD_ENABLE_P2P:
                     p2pSendMessage(WifiStateMachine.CMD_ENABLE_P2P);
                     break;
+                case CMD_GET_ALL_MATCHING_CONFIGS:
+                    replyToMessage(message, message.what,
+                            mPasspointManager.getAllMatchingWifiConfigs((ScanResult) message.obj));
+                    break;
                 default:
                     return NOT_HANDLED;
             }
@@ -6023,14 +6042,11 @@
     class ObtainingIpState extends State {
         @Override
         public void enter() {
-            WifiConfiguration currentConfig = getCurrentWifiConfiguration();
-            boolean isUsingStaticIp =
+            final WifiConfiguration currentConfig = getCurrentWifiConfiguration();
+            final boolean isUsingStaticIp =
                     (currentConfig.getIpAssignment() == IpConfiguration.IpAssignment.STATIC);
             if (mVerboseLoggingEnabled) {
-                String key = "";
-                if (getCurrentWifiConfiguration() != null) {
-                    key = getCurrentWifiConfiguration().configKey();
-                }
+                final String key = currentConfig.configKey();
                 log("enter ObtainingIpState netId=" + Integer.toString(mLastNetworkId)
                         + " " + key + " "
                         + " roam=" + mIsAutoRoaming
@@ -6070,12 +6086,16 @@
                 prov = IpManager.buildProvisioningConfiguration()
                             .withPreDhcpAction()
                             .withApfCapabilities(mWifiNative.getApfCapabilities())
+                            .withNetwork(getCurrentNetwork())
+                            .withDisplayName(currentConfig.SSID)
                             .build();
             } else {
                 StaticIpConfiguration staticIpConfig = currentConfig.getStaticIpConfiguration();
                 prov = IpManager.buildProvisioningConfiguration()
                             .withStaticConfiguration(staticIpConfig)
                             .withApfCapabilities(mWifiNative.getApfCapabilities())
+                            .withNetwork(getCurrentNetwork())
+                            .withDisplayName(currentConfig.SSID)
                             .build();
             }
             mIpManager.startProvisioning(prov);
@@ -6954,7 +6974,8 @@
             mMode = config.getTargetMode();
 
             IApInterface apInterface = null;
-            Pair<Integer, IApInterface> statusAndInterface = mWifiNative.setupForSoftApMode();
+            Pair<Integer, IApInterface> statusAndInterface =
+                    mWifiNative.setupForSoftApMode(mInterfaceName);
             if (statusAndInterface.first == WifiNative.SETUP_SUCCESS) {
                 apInterface = statusAndInterface.second;
             } else {
diff --git a/com/android/server/wifi/WifiStateMachinePrime.java b/com/android/server/wifi/WifiStateMachinePrime.java
index 2006884..cd1948f 100644
--- a/com/android/server/wifi/WifiStateMachinePrime.java
+++ b/com/android/server/wifi/WifiStateMachinePrime.java
@@ -290,7 +290,8 @@
                 }
 
                 try {
-                    mApInterface = mWificond.createApInterface();
+                    mApInterface = mWificond.createApInterface(
+                            mWifiInjector.getWifiNative().getInterfaceName());
                 } catch (RemoteException e1) { }
 
                 if (mApInterface == null) {
diff --git a/com/android/server/wifi/WificondControl.java b/com/android/server/wifi/WificondControl.java
index b6104a8..df4e785 100644
--- a/com/android/server/wifi/WificondControl.java
+++ b/com/android/server/wifi/WificondControl.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wifi;
 
+import android.annotation.NonNull;
 import android.net.wifi.IApInterface;
 import android.net.wifi.IClientInterface;
 import android.net.wifi.IPnoScanEvent;
@@ -133,7 +134,7 @@
     * @return An IClientInterface as wificond client interface binder handler.
     * Returns null on failure.
     */
-    public IClientInterface setupDriverForClientMode() {
+    public IClientInterface setupDriverForClientMode(@NonNull String ifaceName) {
         Log.d(TAG, "Setting up driver for client mode");
         mWificond = mWifiInjector.makeWificond();
         if (mWificond == null) {
@@ -143,7 +144,7 @@
 
         IClientInterface clientInterface = null;
         try {
-            clientInterface = mWificond.createClientInterface();
+            clientInterface = mWificond.createClientInterface(ifaceName);
         } catch (RemoteException e1) {
             Log.e(TAG, "Failed to get IClientInterface due to remote exception");
             return null;
@@ -181,7 +182,7 @@
     * @return An IApInterface as wificond Ap interface binder handler.
     * Returns null on failure.
     */
-    public IApInterface setupDriverForSoftApMode() {
+    public IApInterface setupDriverForSoftApMode(@NonNull String ifaceName) {
         Log.d(TAG, "Setting up driver for soft ap mode");
         mWificond = mWifiInjector.makeWificond();
         if (mWificond == null) {
@@ -191,7 +192,7 @@
 
         IApInterface apInterface = null;
         try {
-            apInterface = mWificond.createApInterface();
+            apInterface = mWificond.createApInterface(ifaceName);
         } catch (RemoteException e1) {
             Log.e(TAG, "Failed to get IApInterface due to remote exception");
             return null;
diff --git a/com/android/server/wifi/hotspot2/PasspointManager.java b/com/android/server/wifi/hotspot2/PasspointManager.java
index 5d79ba4..3580c83 100644
--- a/com/android/server/wifi/hotspot2/PasspointManager.java
+++ b/com/android/server/wifi/hotspot2/PasspointManager.java
@@ -325,6 +325,44 @@
      * @return A pair of {@link PasspointProvider} and match status.
      */
     public Pair<PasspointProvider, PasspointMatch> matchProvider(ScanResult scanResult) {
+        List<Pair<PasspointProvider, PasspointMatch>> allMatches = getAllMatchedProviders(
+                scanResult);
+        if (allMatches == null) {
+            return null;
+        }
+
+        Pair<PasspointProvider, PasspointMatch> bestMatch = null;
+        for (Pair<PasspointProvider, PasspointMatch> match : allMatches) {
+            if (match.second == PasspointMatch.HomeProvider) {
+                bestMatch = match;
+                break;
+            }
+            if (match.second == PasspointMatch.RoamingProvider && bestMatch == null) {
+                bestMatch = match;
+            }
+        }
+
+        if (bestMatch != null) {
+            Log.d(TAG, String.format("Matched %s to %s as %s", scanResult.SSID,
+                    bestMatch.first.getConfig().getHomeSp().getFqdn(),
+                    bestMatch.second == PasspointMatch.HomeProvider ? "Home Provider"
+                            : "Roaming Provider"));
+        } else {
+            Log.d(TAG, "Match not found for " + scanResult.SSID);
+        }
+        return bestMatch;
+    }
+
+    /**
+     * Return a list of all providers that can provide service through the given AP.
+     *
+     * @param scanResult The scan result associated with the AP
+     * @return a list of pairs of {@link PasspointProvider} and match status.
+     */
+    public List<Pair<PasspointProvider, PasspointMatch>> getAllMatchedProviders(
+            ScanResult scanResult) {
+        List<Pair<PasspointProvider, PasspointMatch>> allMatches = new ArrayList<>();
+
         // Retrieve the relevant information elements, mainly Roaming Consortium IE and Hotspot 2.0
         // Vendor Specific IE.
         InformationElementUtil.RoamingConsortium roamingConsortium =
@@ -338,7 +376,7 @@
             bssid = Utils.parseMac(scanResult.BSSID);
         } catch (IllegalArgumentException e) {
             Log.e(TAG, "Invalid BSSID provided in the scan result: " + scanResult.BSSID);
-            return null;
+            return allMatches;
         }
         ANQPNetworkKey anqpKey = ANQPNetworkKey.buildKey(scanResult.SSID, bssid, scanResult.hessid,
                 vsa.anqpDomainID);
@@ -349,30 +387,30 @@
                     roamingConsortium.anqpOICount > 0,
                     vsa.hsRelease  == NetworkDetail.HSRelease.R2);
             Log.d(TAG, "ANQP entry not found for: " + anqpKey);
-            return null;
+            return allMatches;
         }
 
-        Pair<PasspointProvider, PasspointMatch> bestMatch = null;
         for (Map.Entry<String, PasspointProvider> entry : mProviders.entrySet()) {
             PasspointProvider provider = entry.getValue();
             PasspointMatch matchStatus = provider.match(anqpEntry.getElements());
-            if (matchStatus == PasspointMatch.HomeProvider) {
-                bestMatch = Pair.create(provider, matchStatus);
-                break;
-            }
-            if (matchStatus == PasspointMatch.RoamingProvider && bestMatch == null) {
-                bestMatch = Pair.create(provider, matchStatus);
+            if (matchStatus == PasspointMatch.HomeProvider
+                    || matchStatus == PasspointMatch.RoamingProvider) {
+                allMatches.add(Pair.create(provider, matchStatus));
             }
         }
-        if (bestMatch != null) {
-            Log.d(TAG, String.format("Matched %s to %s as %s", scanResult.SSID,
-                    bestMatch.first.getConfig().getHomeSp().getFqdn(),
-                    bestMatch.second == PasspointMatch.HomeProvider ? "Home Provider"
-                            : "Roaming Provider"));
+
+        if (allMatches.size() != 0) {
+            for (Pair<PasspointProvider, PasspointMatch> match : allMatches) {
+                Log.d(TAG, String.format("Matched %s to %s as %s", scanResult.SSID,
+                        match.first.getConfig().getHomeSp().getFqdn(),
+                        match.second == PasspointMatch.HomeProvider ? "Home Provider"
+                                : "Roaming Provider"));
+            }
         } else {
-            Log.d(TAG, "Match not found for " + scanResult.SSID);
+            Log.d(TAG, "No matches not found for " + scanResult.SSID);
         }
-        return bestMatch;
+
+        return allMatches;
     }
 
     /**
@@ -497,6 +535,38 @@
     }
 
     /**
+     * Match the given WiFi AP to all installed Passpoint configurations. Return the list of all
+     * matching configurations (or an empty list if none).
+     *
+     * @param scanResult The scan result of the given AP
+     * @return List of {@link WifiConfiguration}
+     */
+    public List<WifiConfiguration> getAllMatchingWifiConfigs(ScanResult scanResult) {
+        if (scanResult == null) {
+            Log.e(TAG, "Attempt to get matching config for a null ScanResult");
+            return new ArrayList<WifiConfiguration>();
+        }
+        if (!scanResult.isPasspointNetwork()) {
+            Log.e(TAG, "Attempt to get matching config for a non-Passpoint AP");
+            return new ArrayList<WifiConfiguration>();
+        }
+
+        List<Pair<PasspointProvider, PasspointMatch>> matchedProviders = getAllMatchedProviders(
+                scanResult);
+        List<WifiConfiguration> configs = new ArrayList<>();
+        for (Pair<PasspointProvider, PasspointMatch> matchedProvider : matchedProviders) {
+            WifiConfiguration config = matchedProvider.first.getWifiConfig();
+            config.SSID = ScanResultUtil.createQuotedSSID(scanResult.SSID);
+            if (matchedProvider.second == PasspointMatch.HomeProvider) {
+                config.isHomeProviderNetwork = true;
+            }
+            configs.add(config);
+        }
+
+        return configs;
+    }
+
+    /**
      * Return the list of Hosspot 2.0 OSU (Online Sign-Up) providers associated with the given
      * AP.
      *
diff --git a/com/android/server/wifi/rtt/RttNative.java b/com/android/server/wifi/rtt/RttNative.java
index d30fe91..21aa1ce 100644
--- a/com/android/server/wifi/rtt/RttNative.java
+++ b/com/android/server/wifi/rtt/RttNative.java
@@ -23,7 +23,6 @@
 import android.hardware.wifi.V1_0.RttPeerType;
 import android.hardware.wifi.V1_0.RttPreamble;
 import android.hardware.wifi.V1_0.RttResult;
-import android.hardware.wifi.V1_0.RttStatus;
 import android.hardware.wifi.V1_0.RttType;
 import android.hardware.wifi.V1_0.WifiChannelWidthInMhz;
 import android.hardware.wifi.V1_0.WifiStatus;
@@ -31,7 +30,6 @@
 import android.net.wifi.ScanResult;
 import android.net.wifi.rtt.RangingRequest;
 import android.net.wifi.rtt.RangingResult;
-import android.net.wifi.rtt.RangingResultCallback;
 import android.os.RemoteException;
 import android.util.Log;
 
@@ -128,15 +126,7 @@
         if (VDBG) Log.v(TAG, "onResults: cmdId=" + cmdId + ", # of results=" + halResults.size());
         List<RangingResult> results = new ArrayList<>(halResults.size());
 
-        for (RttResult halResult: halResults) {
-            results.add(new RangingResult(
-                    halResult.status == RttStatus.SUCCESS ? RangingResultCallback.STATUS_SUCCESS
-                            : RangingResultCallback.STATUS_FAIL, halResult.addr,
-                    halResult.distanceInMm / 10, halResult.distanceSdInMm / 10, halResult.rssi,
-                    halResult.timeStampInUs));
-        }
-
-        mRttService.onRangingResults(cmdId, results);
+        mRttService.onRangingResults(cmdId, halResults);
     }
 
     private static ArrayList<RttConfig> convertRangingRequestToRttConfigs(RangingRequest request) {
@@ -145,18 +135,17 @@
         // Skipping any configurations which have an error (printing out a message).
         // The caller will only get results for valid configurations.
         for (RangingRequest.RttPeer peer: request.mRttPeers) {
+            RttConfig config = new RttConfig();
+
             if (peer instanceof RangingRequest.RttPeerAp) {
                 ScanResult scanResult = ((RangingRequest.RttPeerAp) peer).scanResult;
-                RttConfig config = new RttConfig();
 
                 byte[] addr = NativeUtil.macAddressToByteArray(scanResult.BSSID);
                 if (addr.length != config.addr.length) {
                     Log.e(TAG, "Invalid configuration: unexpected BSSID length -- " + scanResult);
                     continue;
                 }
-                for (int i = 0; i < config.addr.length; ++i) {
-                    config.addr[i] = addr[i];
-                }
+                System.arraycopy(addr, 0, config.addr, 0, config.addr.length);
 
                 try {
                     config.type =
@@ -189,13 +178,40 @@
                     Log.e(TAG, "Invalid configuration: " + e.getMessage());
                     continue;
                 }
+            } else if (peer instanceof RangingRequest.RttPeerAware) {
+                RangingRequest.RttPeerAware rttPeerAware = (RangingRequest.RttPeerAware) peer;
 
-                rttConfigs.add(config);
+                if (rttPeerAware.peerMacAddress == null
+                        || rttPeerAware.peerMacAddress.length != config.addr.length) {
+                    Log.e(TAG, "Invalid configuration: null MAC or incorrect length");
+                    continue;
+                }
+                System.arraycopy(rttPeerAware.peerMacAddress, 0, config.addr, 0,
+                        config.addr.length);
+
+                config.type = RttType.TWO_SIDED;
+                config.peer = RttPeerType.NAN;
+                config.channel.width = WifiChannelWidthInMhz.WIDTH_80;
+                config.channel.centerFreq = 5200;
+                config.channel.centerFreq0 = 5210;
+                config.channel.centerFreq1 = 0;
+                config.burstPeriod = 0;
+                config.numBurst = 0;
+                config.numFramesPerBurst = 5;
+                config.numRetriesPerRttFrame = 3;
+                config.numRetriesPerFtmr = 3;
+                config.mustRequestLci = false;
+                config.mustRequestLcr = false;
+                config.burstDuration = 15;
+                config.preamble = RttPreamble.VHT;
+                config.bw = RttBw.BW_80MHZ;
             } else {
                 Log.e(TAG, "convertRangingRequestToRttConfigs: unknown request type -- "
                         + peer.getClass().getCanonicalName());
                 return null;
             }
+
+            rttConfigs.add(config);
         }
 
 
diff --git a/com/android/server/wifi/rtt/RttService.java b/com/android/server/wifi/rtt/RttService.java
index 71e8a0a..f248f72 100644
--- a/com/android/server/wifi/rtt/RttService.java
+++ b/com/android/server/wifi/rtt/RttService.java
@@ -17,7 +17,9 @@
 package com.android.server.wifi.rtt;
 
 import android.content.Context;
+import android.net.wifi.aware.IWifiAwareManager;
 import android.os.HandlerThread;
+import android.os.ServiceManager;
 import android.util.Log;
 
 import com.android.server.SystemService;
@@ -31,10 +33,12 @@
  */
 public class RttService extends SystemService {
     private static final String TAG = "RttService";
+    private Context mContext;
     private RttServiceImpl mImpl;
 
     public RttService(Context context) {
         super(context);
+        mContext = context;
         mImpl = new RttServiceImpl(context);
     }
 
@@ -60,8 +64,11 @@
             WifiNative wifiNative = wifiInjector.getWifiNative();
             WifiPermissionsUtil wifiPermissionsUtil = wifiInjector.getWifiPermissionsUtil();
 
+            IWifiAwareManager awareBinder = (IWifiAwareManager) ServiceManager.getService(
+                    Context.WIFI_AWARE_SERVICE);
+
             RttNative rttNative = new RttNative(mImpl, halDeviceManager, wifiNative);
-            mImpl.start(handlerThread.getLooper(), rttNative, wifiPermissionsUtil);
+            mImpl.start(handlerThread.getLooper(), awareBinder, rttNative, wifiPermissionsUtil);
         }
     }
 }
diff --git a/com/android/server/wifi/rtt/RttServiceImpl.java b/com/android/server/wifi/rtt/RttServiceImpl.java
index d248768..9a0a1af 100644
--- a/com/android/server/wifi/rtt/RttServiceImpl.java
+++ b/com/android/server/wifi/rtt/RttServiceImpl.java
@@ -18,6 +18,11 @@
 
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.hardware.wifi.V1_0.RttResult;
+import android.hardware.wifi.V1_0.RttStatus;
+import android.net.wifi.aware.IWifiAwareMacAddressProvider;
+import android.net.wifi.aware.IWifiAwareManager;
+import android.net.wifi.aware.PeerHandle;
 import android.net.wifi.rtt.IRttCallback;
 import android.net.wifi.rtt.IWifiRttManager;
 import android.net.wifi.rtt.RangingRequest;
@@ -37,12 +42,13 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.HashSet;
+import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.ListIterator;
-import java.util.Set;
+import java.util.Map;
 
 /**
  * Implementation of the IWifiRttManager AIDL interface and of the RttService state manager.
@@ -52,6 +58,7 @@
     private static final boolean VDBG = true; // STOPSHIP if true
 
     private final Context mContext;
+    private IWifiAwareManager mAwareBinder;
     private WifiPermissionsUtil mWifiPermissionsUtil;
 
     private RttServiceSynchronized mRttServiceSynchronized;
@@ -69,10 +76,13 @@
      * Initializes the RTT service (usually with objects from an injector).
      *
      * @param looper The looper on which to synchronize operations.
+     * @param awareBinder The Wi-Fi Aware service (binder) if supported on the system.
      * @param rttNative The Native interface to the HAL.
      * @param wifiPermissionsUtil Utility for permission checks.
      */
-    public void start(Looper looper, RttNative rttNative, WifiPermissionsUtil wifiPermissionsUtil) {
+    public void start(Looper looper, IWifiAwareManager awareBinder, RttNative rttNative,
+            WifiPermissionsUtil wifiPermissionsUtil) {
+        mAwareBinder = awareBinder;
         mWifiPermissionsUtil = wifiPermissionsUtil;
         mRttServiceSynchronized = new RttServiceSynchronized(looper, rttNative);
     }
@@ -107,20 +117,10 @@
         if (request == null || request.mRttPeers == null || request.mRttPeers.size() == 0) {
             throw new IllegalArgumentException("Request must not be null or empty");
         }
-        for (RangingRequest.RttPeer peer: request.mRttPeers) {
-            if (peer == null) {
-                throw new IllegalArgumentException(
-                        "Request must not contain empty peer specifications");
-            }
-            if (!(peer instanceof RangingRequest.RttPeerAp)) {
-                throw new IllegalArgumentException(
-                        "Request contains unknown peer specification types");
-            }
-        }
         if (callback == null) {
             throw new IllegalArgumentException("Callback must not be null");
         }
-        request.enforceValidity();
+        request.enforceValidity(mAwareBinder != null);
 
         final int uid = getMockableCallingUid();
 
@@ -158,7 +158,7 @@
      * Called by HAL to report ranging results. Called on HAL thread - needs to post to local
      * thread.
      */
-    public void onRangingResults(int cmdId, List<RangingResult> results) {
+    public void onRangingResults(int cmdId, List<RttResult> results) {
         if (VDBG) Log.v(TAG, "onRangingResults: cmdId=" + cmdId);
         mRttServiceSynchronized.mHandler.post(() -> {
             mRttServiceSynchronized.onRangingResults(cmdId, results);
@@ -251,10 +251,21 @@
                 Log.v(TAG, "RttServiceSynchronized.queueRangingRequest: newRequest=" + newRequest);
             }
 
-            executeNextRangingRequestIfPossible();
+            executeNextRangingRequestIfPossible(false);
         }
 
-        private void executeNextRangingRequestIfPossible() {
+        private void executeNextRangingRequestIfPossible(boolean popFirst) {
+            if (VDBG) Log.v(TAG, "executeNextRangingRequestIfPossible: popFirst=" + popFirst);
+
+            if (popFirst) {
+                if (mRttRequestQueue.size() == 0) {
+                    Log.w(TAG, "executeNextRangingRequestIfPossible: pop requested - but empty "
+                            + "queue!? Ignoring pop.");
+                } else {
+                    mRttRequestQueue.remove(0);
+                }
+            }
+
             if (mRttRequestQueue.size() == 0) {
                 if (VDBG) Log.v(TAG, "executeNextRangingRequestIfPossible: no requests pending");
                 return;
@@ -278,21 +289,114 @@
                 Log.v(TAG, "RttServiceSynchronized.startRanging: nextRequest=" + nextRequest);
             }
 
+            if (processAwarePeerHandles(nextRequest)) {
+                if (VDBG) {
+                    Log.v(TAG, "RttServiceSynchronized.startRanging: deferring due to PeerHandle "
+                            + "Aware requests");
+                }
+                return;
+            }
+
             if (!mRttNative.rangeRequest(nextRequest.cmdId, nextRequest.request)) {
                 Log.w(TAG, "RttServiceSynchronized.startRanging: native rangeRequest call failed");
                 try {
-                    nextRequest.callback.onRangingResults(RangingResultCallback.STATUS_FAIL, null);
+                    nextRequest.callback.onRangingFailure(RangingResultCallback.STATUS_CODE_FAIL);
                 } catch (RemoteException e) {
                     Log.e(TAG, "RttServiceSynchronized.startRanging: HAL request failed, callback "
                             + "failed -- " + e);
                 }
-
-                mRttRequestQueue.remove(0);
-                executeNextRangingRequestIfPossible();
+                executeNextRangingRequestIfPossible(true);
             }
         }
 
-        private void onRangingResults(int cmdId, List<RangingResult> results) {
+        /**
+         * Check request for any PeerHandle Aware requests. If there are any: issue requests to
+         * translate the peer ID to a MAC address and abort current execution of the range request.
+         * The request will be re-attempted when response is received.
+         *
+         * In cases of failure: pop the current request and execute the next one. Failures:
+         * - Not able to connect to remote service (unlikely)
+         * - Request already processed: but we're missing information
+         *
+         * @return true if need to abort execution, false otherwise.
+         */
+        private boolean processAwarePeerHandles(RttRequestInfo request) {
+            List<Integer> peerIdsNeedingTranslation = new ArrayList<>();
+            for (RangingRequest.RttPeer rttPeer: request.request.mRttPeers) {
+                if (rttPeer instanceof RangingRequest.RttPeerAware) {
+                    RangingRequest.RttPeerAware awarePeer = (RangingRequest.RttPeerAware) rttPeer;
+                    if (awarePeer.peerHandle != null && awarePeer.peerMacAddress == null) {
+                        peerIdsNeedingTranslation.add(awarePeer.peerHandle.peerId);
+                    }
+                }
+            }
+
+            if (peerIdsNeedingTranslation.size() == 0) {
+                return false;
+            }
+
+            if (request.peerHandlesTranslated) {
+                Log.w(TAG, "processAwarePeerHandles: request=" + request
+                        + ": PeerHandles translated - but information still missing!?");
+                try {
+                    request.callback.onRangingFailure(RangingResultCallback.STATUS_CODE_FAIL);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "processAwarePeerHandles: onRangingResults failure -- " + e);
+                }
+                executeNextRangingRequestIfPossible(true);
+                return true; // an abort because we removed request and are executing next one
+            }
+
+            request.peerHandlesTranslated = true;
+            try {
+                mAwareBinder.requestMacAddresses(request.uid, peerIdsNeedingTranslation,
+                        new IWifiAwareMacAddressProvider.Stub() {
+                            @Override
+                            public void macAddress(Map peerIdToMacMap) {
+                                // ASYNC DOMAIN
+                                mHandler.post(() -> {
+                                    // BACK TO SYNC DOMAIN
+                                    processReceivedAwarePeerMacAddresses(request, peerIdToMacMap);
+                                });
+                            }
+                        });
+            } catch (RemoteException e1) {
+                Log.e(TAG,
+                        "processAwarePeerHandles: exception while calling requestMacAddresses -- "
+                                + e1 + ", aborting request=" + request);
+                try {
+                    request.callback.onRangingFailure(RangingResultCallback.STATUS_CODE_FAIL);
+                } catch (RemoteException e2) {
+                    Log.e(TAG, "processAwarePeerHandles: onRangingResults failure -- " + e2);
+                }
+                executeNextRangingRequestIfPossible(true);
+                return true; // an abort because we removed request and are executing next one
+            }
+
+            return true; // a deferral
+        }
+
+        private void processReceivedAwarePeerMacAddresses(RttRequestInfo request,
+                Map<Integer, byte[]> peerIdToMacMap) {
+            if (VDBG) {
+                Log.v(TAG, "processReceivedAwarePeerMacAddresses: request=" + request
+                        + ", peerIdToMacMap=" + peerIdToMacMap);
+            }
+
+            for (RangingRequest.RttPeer rttPeer: request.request.mRttPeers) {
+                if (rttPeer instanceof RangingRequest.RttPeerAware) {
+                    RangingRequest.RttPeerAware awarePeer = (RangingRequest.RttPeerAware) rttPeer;
+                    if (awarePeer.peerHandle != null && awarePeer.peerMacAddress == null) {
+                        awarePeer.peerMacAddress = peerIdToMacMap.get(awarePeer.peerHandle.peerId);
+                    }
+                }
+            }
+
+            // run request again
+            startRanging(request);
+        }
+
+        private void onRangingResults(int cmdId, List<RttResult> results) {
             if (mRttRequestQueue.size() == 0) {
                 Log.e(TAG, "RttServiceSynchronized.onRangingResults: no current RTT request "
                         + "pending!?");
@@ -316,14 +420,18 @@
                     topOfQueueRequest.callingPackage, topOfQueueRequest.uid);
             try {
                 if (permissionGranted) {
-                    addMissingEntries(topOfQueueRequest.request, results);
-                    topOfQueueRequest.callback.onRangingResults(
-                            RangingResultCallback.STATUS_SUCCESS, results);
+                    List<RangingResult> finalResults = postProcessResults(topOfQueueRequest.request,
+                            results);
+                    if (VDBG) {
+                        Log.v(TAG, "RttServiceSynchronized.onRangingResults: finalResults="
+                                + finalResults);
+                    }
+                    topOfQueueRequest.callback.onRangingResults(finalResults);
                 } else {
                     Log.w(TAG, "RttServiceSynchronized.onRangingResults: location permission "
                             + "revoked - not forwarding results");
-                    topOfQueueRequest.callback.onRangingResults(RangingResultCallback.STATUS_FAIL,
-                            null);
+                    topOfQueueRequest.callback.onRangingFailure(
+                            RangingResultCallback.STATUS_CODE_FAIL);
                 }
             } catch (RemoteException e) {
                 Log.e(TAG,
@@ -334,39 +442,68 @@
             // done with the binder.
             topOfQueueRequest.binder.unlinkToDeath(topOfQueueRequest.dr, 0);
 
-            mRttRequestQueue.remove(0);
-            executeNextRangingRequestIfPossible();
+            executeNextRangingRequestIfPossible(true);
         }
 
         /*
-         * Make sure the results contain an entry for each request. Add results with FAIL status
-         * if missing.
+         * Post process the results:
+         * - For requests without results: add FAILED results
+         * - For Aware requests using PeerHandle: replace MAC address with PeerHandle
+         * - Effectively: throws away results which don't match requests
          */
-        private void addMissingEntries(RangingRequest request,
-                List<RangingResult> results) {
-            Set<String> resultEntries = new HashSet<>(results.size());
-            for (RangingResult result: results) {
-                resultEntries.add(new String(HexEncoding.encode(result.getMacAddress())));
+        private List<RangingResult> postProcessResults(RangingRequest request,
+                List<RttResult> results) {
+            Map<String, RttResult> resultEntries = new HashMap<>();
+            for (RttResult result: results) {
+                resultEntries.put(new String(HexEncoding.encode(result.addr)), result);
             }
 
+            List<RangingResult> finalResults = new ArrayList<>(request.mRttPeers.size());
+
             for (RangingRequest.RttPeer peer: request.mRttPeers) {
                 byte[] addr;
                 if (peer instanceof RangingRequest.RttPeerAp) {
                     addr = NativeUtil.macAddressToByteArray(
                             ((RangingRequest.RttPeerAp) peer).scanResult.BSSID);
+                } else if (peer instanceof RangingRequest.RttPeerAware) {
+                    addr = ((RangingRequest.RttPeerAware) peer).peerMacAddress;
                 } else {
+                    Log.w(TAG, "postProcessResults: unknown peer type -- " + peer.getClass());
                     continue;
                 }
-                String canonicString = new String(HexEncoding.encode(addr));
 
-                if (!resultEntries.contains(canonicString)) {
+                RttResult resultForRequest = resultEntries.get(
+                        new String(HexEncoding.encode(addr)));
+                if (resultForRequest == null) {
                     if (VDBG) {
-                        Log.v(TAG, "padRangingResultsWithMissingResults: missing=" + canonicString);
+                        Log.v(TAG, "postProcessResults: missing=" + new String(
+                                HexEncoding.encode(addr)));
                     }
-                    results.add(new RangingResult(RangingResultCallback.STATUS_FAIL, addr, 0, 0, 0,
-                            0));
+                    finalResults.add(
+                            new RangingResult(RangingResult.STATUS_FAIL, addr, 0, 0, 0, 0));
+                } else {
+                    int status = resultForRequest.status == RttStatus.SUCCESS
+                            ? RangingResult.STATUS_SUCCESS : RangingResult.STATUS_FAIL;
+                    PeerHandle peerHandle = null;
+                    if (peer instanceof RangingRequest.RttPeerAware) {
+                        peerHandle = ((RangingRequest.RttPeerAware) peer).peerHandle;
+                    }
+
+                    if (peerHandle == null) {
+                        finalResults.add(
+                                new RangingResult(status, addr, resultForRequest.distanceInMm,
+                                        resultForRequest.distanceSdInMm, resultForRequest.rssi,
+                                        resultForRequest.timeStampInUs));
+                    } else {
+                        finalResults.add(
+                                new RangingResult(status, peerHandle, resultForRequest.distanceInMm,
+                                        resultForRequest.distanceSdInMm, resultForRequest.rssi,
+                                        resultForRequest.timeStampInUs));
+                    }
                 }
             }
+
+            return finalResults;
         }
 
         // dump call (asynchronous most likely)
@@ -387,13 +524,15 @@
         public IRttCallback callback;
 
         public int cmdId = 0; // uninitialized cmdId value
+        public boolean peerHandlesTranslated = false;
 
         @Override
         public String toString() {
             return new StringBuilder("RttRequestInfo: uid=").append(uid).append(", binder=").append(
                     binder).append(", dr=").append(dr).append(", callingPackage=").append(
                     callingPackage).append(", request=").append(request.toString()).append(
-                    ", callback=").append(callback).append(", cmdId=").append(cmdId).toString();
+                    ", callback=").append(callback).append(", cmdId=").append(cmdId).append(
+                    ", peerHandlesTranslated=").append(peerHandlesTranslated).toString();
         }
     }
 }
diff --git a/com/android/server/wifi/scanner/WificondScannerImpl.java b/com/android/server/wifi/scanner/WificondScannerImpl.java
index fb878e6..10fc8e3 100644
--- a/com/android/server/wifi/scanner/WificondScannerImpl.java
+++ b/com/android/server/wifi/scanner/WificondScannerImpl.java
@@ -167,12 +167,12 @@
                     + ",eventHandler=" + eventHandler);
             return false;
         }
-        if (mPendingSingleScanSettings != null
-                || (mLastScanSettings != null && mLastScanSettings.singleScanActive)) {
-            Log.w(TAG, "A single scan is already running");
-            return false;
-        }
         synchronized (mSettingsLock) {
+            if (mPendingSingleScanSettings != null
+                    || (mLastScanSettings != null && mLastScanSettings.singleScanActive)) {
+                Log.w(TAG, "A single scan is already running");
+                return false;
+            }
             mPendingSingleScanSettings = settings;
             mPendingSingleScanEventHandler = eventHandler;
             processPendingScans();
@@ -518,8 +518,10 @@
     }
 
     private boolean isHwPnoScanRequired() {
-        if (mPnoSettings == null) return false;
-        return isHwPnoScanRequired(mPnoSettings.isConnected);
+        synchronized (mSettingsLock) {
+            if (mPnoSettings == null) return false;
+            return isHwPnoScanRequired(mPnoSettings.isConnected);
+        }
     }
 
     @Override
diff --git a/com/android/server/wifi/util/InformationElementUtil.java b/com/android/server/wifi/util/InformationElementUtil.java
index c8f9ca3..14912b5 100644
--- a/com/android/server/wifi/util/InformationElementUtil.java
+++ b/com/android/server/wifi/util/InformationElementUtil.java
@@ -247,6 +247,10 @@
                         "Bad Interworking element length: " + ie.bytes.length);
             }
 
+            if (ie.bytes.length == 3 || ie.bytes.length == 9) {
+                int venueInfo = (int) ByteBufferReader.readInteger(data, ByteOrder.BIG_ENDIAN, 2);
+            }
+
             if (ie.bytes.length == 7 || ie.bytes.length == 9) {
                 hessid = ByteBufferReader.readInteger(data, ByteOrder.BIG_ENDIAN, 6);
             }
diff --git a/com/android/server/wm/AppWindowAnimator.java b/com/android/server/wm/AppWindowAnimator.java
index 5365e27..2ef7f25 100644
--- a/com/android/server/wm/AppWindowAnimator.java
+++ b/com/android/server/wm/AppWindowAnimator.java
@@ -295,13 +295,11 @@
     void updateThumbnailLayer() {
         if (thumbnail != null) {
             final int layer = mAppToken.getHighestAnimLayer();
-            if (layer != mThumbnailLayer) {
-                if (DEBUG_LAYERS) Slog.v(TAG,
-                        "Setting thumbnail layer " + mAppToken + ": layer=" + layer);
-                thumbnail.setLayer(layer + WindowManagerService.WINDOW_LAYER_MULTIPLIER
-                        - WindowManagerService.LAYER_OFFSET_THUMBNAIL);
-                mThumbnailLayer = layer;
-            }
+            if (DEBUG_LAYERS) Slog.v(TAG,
+                    "Setting thumbnail layer " + mAppToken + ": layer=" + layer);
+            thumbnail.setLayer(layer + WindowManagerService.WINDOW_LAYER_MULTIPLIER
+                    - WindowManagerService.LAYER_OFFSET_THUMBNAIL);
+            mThumbnailLayer = layer;
         }
     }
 
diff --git a/com/android/server/wm/AppWindowToken.java b/com/android/server/wm/AppWindowToken.java
index a1eeff8..5d03493 100644
--- a/com/android/server/wm/AppWindowToken.java
+++ b/com/android/server/wm/AppWindowToken.java
@@ -823,7 +823,7 @@
 
         // For freeform windows, we can't freeze the bounds at the moment because this would make
         // the resizing unresponsive.
-        if (task == null || task.inFreeformWorkspace()) {
+        if (task == null || task.inFreeformWindowingMode()) {
             return false;
         }
 
@@ -1310,8 +1310,7 @@
 
                 // Notify the pinned stack upon all windows drawn. If there was an animation in
                 // progress then this signal will resume that animation.
-                final TaskStack pinnedStack =
-                        mDisplayContent.getStack(WINDOWING_MODE_PINNED);
+                final TaskStack pinnedStack = mDisplayContent.getPinnedStack();
                 if (pinnedStack != null) {
                     pinnedStack.onAllWindowsDrawn();
                 }
diff --git a/com/android/server/wm/BlackFrame.java b/com/android/server/wm/BlackFrame.java
index 5c29a0a..d206554 100644
--- a/com/android/server/wm/BlackFrame.java
+++ b/com/android/server/wm/BlackFrame.java
@@ -18,7 +18,6 @@
 
 import static android.graphics.PixelFormat.OPAQUE;
 import static android.view.SurfaceControl.FX_SURFACE_DIM;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SURFACE_TRACE;
 import static com.android.server.wm.WindowManagerDebugConfig.SHOW_SURFACE_ALLOC;
 import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -51,14 +50,8 @@
             int w = r-l;
             int h = b-t;
 
-            if (DEBUG_SURFACE_TRACE) {
-                surface = new WindowSurfaceController.SurfaceTrace(session, "BlackSurface("
-                        + l + ", " + t + ")",
-                        w, h, OPAQUE, FX_SURFACE_DIM | SurfaceControl.HIDDEN);
-            } else {
-                surface = new SurfaceControl(session, "BlackSurface",
-                        w, h, OPAQUE, FX_SURFACE_DIM | SurfaceControl.HIDDEN);
-            }
+            surface = new SurfaceControl(session, "BlackSurface",
+                    w, h, OPAQUE, FX_SURFACE_DIM | SurfaceControl.HIDDEN);
 
             surface.setAlpha(1);
             surface.setLayerStack(layerStack);
diff --git a/com/android/server/wm/CircularDisplayMask.java b/com/android/server/wm/CircularDisplayMask.java
index ae41541..85f468b 100644
--- a/com/android/server/wm/CircularDisplayMask.java
+++ b/com/android/server/wm/CircularDisplayMask.java
@@ -17,7 +17,6 @@
 package com.android.server.wm;
 
 
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SURFACE_TRACE;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 
@@ -67,14 +66,9 @@
 
         SurfaceControl ctrl = null;
         try {
-            if (DEBUG_SURFACE_TRACE) {
-                ctrl = new WindowSurfaceController.SurfaceTrace(session, "CircularDisplayMask",
-                        mScreenSize.x, mScreenSize.y, PixelFormat.TRANSLUCENT,
-                        SurfaceControl.HIDDEN);
-            } else {
-                ctrl = new SurfaceControl(session, "CircularDisplayMask", mScreenSize.x,
-                        mScreenSize.y, PixelFormat.TRANSLUCENT, SurfaceControl.HIDDEN);
-            }
+            ctrl = new SurfaceControl(session, "CircularDisplayMask", mScreenSize.x,
+                    mScreenSize.y, PixelFormat.TRANSLUCENT, SurfaceControl.HIDDEN);
+
             ctrl.setLayerStack(display.getLayerStack());
             ctrl.setLayer(zOrder);
             ctrl.setPosition(0, 0);
diff --git a/com/android/server/wm/ConfigurationContainer.java b/com/android/server/wm/ConfigurationContainer.java
index 9e028d3..a4ab3ba 100644
--- a/com/android/server/wm/ConfigurationContainer.java
+++ b/com/android/server/wm/ConfigurationContainer.java
@@ -21,11 +21,14 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.app.WindowConfiguration.activityTypeToString;
+import static android.app.WindowConfiguration.windowingModeToString;
 import static com.android.server.wm.proto.ConfigurationContainerProto.FULL_CONFIGURATION;
 import static com.android.server.wm.proto.ConfigurationContainerProto.MERGED_OVERRIDE_CONFIGURATION;
 import static com.android.server.wm.proto.ConfigurationContainerProto.OVERRIDE_CONFIGURATION;
@@ -35,6 +38,7 @@
 import android.content.res.Configuration;
 import android.util.proto.ProtoOutputStream;
 
+import java.io.PrintWriter;
 import java.util.ArrayList;
 
 /**
@@ -182,6 +186,11 @@
         return windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
     }
 
+    public boolean inSplitScreenPrimaryWindowingMode() {
+        return mFullConfiguration.windowConfiguration.getWindowingMode()
+                == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+    }
+
     /**
      * Returns true if this container can be put in either
      * {@link WindowConfiguration#WINDOWING_MODE_SPLIT_SCREEN_PRIMARY} or
@@ -192,6 +201,14 @@
         return mFullConfiguration.windowConfiguration.supportSplitScreenWindowingMode();
     }
 
+    public boolean inPinnedWindowingMode() {
+        return mFullConfiguration.windowConfiguration.getWindowingMode() == WINDOWING_MODE_PINNED;
+    }
+
+    public boolean inFreeformWindowingMode() {
+        return mFullConfiguration.windowConfiguration.getWindowingMode() == WINDOWING_MODE_FREEFORM;
+    }
+
     /** Returns the activity type associated with the the configuration container. */
     /*@WindowConfiguration.ActivityType*/
     public int getActivityType() {
@@ -327,6 +344,26 @@
         protoOutputStream.end(token);
     }
 
+    /**
+     * Dumps the names of this container children in the input print writer indenting each
+     * level with the input prefix.
+     */
+    public void dumpChildrenNames(PrintWriter pw, String prefix) {
+        final String childPrefix = prefix + " ";
+        pw.println(getName()
+                + " type=" + activityTypeToString(getActivityType())
+                + " mode=" + windowingModeToString(getWindowingMode()));
+        for (int i = getChildCount() - 1; i >= 0; --i) {
+            final E cc = getChildAt(i);
+            pw.print(childPrefix + "#" + i + " ");
+            cc.dumpChildrenNames(pw, childPrefix);
+        }
+    }
+
+    String getName() {
+        return toString();
+    }
+
     abstract protected int getChildCount();
 
     abstract protected E getChildAt(int index);
diff --git a/com/android/server/wm/DimLayer.java b/com/android/server/wm/DimLayer.java
index 708973d..48181d3 100644
--- a/com/android/server/wm/DimLayer.java
+++ b/com/android/server/wm/DimLayer.java
@@ -17,7 +17,6 @@
 package com.android.server.wm;
 
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DIM_LAYER;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SURFACE_TRACE;
 import static com.android.server.wm.WindowManagerDebugConfig.SHOW_SURFACE_ALLOC;
 import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -105,16 +104,10 @@
     private void constructSurface(WindowManagerService service) {
         service.openSurfaceTransaction();
         try {
-            if (DEBUG_SURFACE_TRACE) {
-                mDimSurface = new WindowSurfaceController.SurfaceTrace(service.mFxSession,
-                    "DimSurface",
+            mDimSurface = new SurfaceControl(service.mFxSession, mName,
                     16, 16, PixelFormat.OPAQUE,
                     SurfaceControl.FX_SURFACE_DIM | SurfaceControl.HIDDEN);
-            } else {
-                mDimSurface = new SurfaceControl(service.mFxSession, mName,
-                    16, 16, PixelFormat.OPAQUE,
-                    SurfaceControl.FX_SURFACE_DIM | SurfaceControl.HIDDEN);
-            }
+
             if (SHOW_TRANSACTIONS || SHOW_SURFACE_ALLOC) Slog.i(TAG,
                     "  DIM " + mDimSurface + ": CREATE");
             mDimSurface.setLayerStack(mDisplayId);
diff --git a/com/android/server/wm/DisplayContent.java b/com/android/server/wm/DisplayContent.java
index 0e68a8f..03fdc96 100644
--- a/com/android/server/wm/DisplayContent.java
+++ b/com/android/server/wm/DisplayContent.java
@@ -16,10 +16,9 @@
 
 package com.android.server.wm;
 
-import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
-import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
@@ -118,7 +117,7 @@
 
 import android.annotation.CallSuper;
 import android.annotation.NonNull;
-import android.app.ActivityManager.StackId;
+import android.content.pm.PackageManager;
 import android.content.res.CompatibilityInfo;
 import android.content.res.Configuration;
 import android.graphics.Bitmap;
@@ -297,10 +296,6 @@
     /** Window tokens that are in the process of exiting, but still on screen for animations. */
     final ArrayList<WindowToken> mExitingTokens = new ArrayList<>();
 
-    /** A special TaskStack with id==HOME_STACK_ID that moves to the bottom whenever any TaskStack
-     * (except a future lockscreen TaskStack) moves to the top. */
-    private TaskStack mHomeStack = null;
-
     /** Detect user tapping outside of current focused task bounds .*/
     TaskTapPointerEventListener mTapDetector;
 
@@ -979,8 +974,8 @@
             }
 
             // In the presence of the PINNED stack or System Alert
-            // windows we unforuntately can not seamlessly rotate.
-            if (getStackById(PINNED_STACK_ID) != null) {
+            // windows we unfortunately can not seamlessly rotate.
+            if (hasPinnedStack()) {
                 mayRotateSeamlessly = false;
             }
             for (int i = 0; i < mService.mSessions.size(); i++) {
@@ -1451,20 +1446,31 @@
     }
 
     TaskStack getHomeStack() {
-        if (mHomeStack == null && mDisplayId == DEFAULT_DISPLAY) {
-            Slog.e(TAG_WM, "getHomeStack: Returning null from this=" + this);
-        }
-        return mHomeStack;
+        return mTaskStackContainers.getHomeStack();
     }
 
-    TaskStack getStackById(int stackId) {
-        for (int i = mTaskStackContainers.size() - 1; i >= 0; --i) {
-            final TaskStack stack = mTaskStackContainers.get(i);
-            if (stack.mStackId == stackId) {
-                return stack;
-            }
-        }
-        return null;
+    /**
+     * @return The primary split-screen stack, but only if it is visible, and {@code null} otherwise.
+     */
+    TaskStack getSplitScreenPrimaryStackStack() {
+        TaskStack stack = mTaskStackContainers.getSplitScreenPrimaryStackStack();
+        return (stack != null && stack.isVisible()) ? stack : null;
+    }
+
+    /**
+     * Like {@link #getSplitScreenPrimaryStackStack}, but also returns the stack if it's currently
+     * not visible.
+     */
+    TaskStack getSplitScreenPrimaryStackStackIgnoringVisibility() {
+        return mTaskStackContainers.getSplitScreenPrimaryStackStack();
+    }
+
+    TaskStack getPinnedStack() {
+        return mTaskStackContainers.getPinnedStack();
+    }
+
+    private boolean hasPinnedStack() {
+        return mTaskStackContainers.getPinnedStack() != null;
     }
 
     /**
@@ -1480,29 +1486,16 @@
      * activity type. Null is no compatible stack on the display.
      */
     TaskStack getStack(int windowingMode, int activityType) {
-        for (int i = mTaskStackContainers.size() - 1; i >= 0; --i) {
-            final TaskStack stack = mTaskStackContainers.get(i);
-            if (stack.isCompatible(windowingMode, activityType)) {
-                return stack;
-            }
-        }
-        return null;
+        return mTaskStackContainers.getStack(windowingMode, activityType);
     }
 
     @VisibleForTesting
-    int getStackCount() {
-        return mTaskStackContainers.size();
+    TaskStack getTopStack() {
+        return mTaskStackContainers.getTopStack();
     }
 
-    @VisibleForTesting
-    int getStackPosition(int windowingMode, int activityType) {
-        for (int i = mTaskStackContainers.size() - 1; i >= 0; --i) {
-            final TaskStack stack = mTaskStackContainers.get(i);
-            if (stack.isCompatible(windowingMode, activityType)) {
-                return i;
-            }
-        }
-        return -1;
+    void onStackWindowingModeChanged(TaskStack stack) {
+        mTaskStackContainers.onStackWindowingModeChanged(stack);
     }
 
     @Override
@@ -1523,8 +1516,8 @@
      * bounds were updated.
      */
     void updateStackBoundsAfterConfigChange(@NonNull List<Integer> changedStackList) {
-        for (int i = mTaskStackContainers.size() - 1; i >= 0; --i) {
-            final TaskStack stack = mTaskStackContainers.get(i);
+        for (int i = mTaskStackContainers.getChildCount() - 1; i >= 0; --i) {
+            final TaskStack stack = mTaskStackContainers.getChildAt(i);
             if (stack.updateBoundsAfterConfigChange()) {
                 changedStackList.add(stack.mStackId);
             }
@@ -1533,7 +1526,7 @@
         // If there was no pinned stack, we still need to notify the controller of the display info
         // update as a result of the config change.  We do this here to consolidate the flow between
         // changes when there is and is not a stack.
-        if (getStack(WINDOWING_MODE_PINNED) == null) {
+        if (!hasPinnedStack()) {
             mPinnedStackControllerLocked.onDisplayInfoChanged();
         }
     }
@@ -1632,8 +1625,8 @@
         mDisplay.getDisplayInfo(mDisplayInfo);
         mDisplay.getMetrics(mDisplayMetrics);
 
-        for (int i = mTaskStackContainers.size() - 1; i >= 0; --i) {
-            mTaskStackContainers.get(i).updateDisplayInfo(null);
+        for (int i = mTaskStackContainers.getChildCount() - 1; i >= 0; --i) {
+            mTaskStackContainers.getChildAt(i).updateDisplayInfo(null);
         }
     }
 
@@ -1754,26 +1747,14 @@
         out.set(mContentRect);
     }
 
-    TaskStack addStackToDisplay(int stackId, boolean onTop, StackWindowController controller) {
+    TaskStack createStack(int stackId, boolean onTop, StackWindowController controller) {
         if (DEBUG_STACK) Slog.d(TAG_WM, "Create new stackId=" + stackId + " on displayId="
                 + mDisplayId);
 
-        TaskStack stack = getStackById(stackId);
-        if (stack != null) {
-            // It's already attached to the display...clear mDeferRemoval, set controller, and move
-            // stack to appropriate z-order on display as needed.
-            stack.mDeferRemoval = false;
-            stack.setController(controller);
-            // We're not moving the display to front when we're adding stacks, only when
-            // requested to change the position of stack explicitly.
-            mTaskStackContainers.positionChildAt(onTop ? POSITION_TOP : POSITION_BOTTOM, stack,
-                    false /* includingParents */);
-        } else {
-            stack = new TaskStack(mService, stackId, controller);
-            mTaskStackContainers.addStackToDisplay(stack, onTop);
-        }
+        final TaskStack stack = new TaskStack(mService, stackId, controller);
+        mTaskStackContainers.addStackToDisplay(stack, onTop);
 
-        if (stackId == DOCKED_STACK_ID) {
+        if (stack.inSplitScreenPrimaryWindowingMode()) {
             mDividerControllerLocked.notifyDockedStackExistsChanged(true);
         }
         return stack;
@@ -1790,7 +1771,7 @@
                     + " to its current displayId=" + mDisplayId);
         }
 
-        prevDc.mTaskStackContainers.removeStackFromDisplay(stack);
+        prevDc.mTaskStackContainers.removeChild(stack);
         mTaskStackContainers.addStackToDisplay(stack, onTop);
     }
 
@@ -1824,8 +1805,8 @@
     }
 
     int taskIdFromPoint(int x, int y) {
-        for (int stackNdx = mTaskStackContainers.size() - 1; stackNdx >= 0; --stackNdx) {
-            final TaskStack stack = mTaskStackContainers.get(stackNdx);
+        for (int stackNdx = mTaskStackContainers.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+            final TaskStack stack = mTaskStackContainers.getChildAt(stackNdx);
             final int taskId = stack.taskIdFromPoint(x, y);
             if (taskId != -1) {
                 return taskId;
@@ -1841,8 +1822,8 @@
     Task findTaskForResizePoint(int x, int y) {
         final int delta = dipToPixel(RESIZE_HANDLE_WIDTH_IN_DP, mDisplayMetrics);
         mTmpTaskForResizePointSearchResult.reset();
-        for (int stackNdx = mTaskStackContainers.size() - 1; stackNdx >= 0; --stackNdx) {
-            final TaskStack stack = mTaskStackContainers.get(stackNdx);
+        for (int stackNdx = mTaskStackContainers.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+            final TaskStack stack = mTaskStackContainers.getChildAt(stackNdx);
             if (!stack.getWindowConfiguration().canResizeTask()) {
                 return null;
             }
@@ -1864,8 +1845,8 @@
             mTouchExcludeRegion.set(mBaseDisplayRect);
             final int delta = dipToPixel(RESIZE_HANDLE_WIDTH_IN_DP, mDisplayMetrics);
             mTmpRect2.setEmpty();
-            for (int stackNdx = mTaskStackContainers.size() - 1; stackNdx >= 0; --stackNdx) {
-                final TaskStack stack = mTaskStackContainers.get(stackNdx);
+            for (int stackNdx = mTaskStackContainers.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+                final TaskStack stack = mTaskStackContainers.getChildAt(stackNdx);
                 stack.setTouchExcludeRegion(
                         focusedTask, delta, mTouchExcludeRegion, mContentRect, mTmpRect2);
             }
@@ -1896,7 +1877,7 @@
             mTouchExcludeRegion.op(mTmpRegion, Region.Op.UNION);
         }
         // TODO(multi-display): Support docked stacks on secondary displays.
-        if (mDisplayId == DEFAULT_DISPLAY && getDockedStackLocked() != null) {
+        if (mDisplayId == DEFAULT_DISPLAY && getSplitScreenPrimaryStackStack() != null) {
             mDividerControllerLocked.getTouchRegion(mTmpRect);
             mTmpRegion.set(mTmpRect);
             mTouchExcludeRegion.op(mTmpRegion, Op.UNION);
@@ -1913,8 +1894,8 @@
     }
 
     private void resetAnimationBackgroundAnimator() {
-        for (int stackNdx = mTaskStackContainers.size() - 1; stackNdx >= 0; --stackNdx) {
-            mTaskStackContainers.get(stackNdx).resetAnimationBackgroundAnimator();
+        for (int stackNdx = mTaskStackContainers.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+            mTaskStackContainers.getChildAt(stackNdx).resetAnimationBackgroundAnimator();
         }
     }
 
@@ -1985,8 +1966,8 @@
             float dividerAnimationTarget) {
         boolean updated = false;
 
-        for (int i = mTaskStackContainers.size() - 1; i >= 0; --i) {
-            final TaskStack stack = mTaskStackContainers.get(i);
+        for (int i = mTaskStackContainers.getChildCount() - 1; i >= 0; --i) {
+            final TaskStack stack = mTaskStackContainers.getChildAt(i);
             if (stack == null || !stack.isAdjustedForIme()) {
                 continue;
             }
@@ -2014,8 +1995,8 @@
 
     boolean clearImeAdjustAnimation() {
         boolean changed = false;
-        for (int i = mTaskStackContainers.size() - 1; i >= 0; --i) {
-            final TaskStack stack = mTaskStackContainers.get(i);
+        for (int i = mTaskStackContainers.getChildCount() - 1; i >= 0; --i) {
+            final TaskStack stack = mTaskStackContainers.getChildAt(i);
             if (stack != null && stack.isAdjustedForIme()) {
                 stack.resetAdjustedForIme(true /* adjustBoundsNow */);
                 changed  = true;
@@ -2025,8 +2006,8 @@
     }
 
     void beginImeAdjustAnimation() {
-        for (int i = mTaskStackContainers.size() - 1; i >= 0; --i) {
-            final TaskStack stack = mTaskStackContainers.get(i);
+        for (int i = mTaskStackContainers.getChildCount() - 1; i >= 0; --i) {
+            final TaskStack stack = mTaskStackContainers.getChildAt(i);
             if (stack.isVisible() && stack.isAdjustedForIme()) {
                 stack.beginImeAdjustAnimation();
             }
@@ -2037,7 +2018,7 @@
         final WindowState imeWin = mService.mInputMethodWindow;
         final boolean imeVisible = imeWin != null && imeWin.isVisibleLw() && imeWin.isDisplayedLw()
                 && !mDividerControllerLocked.isImeHideRequested();
-        final boolean dockVisible = isStackVisible(DOCKED_STACK_ID);
+        final boolean dockVisible = isStackVisible(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
         final TaskStack imeTargetStack = mService.getImeFocusStackLocked();
         final int imeDockSide = (dockVisible && imeTargetStack != null) ?
                 imeTargetStack.getDockSide() : DOCKED_INVALID;
@@ -2055,8 +2036,8 @@
         // - If IME is not visible, divider is not moved and is normal width.
 
         if (imeVisible && dockVisible && (imeOnTop || imeOnBottom) && !dockMinimized) {
-            for (int i = mTaskStackContainers.size() - 1; i >= 0; --i) {
-                final TaskStack stack = mTaskStackContainers.get(i);
+            for (int i = mTaskStackContainers.getChildCount() - 1; i >= 0; --i) {
+                final TaskStack stack = mTaskStackContainers.getChildAt(i);
                 final boolean isDockedOnBottom = stack.getDockSide() == DOCKED_BOTTOM;
                 if (stack.isVisible() && (imeOnBottom || isDockedOnBottom)
                         && stack.inSplitScreenWindowingMode()) {
@@ -2068,8 +2049,8 @@
             mDividerControllerLocked.setAdjustedForIme(
                     imeOnBottom /*ime*/, true /*divider*/, true /*animate*/, imeWin, imeHeight);
         } else {
-            for (int i = mTaskStackContainers.size() - 1; i >= 0; --i) {
-                final TaskStack stack = mTaskStackContainers.get(i);
+            for (int i = mTaskStackContainers.getChildCount() - 1; i >= 0; --i) {
+                final TaskStack stack = mTaskStackContainers.getChildAt(i);
                 stack.resetAdjustedForIme(!dockVisible);
             }
             mDividerControllerLocked.setAdjustedForIme(
@@ -2100,8 +2081,8 @@
     }
 
     void prepareFreezingTaskBounds() {
-        for (int stackNdx = mTaskStackContainers.size() - 1; stackNdx >= 0; --stackNdx) {
-            final TaskStack stack = mTaskStackContainers.get(stackNdx);
+        for (int stackNdx = mTaskStackContainers.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+            final TaskStack stack = mTaskStackContainers.getChildAt(stackNdx);
             stack.prepareFreezingTaskBounds();
         }
     }
@@ -2160,22 +2141,22 @@
         final long token = proto.start(fieldId);
         super.writeToProto(proto, WINDOW_CONTAINER);
         proto.write(ID, mDisplayId);
-        for (int stackNdx = mTaskStackContainers.size() - 1; stackNdx >= 0; --stackNdx) {
-            final TaskStack stack = mTaskStackContainers.get(stackNdx);
+        for (int stackNdx = mTaskStackContainers.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+            final TaskStack stack = mTaskStackContainers.getChildAt(stackNdx);
             stack.writeToProto(proto, STACKS);
         }
         mDividerControllerLocked.writeToProto(proto, DOCKED_STACK_DIVIDER_CONTROLLER);
         mPinnedStackControllerLocked.writeToProto(proto, PINNED_STACK_CONTROLLER);
-        for (int i = mAboveAppWindowsContainers.size() - 1; i >= 0; --i) {
-            final WindowToken windowToken = mAboveAppWindowsContainers.get(i);
+        for (int i = mAboveAppWindowsContainers.getChildCount() - 1; i >= 0; --i) {
+            final WindowToken windowToken = mAboveAppWindowsContainers.getChildAt(i);
             windowToken.writeToProto(proto, ABOVE_APP_WINDOWS);
         }
-        for (int i = mBelowAppWindowsContainers.size() - 1; i >= 0; --i) {
-            final WindowToken windowToken = mBelowAppWindowsContainers.get(i);
+        for (int i = mBelowAppWindowsContainers.getChildCount() - 1; i >= 0; --i) {
+            final WindowToken windowToken = mBelowAppWindowsContainers.getChildAt(i);
             windowToken.writeToProto(proto, BELOW_APP_WINDOWS);
         }
-        for (int i = mImeWindowsContainers.size() - 1; i >= 0; --i) {
-            final WindowToken windowToken = mImeWindowsContainers.get(i);
+        for (int i = mImeWindowsContainers.getChildCount() - 1; i >= 0; --i) {
+            final WindowToken windowToken = mImeWindowsContainers.getChildAt(i);
             windowToken.writeToProto(proto, IME_WINDOWS);
         }
         proto.write(DPI, mBaseDisplayDensity);
@@ -2221,8 +2202,8 @@
 
         pw.println();
         pw.println(prefix + "Application tokens in top down Z order:");
-        for (int stackNdx = mTaskStackContainers.size() - 1; stackNdx >= 0; --stackNdx) {
-            final TaskStack stack = mTaskStackContainers.get(stackNdx);
+        for (int stackNdx = mTaskStackContainers.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+            final TaskStack stack = mTaskStackContainers.getChildAt(stackNdx);
             stack.dump(prefix + "  ", pw);
         }
 
@@ -2241,6 +2222,22 @@
         pw.println();
         mDimLayerController.dump(prefix, pw);
         pw.println();
+
+        // Dump stack references
+        final TaskStack homeStack = getHomeStack();
+        if (homeStack != null) {
+            pw.println(prefix + "homeStack=" + homeStack.getName());
+        }
+        final TaskStack pinnedStack = getPinnedStack();
+        if (pinnedStack != null) {
+            pw.println(prefix + "pinnedStack=" + pinnedStack.getName());
+        }
+        final TaskStack splitScreenPrimaryStack = getSplitScreenPrimaryStackStack();
+        if (splitScreenPrimaryStack != null) {
+            pw.println(prefix + "splitScreenPrimaryStack=" + splitScreenPrimaryStack.getName());
+        }
+
+        pw.println();
         mDividerControllerLocked.dump(prefix, pw);
         pw.println();
         mPinnedStackControllerLocked.dump(prefix, pw);
@@ -2260,26 +2257,10 @@
         return "Display " + mDisplayId + " name=\"" + mDisplayInfo.name + "\"";
     }
 
-    /** Checks if stack with provided id is visible on this display. */
-    boolean isStackVisible(int stackId) {
-        final TaskStack stack = getStackById(stackId);
-        return (stack != null && stack.isVisible());
-    }
-
-    /**
-     * @return The docked stack, but only if it is visible, and {@code null} otherwise.
-     */
-    TaskStack getDockedStackLocked() {
-        final TaskStack stack = getStack(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
-        return (stack != null && stack.isVisible()) ? stack : null;
-    }
-
-    /**
-     * Like {@link #getDockedStackLocked}, but also returns the docked stack if it's currently not
-     * visible.
-     */
-    TaskStack getDockedStackIgnoringVisibility() {
-        return getStack(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+    /** Returns true if the stack in the windowing mode is visible. */
+    boolean isStackVisible(int windowingMode) {
+        final TaskStack stack = getStack(windowingMode);
+        return stack != null && stack.isVisible();
     }
 
     /** Find the visible, touch-deliverable window under the given point */
@@ -3358,14 +3339,6 @@
      */
     static class DisplayChildWindowContainer<E extends WindowContainer> extends WindowContainer<E> {
 
-        int size() {
-            return mChildren.size();
-        }
-
-        E get(int index) {
-            return mChildren.get(index);
-        }
-
         @Override
         boolean fillsParent() {
             return true;
@@ -3383,25 +3356,108 @@
      */
     private final class TaskStackContainers extends DisplayChildWindowContainer<TaskStack> {
 
+        // Cached reference to some special stacks we tend to get a lot so we don't need to loop
+        // through the list to find them.
+        private TaskStack mHomeStack = null;
+        private TaskStack mPinnedStack = null;
+        private TaskStack mSplitScreenPrimaryStack = null;
+
+        /**
+         * Returns the topmost stack on the display that is compatible with the input windowing mode
+         * and activity type. Null is no compatible stack on the display.
+         */
+        TaskStack getStack(int windowingMode, int activityType) {
+            if (activityType == ACTIVITY_TYPE_HOME) {
+                return mHomeStack;
+            }
+            if (windowingMode == WINDOWING_MODE_PINNED) {
+                return mPinnedStack;
+            } else if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
+                return mSplitScreenPrimaryStack;
+            }
+            for (int i = mTaskStackContainers.getChildCount() - 1; i >= 0; --i) {
+                final TaskStack stack = mTaskStackContainers.getChildAt(i);
+                if (stack.isCompatible(windowingMode, activityType)) {
+                    return stack;
+                }
+            }
+            return null;
+        }
+
+        @VisibleForTesting
+        TaskStack getTopStack() {
+            return mTaskStackContainers.getChildCount() > 0
+                    ? mTaskStackContainers.getChildAt(mTaskStackContainers.getChildCount() - 1) : null;
+        }
+
+        TaskStack getHomeStack() {
+            if (mHomeStack == null && mDisplayId == DEFAULT_DISPLAY) {
+                Slog.e(TAG_WM, "getHomeStack: Returning null from this=" + this);
+            }
+            return mHomeStack;
+        }
+
+        TaskStack getPinnedStack() {
+            return mPinnedStack;
+        }
+
+        TaskStack getSplitScreenPrimaryStackStack() {
+            return mSplitScreenPrimaryStack;
+        }
+
         /**
          * Adds the stack to this container.
-         * @see WindowManagerService#addStackToDisplay(int, int, boolean)
+         * @see DisplayContent#createStack(int, boolean, StackWindowController)
          */
         void addStackToDisplay(TaskStack stack, boolean onTop) {
-            if (stack.isActivityTypeHome()) {
-                if (mHomeStack != null) {
-                    throw new IllegalArgumentException("attachStack: HOME_STACK_ID (0) not first.");
-                }
-                mHomeStack = stack;
-            }
+            addStackReferenceIfNeeded(stack);
             addChild(stack, onTop);
             stack.onDisplayChanged(DisplayContent.this);
         }
 
-        /** Removes the stack from its container and prepare for changing the parent. */
-        void removeStackFromDisplay(TaskStack stack) {
-            removeChild(stack);
-            stack.onRemovedFromDisplay();
+        void onStackWindowingModeChanged(TaskStack stack) {
+            removeStackReferenceIfNeeded(stack);
+            addStackReferenceIfNeeded(stack);
+            if (stack == mPinnedStack && getTopStack() != stack) {
+                // Looks like this stack changed windowing mode to pinned. Move it to the top.
+                positionChildAt(POSITION_TOP, stack, false /* includingParents */);
+            }
+        }
+
+        private void addStackReferenceIfNeeded(TaskStack stack) {
+            if (stack.isActivityTypeHome()) {
+                if (mHomeStack != null) {
+                    throw new IllegalArgumentException("addStackReferenceIfNeeded: home stack="
+                            + mHomeStack + " already exist on display=" + this + " stack=" + stack);
+                }
+                mHomeStack = stack;
+            }
+            final int windowingMode = stack.getWindowingMode();
+            if (windowingMode == WINDOWING_MODE_PINNED) {
+                if (mPinnedStack != null) {
+                    throw new IllegalArgumentException("addStackReferenceIfNeeded: pinned stack="
+                            + mPinnedStack + " already exist on display=" + this
+                            + " stack=" + stack);
+                }
+                mPinnedStack = stack;
+            } else if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
+                if (mSplitScreenPrimaryStack != null) {
+                    throw new IllegalArgumentException("addStackReferenceIfNeeded:"
+                            + " split-screen-primary" + " stack=" + mSplitScreenPrimaryStack
+                            + " already exist on display=" + this + " stack=" + stack);
+                }
+                mSplitScreenPrimaryStack = stack;
+            }
+        }
+
+        private void removeStackReferenceIfNeeded(TaskStack stack) {
+            if (stack == mHomeStack) {
+                mHomeStack = null;
+            } else if (stack == mPinnedStack) {
+                mPinnedStack = null;
+            } else if (stack == mSplitScreenPrimaryStack) {
+                mSplitScreenPrimaryStack = null;
+            }
         }
 
         private void addChild(TaskStack stack, boolean toTop) {
@@ -3411,6 +3467,11 @@
             setLayoutNeeded();
         }
 
+        @Override
+        protected void removeChild(TaskStack stack) {
+            super.removeChild(stack);
+            removeStackReferenceIfNeeded(stack);
+        }
 
         @Override
         boolean isOnTop() {
@@ -3453,8 +3514,7 @@
                     : requestedPosition >= topChildPosition;
             int targetPosition = requestedPosition;
 
-            if (toTop && stack.getWindowingMode() != WINDOWING_MODE_PINNED
-                    && getStack(WINDOWING_MODE_PINNED) != null) {
+            if (toTop && stack.getWindowingMode() != WINDOWING_MODE_PINNED && hasPinnedStack()) {
                 // The pinned stack is always the top most stack (always-on-top) when it is present.
                 TaskStack topStack = mChildren.get(topChildPosition);
                 if (topStack.getWindowingMode() != WINDOWING_MODE_PINNED) {
@@ -3556,7 +3616,8 @@
 
         @Override
         int getOrientation() {
-            if (isStackVisible(DOCKED_STACK_ID) || isStackVisible(FREEFORM_WORKSPACE_STACK_ID)) {
+            if (isStackVisible(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY)
+                    || isStackVisible(WINDOWING_MODE_FREEFORM)) {
                 // Apps and their containers are not allowed to specify an orientation while the
                 // docked or freeform stack is visible...except for the home stack/task if the
                 // docked stack is minimized and it actually set something.
@@ -3571,6 +3632,16 @@
             }
 
             final int orientation = super.getOrientation();
+            boolean isCar = mService.mContext.getPackageManager().hasSystemFeature(
+                    PackageManager.FEATURE_AUTOMOTIVE);
+            if (isCar) {
+                // In a car, you cannot physically rotate the screen, so it doesn't make sense to
+                // allow anything but the default orientation.
+                if (DEBUG_ORIENTATION) Slog.v(TAG_WM,
+                        "Forcing UNSPECIFIED orientation in car. Ignoring " + orientation);
+                return SCREEN_ORIENTATION_UNSPECIFIED;
+            }
+
             if (orientation != SCREEN_ORIENTATION_UNSET
                     && orientation != SCREEN_ORIENTATION_BEHIND) {
                 if (DEBUG_ORIENTATION) Slog.v(TAG_WM,
diff --git a/com/android/server/wm/DockedStackDividerController.java b/com/android/server/wm/DockedStackDividerController.java
index 6f441b9..52526e2 100644
--- a/com/android/server/wm/DockedStackDividerController.java
+++ b/com/android/server/wm/DockedStackDividerController.java
@@ -16,9 +16,6 @@
 
 package com.android.server.wm;
 
-import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
-import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
@@ -322,7 +319,7 @@
         if (mWindow == null) {
             return;
         }
-        TaskStack stack = mDisplayContent.getDockedStackIgnoringVisibility();
+        TaskStack stack = mDisplayContent.getSplitScreenPrimaryStackStackIgnoringVisibility();
 
         // If the stack is invisible, we policy force hide it in WindowAnimator.shouldForceHide
         final boolean visible = stack != null;
@@ -362,7 +359,7 @@
     }
 
     void positionDockedStackedDivider(Rect frame) {
-        TaskStack stack = mDisplayContent.getDockedStackLocked();
+        TaskStack stack = mDisplayContent.getSplitScreenPrimaryStackStack();
         if (stack == null) {
             // Unfortunately we might end up with still having a divider, even though the underlying
             // stack was already removed. This is because we are on AM thread and the removal of the
@@ -459,7 +456,7 @@
         long animDuration = 0;
         if (animate) {
             final TaskStack stack =
-                    mDisplayContent.getStack(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+                    mDisplayContent.getSplitScreenPrimaryStackStackIgnoringVisibility();
             final long transitionDuration = isAnimationMaximizing()
                     ? mService.mAppTransition.getLastClipRevealTransitionDuration()
                     : DEFAULT_APP_TRANSITION_DURATION;
@@ -513,7 +510,8 @@
     void registerDockedStackListener(IDockedStackListener listener) {
         mDockedStackListeners.register(listener);
         notifyDockedDividerVisibilityChanged(wasVisible());
-        notifyDockedStackExistsChanged(mDisplayContent.getDockedStackIgnoringVisibility() != null);
+        notifyDockedStackExistsChanged(
+                mDisplayContent.getSplitScreenPrimaryStackStackIgnoringVisibility() != null);
         notifyDockedStackMinimizedChanged(mMinimizedDock, false /* animate */,
                 isHomeStackResizable());
         notifyAdjustedForImeChanged(mAdjustedForIme, 0 /* animDuration */);
@@ -532,7 +530,7 @@
         final TaskStack stack = targetWindowingMode != WINDOWING_MODE_UNDEFINED
                 ? mDisplayContent.getStack(targetWindowingMode)
                 : null;
-        final TaskStack dockedStack = mDisplayContent.getDockedStackLocked();
+        final TaskStack dockedStack = mDisplayContent.getSplitScreenPrimaryStackStack();
         boolean visibleAndValid = visible && stack != null && dockedStack != null;
         if (visibleAndValid) {
             stack.getDimBounds(mTmpRect);
@@ -588,7 +586,7 @@
     private boolean containsAppInDockedStack(ArraySet<AppWindowToken> apps) {
         for (int i = apps.size() - 1; i >= 0; i--) {
             final AppWindowToken token = apps.valueAt(i);
-            if (token.getTask() != null && token.getTask().mStack.mStackId == DOCKED_STACK_ID) {
+            if (token.getTask() != null && token.inSplitScreenPrimaryWindowingMode()) {
                 return true;
             }
         }
@@ -600,7 +598,7 @@
     }
 
     private void checkMinimizeChanged(boolean animate) {
-        if (mDisplayContent.getDockedStackIgnoringVisibility() == null) {
+        if (mDisplayContent.getSplitScreenPrimaryStackStackIgnoringVisibility() == null) {
             return;
         }
         final TaskStack homeStack = mDisplayContent.getHomeStack();
@@ -762,7 +760,7 @@
     }
 
     private boolean setMinimizedDockedStack(boolean minimized) {
-        final TaskStack stack = mDisplayContent.getDockedStackIgnoringVisibility();
+        final TaskStack stack = mDisplayContent.getSplitScreenPrimaryStackStackIgnoringVisibility();
         notifyDockedStackMinimizedChanged(minimized, false /* animate */, isHomeStackResizable());
         return stack != null && stack.setAdjustedForMinimizedDock(minimized ? 1f : 0f);
     }
@@ -813,8 +811,7 @@
     }
 
     private boolean animateForMinimizedDockedStack(long now) {
-        final TaskStack stack =
-                mDisplayContent.getStack(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+        final TaskStack stack = mDisplayContent.getSplitScreenPrimaryStackStackIgnoringVisibility();
         if (!mAnimationStarted) {
             mAnimationStarted = true;
             mAnimationStartTime = now;
diff --git a/com/android/server/wm/EmulatorDisplayOverlay.java b/com/android/server/wm/EmulatorDisplayOverlay.java
index 3186d3d..19bd8e9 100644
--- a/com/android/server/wm/EmulatorDisplayOverlay.java
+++ b/com/android/server/wm/EmulatorDisplayOverlay.java
@@ -17,7 +17,6 @@
 package com.android.server.wm;
 
 
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SURFACE_TRACE;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 
@@ -57,14 +56,8 @@
 
         SurfaceControl ctrl = null;
         try {
-            if (DEBUG_SURFACE_TRACE) {
-                ctrl = new WindowSurfaceController.SurfaceTrace(session, "EmulatorDisplayOverlay",
-                        mScreenSize.x, mScreenSize.y, PixelFormat.TRANSLUCENT,
-                        SurfaceControl.HIDDEN);
-            } else {
-                ctrl = new SurfaceControl(session, "EmulatorDisplayOverlay", mScreenSize.x,
-                        mScreenSize.y, PixelFormat.TRANSLUCENT, SurfaceControl.HIDDEN);
-            }
+            ctrl = new SurfaceControl(session, "EmulatorDisplayOverlay", mScreenSize.x,
+                    mScreenSize.y, PixelFormat.TRANSLUCENT, SurfaceControl.HIDDEN);
             ctrl.setLayerStack(display.getLayerStack());
             ctrl.setLayer(zOrder);
             ctrl.setPosition(0, 0);
diff --git a/com/android/server/wm/InputMonitor.java b/com/android/server/wm/InputMonitor.java
index 5057f63..238cb9f 100644
--- a/com/android/server/wm/InputMonitor.java
+++ b/com/android/server/wm/InputMonitor.java
@@ -16,7 +16,6 @@
 
 package com.android.server.wm;
 
-import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManager.INPUT_CONSUMER_NAVIGATION;
 import static android.view.WindowManager.INPUT_CONSUMER_PIP;
@@ -650,7 +649,7 @@
             final boolean hasFocus = w == mInputFocus;
             final boolean isVisible = w.isVisibleLw();
 
-            if (w.getStackId() == PINNED_STACK_ID) {
+            if (w.inPinnedWindowingMode()) {
                 if (mAddPipInputConsumerHandle
                         && (inputWindowHandle.layer <= pipInputConsumer.mWindowHandle.layer)) {
                     // Update the bounds of the Pip input consumer to match the Pinned stack
diff --git a/com/android/server/wm/PinnedStackController.java b/com/android/server/wm/PinnedStackController.java
index ef31598..365366a 100644
--- a/com/android/server/wm/PinnedStackController.java
+++ b/com/android/server/wm/PinnedStackController.java
@@ -417,8 +417,7 @@
                             false /* useCurrentMinEdgeSize */);
                 }
                 final Rect animatingBounds = mTmpAnimatingBoundsRect;
-                final TaskStack pinnedStack =
-                        mDisplayContent.getStack(WINDOWING_MODE_PINNED);
+                final TaskStack pinnedStack = mDisplayContent.getPinnedStack();
                 if (pinnedStack != null) {
                     pinnedStack.getAnimationOrCurrentBounds(animatingBounds);
                 } else {
diff --git a/com/android/server/wm/RootWindowContainer.java b/com/android/server/wm/RootWindowContainer.java
index 7832f5d..fd57470 100644
--- a/com/android/server/wm/RootWindowContainer.java
+++ b/com/android/server/wm/RootWindowContainer.java
@@ -411,17 +411,6 @@
         }
     }
 
-    TaskStack getStackById(int stackId) {
-        for (int i = mChildren.size() - 1; i >= 0; i--) {
-            final DisplayContent dc = mChildren.get(i);
-            final TaskStack stack = dc.getStackById(stackId);
-            if (stack != null) {
-                return stack;
-            }
-        }
-        return null;
-    }
-
     TaskStack getStack(int windowingMode, int activityType) {
         for (int i = mChildren.size() - 1; i >= 0; i--) {
             final DisplayContent dc = mChildren.get(i);
diff --git a/com/android/server/wm/ScreenRotationAnimation.java b/com/android/server/wm/ScreenRotationAnimation.java
index d5b6d24..8e99be8 100644
--- a/com/android/server/wm/ScreenRotationAnimation.java
+++ b/com/android/server/wm/ScreenRotationAnimation.java
@@ -16,7 +16,6 @@
 
 package com.android.server.wm;
 
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SURFACE_TRACE;
 import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS;
 import static com.android.server.wm.WindowManagerDebugConfig.SHOW_SURFACE_ALLOC;
 import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS;
@@ -24,7 +23,6 @@
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 import static com.android.server.wm.WindowManagerService.TYPE_LAYER_MULTIPLIER;
 import static com.android.server.wm.WindowStateAnimator.WINDOW_FREEZE_LAYER;
-import static com.android.server.wm.WindowSurfaceController.SurfaceTrace;
 import static com.android.server.wm.proto.ScreenRotationAnimationProto.ANIMATION_RUNNING;
 import static com.android.server.wm.proto.ScreenRotationAnimationProto.STARTED;
 
@@ -276,17 +274,10 @@
                     flags |= SurfaceControl.SECURE;
                 }
 
-                if (DEBUG_SURFACE_TRACE) {
-                    mSurfaceControl = new SurfaceTrace(session, "ScreenshotSurface",
-                            mWidth, mHeight,
-                            PixelFormat.OPAQUE, flags);
-                    Slog.w(TAG, "ScreenRotationAnimation ctor: displayOffset="
-                            + mOriginalDisplayRect.toShortString());
-                } else {
-                    mSurfaceControl = new SurfaceControl(session, "ScreenshotSurface",
-                            mWidth, mHeight,
-                            PixelFormat.OPAQUE, flags);
-                }
+                mSurfaceControl = new SurfaceControl(session, "ScreenshotSurface",
+                        mWidth, mHeight,
+                        PixelFormat.OPAQUE, flags);
+
                 // capture a screenshot into the surface we just created
                 Surface sur = new Surface();
                 sur.copyFrom(mSurfaceControl);
diff --git a/com/android/server/wm/StackWindowController.java b/com/android/server/wm/StackWindowController.java
index c0a4cb7..1fda832 100644
--- a/com/android/server/wm/StackWindowController.java
+++ b/com/android/server/wm/StackWindowController.java
@@ -16,8 +16,6 @@
 
 package com.android.server.wm;
 
-import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
-
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.Handler;
@@ -45,7 +43,7 @@
 public class StackWindowController
         extends WindowContainerController<TaskStack, StackWindowListener> {
 
-    final int mStackId;
+    private final int mStackId;
 
     private final H mHandler;
 
@@ -74,7 +72,7 @@
                         + " to unknown displayId=" + displayId);
             }
 
-            dc.addStackToDisplay(stackId, onTop, this);
+            dc.createStack(stackId, onTop, this);
             getRawBounds(outBounds);
         }
     }
@@ -280,8 +278,9 @@
             if (stack.getWindowConfiguration().tasksAreFloating()) {
                 // Floating tasks should not be resized to the screen's bounds.
 
-                if (mStackId == PINNED_STACK_ID && bounds.width() == mTmpDisplayBounds.width() &&
-                        bounds.height() == mTmpDisplayBounds.height()) {
+                if (stack.inPinnedWindowingMode()
+                        && bounds.width() == mTmpDisplayBounds.width()
+                        && bounds.height() == mTmpDisplayBounds.height()) {
                     // If the bounds we are animating is the same as the fullscreen stack
                     // dimensions, then apply the same inset calculations that we normally do for
                     // the fullscreen stack, without intersecting it with the display bounds
diff --git a/com/android/server/wm/Task.java b/com/android/server/wm/Task.java
index 7e8d130..891d637 100644
--- a/com/android/server/wm/Task.java
+++ b/com/android/server/wm/Task.java
@@ -17,8 +17,6 @@
 package com.android.server.wm;
 
 import static android.app.ActivityManager.RESIZE_MODE_SYSTEM_SCREEN_ROTATION;
-import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
 import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_LANDSCAPE_ONLY;
 import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PORTRAIT_ONLY;
 import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PRESERVE_ORIENTATION;
@@ -213,7 +211,7 @@
         // then we want to preserve our insets so that there will not
         // be a jump in the area covered by system decorations. We rely
         // on the pinned animation to later unset this value.
-        if (stack.mStackId == PINNED_STACK_ID) {
+        if (stack.inPinnedWindowingMode()) {
             mPreserveNonFloatingState = true;
         } else {
             mPreserveNonFloatingState = false;
@@ -421,7 +419,7 @@
         return mFillsParent
                 || !inSplitScreenSecondaryWindowingMode()
                 || displayContent == null
-                || displayContent.getDockedStackIgnoringVisibility() != null;
+                || displayContent.getSplitScreenPrimaryStackStackIgnoringVisibility() != null;
     }
 
     /** Original bounds of the task if applicable, otherwise fullscreen rect. */
@@ -492,7 +490,7 @@
         final boolean dockedResizing = displayContent != null
                 && displayContent.mDividerControllerLocked.isResizing();
         if (useCurrentBounds()) {
-            if (inFreeformWorkspace() && getMaxVisibleBounds(out)) {
+            if (inFreeformWindowingMode() && getMaxVisibleBounds(out)) {
                 return;
             }
 
@@ -598,14 +596,6 @@
         return (tokensCount != 0) && mChildren.get(tokensCount - 1).mShowForAllUsers;
     }
 
-    boolean inFreeformWorkspace() {
-        return mStack != null && mStack.mStackId == FREEFORM_WORKSPACE_STACK_ID;
-    }
-
-    boolean inPinnedWorkspace() {
-        return mStack != null && mStack.mStackId == PINNED_STACK_ID;
-    }
-
     /**
      * When we are in a floating stack (Freeform, Pinned, ...) we calculate
      * insets differently. However if we are animating to the fullscreen stack
diff --git a/com/android/server/wm/TaskPositioner.java b/com/android/server/wm/TaskPositioner.java
index c58212c..87de151 100644
--- a/com/android/server/wm/TaskPositioner.java
+++ b/com/android/server/wm/TaskPositioner.java
@@ -20,7 +20,6 @@
 import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
 import static android.app.ActivityManager.RESIZE_MODE_USER;
 import static android.app.ActivityManager.RESIZE_MODE_USER_FORCED;
-import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ORIENTATION;
@@ -649,7 +648,7 @@
      * shouldn't be shown.
      */
     private int getDimSide(int x) {
-        if (mTask.mStack.mStackId != FREEFORM_WORKSPACE_STACK_ID
+        if (!mTask.mStack.inFreeformWindowingMode()
                 || !mTask.mStack.fillsParent()
                 || mTask.mStack.getConfiguration().orientation != ORIENTATION_LANDSCAPE) {
             return CTRL_NONE;
diff --git a/com/android/server/wm/TaskSnapshotController.java b/com/android/server/wm/TaskSnapshotController.java
index bff24f6..54ef065 100644
--- a/com/android/server/wm/TaskSnapshotController.java
+++ b/com/android/server/wm/TaskSnapshotController.java
@@ -294,7 +294,9 @@
         decorPainter.drawDecors(c, null /* statusBarExcludeFrame */);
         node.end(c);
         final Bitmap hwBitmap = ThreadedRenderer.createHardwareBitmap(node, width, height);
-
+        if (hwBitmap == null) {
+            return null;
+        }
         return new TaskSnapshot(hwBitmap.createGraphicBufferHandle(),
                 topChild.getConfiguration().orientation, mainWindow.mStableInsets,
                 ActivityManager.isLowRamDeviceStatic() /* reduced */, 1.0f /* scale */);
diff --git a/com/android/server/wm/TaskStack.java b/com/android/server/wm/TaskStack.java
index 6527883..d170b6f 100644
--- a/com/android/server/wm/TaskStack.java
+++ b/com/android/server/wm/TaskStack.java
@@ -18,8 +18,6 @@
 
 import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
 import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT;
-import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
-import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
@@ -296,7 +294,7 @@
         if (mFillsParent
                 || !inSplitScreenSecondaryWindowingMode()
                 || mDisplayContent == null
-                || mDisplayContent.getDockedStackLocked() != null) {
+                || mDisplayContent.getSplitScreenPrimaryStackStack() != null) {
             return true;
         }
         return false;
@@ -409,7 +407,7 @@
             return false;
         }
 
-        if (mStackId == PINNED_STACK_ID) {
+        if (inPinnedWindowingMode()) {
             getAnimationOrCurrentBounds(mTmpRect2);
             boolean updated = mDisplayContent.mPinnedStackControllerLocked.onTaskStackBoundsChanged(
                     mTmpRect2, mTmpRect3);
@@ -443,21 +441,19 @@
 
         mTmpRect2.set(mBounds);
         mDisplayContent.rotateBounds(mRotation, newRotation, mTmpRect2);
-        switch (mStackId) {
-            case DOCKED_STACK_ID:
-                repositionDockedStackAfterRotation(mTmpRect2);
-                snapDockedStackAfterRotation(mTmpRect2);
-                final int newDockSide = getDockSide(mTmpRect2);
+        if (inSplitScreenPrimaryWindowingMode()) {
+            repositionDockedStackAfterRotation(mTmpRect2);
+            snapDockedStackAfterRotation(mTmpRect2);
+            final int newDockSide = getDockSide(mTmpRect2);
 
-                // Update the dock create mode and clear the dock create bounds, these
-                // might change after a rotation and the original values will be invalid.
-                mService.setDockedStackCreateStateLocked(
-                        (newDockSide == DOCKED_LEFT || newDockSide == DOCKED_TOP)
-                                ? DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT
-                                : DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT,
-                        null);
-                mDisplayContent.getDockedDividerController().notifyDockSideChanged(newDockSide);
-                break;
+            // Update the dock create mode and clear the dock create bounds, these
+            // might change after a rotation and the original values will be invalid.
+            mService.setDockedStackCreateStateLocked(
+                    (newDockSide == DOCKED_LEFT || newDockSide == DOCKED_TOP)
+                            ? DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT
+                            : DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT,
+                    null);
+            mDisplayContent.getDockedDividerController().notifyDockSideChanged(newDockSide);
         }
 
         mBoundsAfterRotation.set(mTmpRect2);
@@ -677,6 +673,16 @@
         }
     }
 
+    @Override
+    public void onConfigurationChanged(Configuration newParentConfig) {
+        final int prevWindowingMode = getWindowingMode();
+        super.onConfigurationChanged(newParentConfig);
+        if (mDisplayContent != null && prevWindowingMode != getWindowingMode()) {
+            mDisplayContent.onStackWindowingModeChanged(this);
+        }
+    }
+
+    @Override
     void onDisplayChanged(DisplayContent dc) {
         if (mDisplayContent != null) {
             throw new IllegalStateException("onDisplayChanged: Already attached");
@@ -687,8 +693,8 @@
                 "animation background stackId=" + mStackId);
 
         Rect bounds = null;
-        final TaskStack dockedStack = dc.getDockedStackIgnoringVisibility();
-        if (mStackId == DOCKED_STACK_ID
+        final TaskStack dockedStack = dc.getSplitScreenPrimaryStackStackIgnoringVisibility();
+        if (inSplitScreenPrimaryWindowingMode()
                 || (dockedStack != null && inSplitScreenSecondaryWindowingMode()
                         && !dockedStack.fillsParent())) {
             // The existence of a docked stack affects the size of other static stack created since
@@ -703,10 +709,10 @@
             }
             final boolean dockedOnTopOrLeft = mService.mDockedStackCreateMode
                     == DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
-            getStackDockedModeBounds(mTmpRect, bounds, mStackId, mTmpRect2,
+            getStackDockedModeBounds(mTmpRect, bounds, mTmpRect2,
                     mDisplayContent.mDividerControllerLocked.getContentWidth(),
                     dockedOnTopOrLeft);
-        } else if (mStackId == PINNED_STACK_ID) {
+        } else if (inPinnedWindowingMode()) {
             // Update the bounds based on any changes to the display info
             getAnimationOrCurrentBounds(mTmpRect2);
             boolean updated = mDisplayContent.mPinnedStackControllerLocked.onTaskStackBoundsChanged(
@@ -766,7 +772,8 @@
             return;
         }
 
-        final TaskStack dockedStack = mDisplayContent.getDockedStackIgnoringVisibility();
+        final TaskStack dockedStack =
+                mDisplayContent.getSplitScreenPrimaryStackStackIgnoringVisibility();
         if (dockedStack == null) {
             // Not sure why you are calling this method when there is no docked stack...
             throw new IllegalStateException(
@@ -791,7 +798,7 @@
         mDisplayContent.getLogicalDisplayRect(mTmpRect);
         dockedStack.getRawBounds(mTmpRect2);
         final boolean dockedOnTopOrLeft = dockedSide == DOCKED_TOP || dockedSide == DOCKED_LEFT;
-        getStackDockedModeBounds(mTmpRect, outStackBounds, mStackId, mTmpRect2,
+        getStackDockedModeBounds(mTmpRect, outStackBounds, mTmpRect2,
                 mDisplayContent.mDividerControllerLocked.getContentWidth(), dockedOnTopOrLeft);
 
     }
@@ -800,16 +807,15 @@
      * Outputs the bounds a stack should be given the presence of a docked stack on the display.
      * @param displayRect The bounds of the display the docked stack is on.
      * @param outBounds Output bounds that should be used for the stack.
-     * @param stackId Id of stack we are calculating the bounds for.
      * @param dockedBounds Bounds of the docked stack.
      * @param dockDividerWidth We need to know the width of the divider make to the output bounds
      *                         close to the side of the dock.
      * @param dockOnTopOrLeft If the docked stack is on the top or left side of the screen.
      */
     private void getStackDockedModeBounds(
-            Rect displayRect, Rect outBounds, int stackId, Rect dockedBounds, int dockDividerWidth,
+            Rect displayRect, Rect outBounds, Rect dockedBounds, int dockDividerWidth,
             boolean dockOnTopOrLeft) {
-        final boolean dockedStack = stackId == DOCKED_STACK_ID;
+        final boolean dockedStack = inSplitScreenPrimaryWindowingMode();
         final boolean splitHorizontally = displayRect.width() > displayRect.height();
 
         outBounds.set(displayRect);
@@ -866,7 +872,7 @@
     }
 
     void resetDockedStackToMiddle() {
-        if (mStackId != DOCKED_STACK_ID) {
+        if (inSplitScreenPrimaryWindowingMode()) {
             throw new IllegalStateException("Not a docked stack=" + this);
         }
 
@@ -894,17 +900,12 @@
     }
 
     @Override
-    void removeImmediately() {
-        super.removeImmediately();
+    void onParentSet() {
+        if (getParent() != null || mDisplayContent == null) {
+            return;
+        }
 
-        onRemovedFromDisplay();
-    }
-
-    /**
-     * Removes the stack it from its current parent, so it can be either destroyed completely or
-     * re-parented.
-     */
-    void onRemovedFromDisplay() {
+        // Looks like the stack was removed from the display. Go ahead and clean things up.
         mDisplayContent.mDimLayerController.removeDimLayerUser(this);
         EventLog.writeEvent(EventLogTags.WM_STACK_REMOVED, mStackId);
 
@@ -913,7 +914,7 @@
             mAnimationBackgroundSurface = null;
         }
 
-        if (mStackId == DOCKED_STACK_ID) {
+        if (inSplitScreenPrimaryWindowingMode()) {
             mDisplayContent.mDividerControllerLocked.notifyDockedStackExistsChanged(false);
         }
 
@@ -1035,8 +1036,8 @@
     }
 
     boolean shouldIgnoreInput() {
-        return isAdjustedForMinimizedDockedStack() || mStackId == DOCKED_STACK_ID &&
-                isMinimizedDockAndHomeStackResizable();
+        return isAdjustedForMinimizedDockedStack() ||
+                (inSplitScreenPrimaryWindowingMode() && isMinimizedDockAndHomeStackResizable());
     }
 
     /**
@@ -1471,7 +1472,7 @@
                 postExclude.set(mTmpRect);
             }
 
-            final boolean isFreeformed = task.inFreeformWorkspace();
+            final boolean isFreeformed = task.inFreeformWindowingMode();
             if (task != focusedTask || isFreeformed) {
                 if (isFreeformed) {
                     // If the task is freeformed, enlarge the area to account for outside
@@ -1529,7 +1530,7 @@
             }
         }
 
-        if (mStackId == PINNED_STACK_ID) {
+        if (inPinnedWindowingMode()) {
             try {
                 mService.mActivityManager.notifyPinnedStackAnimationStarted();
             } catch (RemoteException e) {
@@ -1561,7 +1562,7 @@
             mService.requestTraversal();
         }
 
-        if (mStackId == PINNED_STACK_ID) {
+        if (inPinnedWindowingMode()) {
             // Update to the final bounds if requested. This is done here instead of in the bounds
             // animator to allow us to coordinate this after we notify the PiP mode changed
 
@@ -1595,7 +1596,7 @@
      *         bounds and we have a deferred PiP mode changed callback set with the animation.
      */
     public boolean deferScheduleMultiWindowModeChanged() {
-        if (mStackId == PINNED_STACK_ID) {
+        if (inPinnedWindowingMode()) {
             return (mBoundsAnimatingRequested || mBoundsAnimating);
         }
         return false;
diff --git a/com/android/server/wm/WallpaperController.java b/com/android/server/wm/WallpaperController.java
index 7213c95..629cc86 100644
--- a/com/android/server/wm/WallpaperController.java
+++ b/com/android/server/wm/WallpaperController.java
@@ -18,7 +18,7 @@
 
 import com.android.internal.util.ToBooleanFunction;
 
-import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
 import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
@@ -447,7 +447,7 @@
 
     private void findWallpaperTarget(DisplayContent dc) {
         mFindResults.reset();
-        if (dc.isStackVisible(FREEFORM_WORKSPACE_STACK_ID)) {
+        if (dc.isStackVisible(WINDOWING_MODE_FREEFORM)) {
             // In freeform mode we set the wallpaper as its own target, so we don't need an
             // additional window to make it visible.
             mFindResults.setUseTopWallpaperAsTarget(true);
diff --git a/com/android/server/wm/WindowContainer.java b/com/android/server/wm/WindowContainer.java
index 40923c8..563eb9c 100644
--- a/com/android/server/wm/WindowContainer.java
+++ b/com/android/server/wm/WindowContainer.java
@@ -73,12 +73,12 @@
 
 
     @Override
-    final protected int getChildCount() {
+    protected int getChildCount() {
         return mChildren.size();
     }
 
     @Override
-    final protected E getChildAt(int index) {
+    protected E getChildAt(int index) {
         return mChildren.get(index);
     }
 
@@ -674,20 +674,6 @@
     }
 
     /**
-     * Dumps the names of this container children in the input print writer indenting each
-     * level with the input prefix.
-     */
-    void dumpChildrenNames(StringBuilder out, String prefix) {
-        final String childPrefix = prefix + " ";
-        out.append(getName() + "\n");
-        for (int i = mChildren.size() - 1; i >= 0; --i) {
-            final WindowContainer wc = mChildren.get(i);
-            out.append(childPrefix + "#" + i + " ");
-            wc.dumpChildrenNames(out, childPrefix);
-        }
-    }
-
-    /**
      * Write to a protocol buffer output stream. Protocol buffer message definition is at
      * {@link com.android.server.wm.proto.WindowContainerProto}.
      *
@@ -704,10 +690,6 @@
         protoOutputStream.end(token);
     }
 
-    String getName() {
-        return toString();
-    }
-
     private ForAllWindowsConsumerWrapper obtainConsumerWrapper(Consumer<WindowState> consumer) {
         ForAllWindowsConsumerWrapper wrapper = mConsumerWrapperPool.acquire();
         if (wrapper == null) {
diff --git a/com/android/server/wm/WindowManagerDebugConfig.java b/com/android/server/wm/WindowManagerDebugConfig.java
index 6d5673e..9d9805a 100644
--- a/com/android/server/wm/WindowManagerDebugConfig.java
+++ b/com/android/server/wm/WindowManagerDebugConfig.java
@@ -60,7 +60,6 @@
     static final boolean DEBUG_SCREENSHOT = false;
     static final boolean DEBUG_BOOT = false;
     static final boolean DEBUG_LAYOUT_REPEATS = false;
-    static final boolean DEBUG_SURFACE_TRACE = false;
     static final boolean DEBUG_WINDOW_TRACE = false;
     static final boolean DEBUG_TASK_MOVEMENT = false;
     static final boolean DEBUG_TASK_POSITIONING = false;
diff --git a/com/android/server/wm/WindowManagerService.java b/com/android/server/wm/WindowManagerService.java
index 1fb2188..f0da474 100644
--- a/com/android/server/wm/WindowManagerService.java
+++ b/com/android/server/wm/WindowManagerService.java
@@ -2384,7 +2384,7 @@
             final Rect insets = new Rect();
             final Rect stableInsets = new Rect();
             Rect surfaceInsets = null;
-            final boolean freeform = win != null && win.inFreeformWorkspace();
+            final boolean freeform = win != null && win.inFreeformWindowingMode();
             if (win != null) {
                 // Containing frame will usually cover the whole screen, including dialog windows.
                 // For freeform workspace windows it will not cover the whole screen and it also
@@ -2794,7 +2794,7 @@
         for (final WindowState win : mWindowMap.values()) {
             final Task task = win.getTask();
             if (task != null && mTmpTaskIds.get(task.mTaskId, -1) != -1
-                    && task.inFreeformWorkspace()) {
+                    && task.inFreeformWindowingMode()) {
                 final AppWindowToken appToken = win.mAppToken;
                 if (appToken != null && appToken.mAppAnimator != null) {
                     appToken.mAppAnimator.startProlongAnimation(scaleUp ?
@@ -3391,7 +3391,8 @@
 
             // Notify whether the docked stack exists for the current user
             final DisplayContent displayContent = getDefaultDisplayContentLocked();
-            final TaskStack stack = displayContent.getDockedStackIgnoringVisibility();
+            final TaskStack stack =
+                    displayContent.getSplitScreenPrimaryStackStackIgnoringVisibility();
             displayContent.mDividerControllerLocked.notifyDockedStackExistsChanged(
                     stack != null && stack.hasTaskForUser(newUserId));
 
@@ -6898,11 +6899,6 @@
                     dumpSessionsLocked(pw, true);
                 }
                 return;
-            } else if ("surfaces".equals(cmd)) {
-                synchronized(mWindowMap) {
-                    WindowSurfaceController.SurfaceTrace.dumpAllSurfaces(pw, null);
-                }
-                return;
             } else if ("displays".equals(cmd) || "d".equals(cmd)) {
                 synchronized(mWindowMap) {
                     mRoot.dumpDisplayContents(pw);
@@ -6925,9 +6921,7 @@
                 return;
             } else if ("containers".equals(cmd)) {
                 synchronized(mWindowMap) {
-                    StringBuilder output = new StringBuilder();
-                    mRoot.dumpChildrenNames(output, " ");
-                    pw.println(output.toString());
+                    mRoot.dumpChildrenNames(pw, " ");
                     pw.println(" ");
                     mRoot.forAllWindows(w -> {pw.println(w);}, true /* traverseTopToBottom */);
                 }
@@ -6967,10 +6961,6 @@
             if (dumpAll) {
                 pw.println("-------------------------------------------------------------------------------");
             }
-            WindowSurfaceController.SurfaceTrace.dumpAllSurfaces(pw, dumpAll ?
-                    "-------------------------------------------------------------------------------"
-                    : null);
-            pw.println();
             if (dumpAll) {
                 pw.println("-------------------------------------------------------------------------------");
             }
@@ -7132,7 +7122,7 @@
     public int getDockedStackSide() {
         synchronized (mWindowMap) {
             final TaskStack dockedStack = getDefaultDisplayContentLocked()
-                    .getDockedStackIgnoringVisibility();
+                    .getSplitScreenPrimaryStackStackIgnoringVisibility();
             return dockedStack == null ? DOCKED_INVALID : dockedStack.getDockSide();
         }
     }
@@ -7604,10 +7594,10 @@
         }
 
         @Override
-        public boolean isStackVisible(int stackId) {
+        public boolean isStackVisible(int windowingMode) {
             synchronized (mWindowMap) {
                 final DisplayContent dc = getDefaultDisplayContentLocked();
-                return dc.isStackVisible(stackId);
+                return dc.isStackVisible(windowingMode);
             }
         }
 
diff --git a/com/android/server/wm/WindowState.java b/com/android/server/wm/WindowState.java
index 4ff0f39..e171528 100644
--- a/com/android/server/wm/WindowState.java
+++ b/com/android/server/wm/WindowState.java
@@ -16,10 +16,7 @@
 
 package com.android.server.wm;
 
-import static android.app.ActivityManager.StackId;
-import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
 import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
-import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT;
@@ -83,7 +80,6 @@
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_RESIZE;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STARTING_WINDOW;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STARTING_WINDOW_VERBOSE;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SURFACE_TRACE;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -815,13 +811,12 @@
             final WindowState imeWin = mService.mInputMethodWindow;
             // IME is up and obscuring this window. Adjust the window position so it is visible.
             if (imeWin != null && imeWin.isVisibleNow() && mService.mInputMethodTarget == this) {
-                final int stackId = getStackId();
-                if (stackId == FREEFORM_WORKSPACE_STACK_ID
+                if (inFreeformWindowingMode()
                         && mContainingFrame.bottom > contentFrame.bottom) {
                     // In freeform we want to move the top up directly.
                     // TODO: Investigate why this is contentFrame not parentFrame.
                     mContainingFrame.top -= mContainingFrame.bottom - contentFrame.bottom;
-                } else if (stackId != PINNED_STACK_ID
+                } else if (!inPinnedWindowingMode()
                         && mContainingFrame.bottom > parentFrame.bottom) {
                     // But in docked we want to behave like fullscreen and behave as if the task
                     // were given smaller bounds for the purposes of layout. Skip adjustments for
@@ -898,7 +893,7 @@
             // For pinned workspace the frame isn't limited in any particular
             // way since SystemUI controls the bounds. For freeform however
             // we want to keep things inside the content frame.
-            final Rect limitFrame = task.inPinnedWorkspace() ? mFrame : mContentFrame;
+            final Rect limitFrame = task.inPinnedWindowingMode() ? mFrame : mContentFrame;
             // Keep the frame out of the blocked system area, limit it in size to the content area
             // and make sure that there is always a minimum visible so that the user can drag it
             // into a usable area..
@@ -1210,7 +1205,7 @@
             // application when it has finished drawing.
             if (getOrientationChanging() || dragResizingChanged
                     || isResizedWhileNotDragResizing()) {
-                if (DEBUG_SURFACE_TRACE || DEBUG_ANIM || DEBUG_ORIENTATION || DEBUG_RESIZE) {
+                if (DEBUG_ANIM || DEBUG_ORIENTATION || DEBUG_RESIZE) {
                     Slog.v(TAG_WM, "Orientation or resize start waiting for draw"
                             + ", mDrawState=DRAW_PENDING in " + this
                             + ", surfaceController " + winAnimator.mSurfaceController);
@@ -1662,9 +1657,9 @@
             //
             // Anyway we don't need to synchronize position and content updates for these
             // windows since they aren't at the base layer and could be moved around anyway.
-            if (!computeDragResizing() && mAttrs.type == TYPE_BASE_APPLICATION &&
-                    !mWinAnimator.isForceScaled() && !isGoneForLayoutLw() &&
-                    !getTask().inPinnedWorkspace()) {
+            if (!computeDragResizing() && mAttrs.type == TYPE_BASE_APPLICATION
+                    && !mWinAnimator.isForceScaled() && !isGoneForLayoutLw()
+                    && !getTask().inPinnedWindowingMode()) {
                 setResizedWhileNotDragResizing(true);
             }
         }
@@ -2196,12 +2191,6 @@
         }
     }
 
-    // TODO: Strange usage of word workspace here and above.
-    boolean inPinnedWorkspace() {
-        final Task task = getTask();
-        return task != null && task.inPinnedWorkspace();
-    }
-
     void applyAdjustForImeIfNeeded() {
         final Task task = getTask();
         if (task != null && task.mStack != null && task.mStack.isAdjustedForIme()) {
@@ -2235,7 +2224,7 @@
             } else {
                 getVisibleBounds(mTmpRect);
             }
-            if (inFreeformWorkspace()) {
+            if (inFreeformWindowingMode()) {
                 // For freeform windows we the touch region to include the whole surface for the
                 // shadows.
                 final DisplayMetrics displayMetrics = getDisplayContent().getDisplayMetrics();
@@ -2371,7 +2360,8 @@
                             // just in case they have the divider at an unstable position. Better
                             // also reset drag resizing state, because the owner can't do it
                             // anymore.
-                            final TaskStack stack = dc.getDockedStackIgnoringVisibility();
+                            final TaskStack stack =
+                                    dc.getSplitScreenPrimaryStackStackIgnoringVisibility();
                             if (stack != null) {
                                 stack.resetDockedStackToMiddle();
                             }
@@ -2938,8 +2928,7 @@
         return mTmpRect;
     }
 
-    @Override
-    public int getStackId() {
+    private int getStackId() {
         final TaskStack stack = getStack();
         if (stack == null) {
             return INVALID_STACK_ID;
@@ -2983,11 +2972,6 @@
         }
     }
 
-    boolean inFreeformWorkspace() {
-        final Task task = getTask();
-        return task != null && task.inFreeformWorkspace();
-    }
-
     @Override
     public boolean isInMultiWindowMode() {
         final Task task = getTask();
@@ -3105,7 +3089,7 @@
         // background.
         return (getDisplayContent().mDividerControllerLocked.isResizing()
                         || mAppToken != null && !mAppToken.mFrozenBounds.isEmpty()) &&
-                !task.inFreeformWorkspace() && !isGoneForLayoutLw();
+                !task.inFreeformWindowingMode() && !isGoneForLayoutLw();
 
     }
 
@@ -3695,7 +3679,7 @@
 
         // Force the show in the next prepareSurfaceLocked() call.
         mWinAnimator.mLastAlpha = -1;
-        if (DEBUG_SURFACE_TRACE || DEBUG_ANIM) Slog.v(TAG,
+        if (DEBUG_ANIM) Slog.v(TAG,
                 "performShowLocked: mDrawState=HAS_DRAWN in " + this);
         mWinAnimator.mDrawState = HAS_DRAWN;
         mService.scheduleAnimationLocked();
@@ -3756,7 +3740,7 @@
         windowInfo.accessibilityIdOfAnchor = mAttrs.accessibilityIdOfAnchor;
         windowInfo.focused = isFocused();
         Task task = getTask();
-        windowInfo.inPictureInPicture = (task != null) && task.inPinnedWorkspace();
+        windowInfo.inPictureInPicture = (task != null) && task.inPinnedWindowingMode();
 
         if (mIsChildWindow) {
             windowInfo.parentToken = getParentWindow().mClient.asBinder();
@@ -4219,7 +4203,7 @@
         // If a freeform window is animating from a position where it would be cutoff, it would be
         // cutoff during the animation. We don't want that, so for the duration of the animation
         // we ignore the decor cropping and depend on layering to position windows correctly.
-        final boolean cropToDecor = !(inFreeformWorkspace() && isAnimatingLw());
+        final boolean cropToDecor = !(inFreeformWindowingMode() && isAnimatingLw());
         if (cropToDecor) {
             // Intersect with the decor rect, offsetted by window position.
             systemDecorRect.intersect(decorRect.left - left, decorRect.top - top,
@@ -4303,7 +4287,7 @@
         // scale for the animation using the source hint rect
         // (see WindowStateAnimator#setSurfaceBoundariesLocked()).
         if (isDragResizeChanged() || isResizedWhileNotDragResizing()
-                || (surfaceInsetsChanging() && !inPinnedWorkspace())) {
+                || (surfaceInsetsChanging() && !inPinnedWindowingMode())) {
             mLastSurfaceInsets.set(mAttrs.surfaceInsets);
 
             setDragResizing();
diff --git a/com/android/server/wm/WindowStateAnimator.java b/com/android/server/wm/WindowStateAnimator.java
index 1b7e527..5266903 100644
--- a/com/android/server/wm/WindowStateAnimator.java
+++ b/com/android/server/wm/WindowStateAnimator.java
@@ -31,7 +31,6 @@
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ORIENTATION;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STARTING_WINDOW;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STARTING_WINDOW_VERBOSE;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SURFACE_TRACE;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WINDOW_CROP;
@@ -509,7 +508,7 @@
         boolean layoutNeeded = false;
 
         if (mDrawState == DRAW_PENDING) {
-            if (DEBUG_SURFACE_TRACE || DEBUG_ANIM || SHOW_TRANSACTIONS || DEBUG_ORIENTATION)
+            if (DEBUG_ANIM || SHOW_TRANSACTIONS || DEBUG_ORIENTATION)
                 Slog.v(TAG, "finishDrawingLocked: mDrawState=COMMIT_DRAW_PENDING " + mWin + " in "
                         + mSurfaceController);
             if (DEBUG_STARTING_WINDOW && startingWindow) {
@@ -532,7 +531,7 @@
         if (mDrawState != COMMIT_DRAW_PENDING && mDrawState != READY_TO_SHOW) {
             return false;
         }
-        if (DEBUG_SURFACE_TRACE || DEBUG_ANIM) {
+        if (DEBUG_ANIM) {
             Slog.i(TAG, "commitFinishDrawingLocked: mDrawState=READY_TO_SHOW " + mSurfaceController);
         }
         mDrawState = READY_TO_SHOW;
@@ -1033,7 +1032,7 @@
                 //Slog.i(TAG_WM, "Not applying alpha transform");
             }
 
-            if ((DEBUG_SURFACE_TRACE || WindowManagerService.localLOGV)
+            if ((DEBUG_ANIM || WindowManagerService.localLOGV)
                     && (mShownAlpha == 1.0 || mShownAlpha == 0.0)) Slog.v(
                     TAG, "computeShownFrameLocked: Animating " + this + " mAlpha=" + mAlpha
                     + " self=" + (selfTransformation ? mTransformation.getAlpha() : "null")
@@ -1112,7 +1111,7 @@
      */
     private boolean useFinalClipRect() {
         return (isAnimationSet() && resolveStackClip() == STACK_CLIP_AFTER_ANIM)
-                || mDestroyPreservedSurfaceUponRedraw || mWin.inPinnedWorkspace();
+                || mDestroyPreservedSurfaceUponRedraw || mWin.inPinnedWindowingMode();
     }
 
     /**
@@ -1177,7 +1176,7 @@
             return false;
         }
 
-        if (w.inPinnedWorkspace()) {
+        if (w.inPinnedWindowingMode()) {
             return false;
         }
 
diff --git a/com/android/server/wm/WindowSurfaceController.java b/com/android/server/wm/WindowSurfaceController.java
index 2e1e3f7..d56df55 100644
--- a/com/android/server/wm/WindowSurfaceController.java
+++ b/com/android/server/wm/WindowSurfaceController.java
@@ -22,7 +22,6 @@
 import static com.android.server.wm.WindowManagerDebugConfig.SHOW_SURFACE_ALLOC;
 import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS;
 import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SURFACE_TRACE;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -565,262 +564,4 @@
     public String toString() {
         return mSurfaceControl.toString();
     }
-
-    static class SurfaceTrace extends SurfaceControl {
-        private final static String SURFACE_TAG = TAG_WITH_CLASS_NAME ? "SurfaceTrace" : TAG_WM;
-        private final static boolean LOG_SURFACE_TRACE = DEBUG_SURFACE_TRACE;
-        final static ArrayList<SurfaceTrace> sSurfaces = new ArrayList<SurfaceTrace>();
-
-        private float mSurfaceTraceAlpha = 0;
-        private int mLayer;
-        private final PointF mPosition = new PointF();
-        private final Point mSize = new Point();
-        private final Rect mWindowCrop = new Rect();
-        private final Rect mFinalCrop = new Rect();
-        private boolean mShown = false;
-        private int mLayerStack;
-        private boolean mIsOpaque;
-        private float mDsdx, mDtdx, mDsdy, mDtdy;
-        private final String mName;
-
-        public SurfaceTrace(SurfaceSession s, String name, int w, int h, int format, int flags,
-                        int windowType, int ownerUid)
-                    throws OutOfResourcesException {
-            super(s, name, w, h, format, flags, windowType, ownerUid);
-            mName = name != null ? name : "Not named";
-            mSize.set(w, h);
-            if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "ctor: " + this + ". Called by "
-                    + Debug.getCallers(3));
-            synchronized (sSurfaces) {
-                sSurfaces.add(0, this);
-            }
-        }
-
-        public SurfaceTrace(SurfaceSession s,
-                        String name, int w, int h, int format, int flags) {
-            super(s, name, w, h, format, flags);
-            mName = name != null ? name : "Not named";
-            mSize.set(w, h);
-            if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "ctor: " + this + ". Called by "
-                    + Debug.getCallers(3));
-            synchronized (sSurfaces) {
-                sSurfaces.add(0, this);
-            }
-        }
-
-        @Override
-        public void setAlpha(float alpha) {
-            if (mSurfaceTraceAlpha != alpha) {
-                if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "setAlpha(" + alpha + "): OLD:" + this +
-                        ". Called by " + Debug.getCallers(3));
-                mSurfaceTraceAlpha = alpha;
-            }
-            super.setAlpha(alpha);
-        }
-
-        @Override
-        public void setLayer(int zorder) {
-            if (zorder != mLayer) {
-                if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "setLayer(" + zorder + "): OLD:" + this
-                        + ". Called by " + Debug.getCallers(3));
-                mLayer = zorder;
-            }
-            super.setLayer(zorder);
-
-            synchronized (sSurfaces) {
-                sSurfaces.remove(this);
-                int i;
-                for (i = sSurfaces.size() - 1; i >= 0; i--) {
-                    SurfaceTrace s = sSurfaces.get(i);
-                    if (s.mLayer < zorder) {
-                        break;
-                    }
-                }
-                sSurfaces.add(i + 1, this);
-            }
-        }
-
-        @Override
-        public void setPosition(float x, float y) {
-            if (x != mPosition.x || y != mPosition.y) {
-                if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "setPosition(" + x + "," + y + "): OLD:"
-                        + this + ". Called by " + Debug.getCallers(3));
-                mPosition.set(x, y);
-            }
-            super.setPosition(x, y);
-        }
-
-        @Override
-        public void setGeometryAppliesWithResize() {
-            if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "setGeometryAppliesWithResize(): OLD: "
-                    + this + ". Called by" + Debug.getCallers(3));
-            super.setGeometryAppliesWithResize();
-        }
-
-        @Override
-        public void setSize(int w, int h) {
-            if (w != mSize.x || h != mSize.y) {
-                if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "setSize(" + w + "," + h + "): OLD:"
-                        + this + ". Called by " + Debug.getCallers(3));
-                mSize.set(w, h);
-            }
-            super.setSize(w, h);
-        }
-
-        @Override
-        public void setWindowCrop(Rect crop) {
-            if (crop != null) {
-                if (!crop.equals(mWindowCrop)) {
-                    if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "setWindowCrop("
-                            + crop.toShortString() + "): OLD:" + this + ". Called by "
-                            + Debug.getCallers(3));
-                    mWindowCrop.set(crop);
-                }
-            }
-            super.setWindowCrop(crop);
-        }
-
-        @Override
-        public void setFinalCrop(Rect crop) {
-            if (crop != null) {
-                if (!crop.equals(mFinalCrop)) {
-                    if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "setFinalCrop("
-                            + crop.toShortString() + "): OLD:" + this + ". Called by "
-                            + Debug.getCallers(3));
-                    mFinalCrop.set(crop);
-                }
-            }
-            super.setFinalCrop(crop);
-        }
-
-        @Override
-        public void setLayerStack(int layerStack) {
-            if (layerStack != mLayerStack) {
-                if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "setLayerStack(" + layerStack + "): OLD:"
-                        + this + ". Called by " + Debug.getCallers(3));
-                mLayerStack = layerStack;
-            }
-            super.setLayerStack(layerStack);
-        }
-
-        @Override
-        public void setOpaque(boolean isOpaque) {
-            if (isOpaque != mIsOpaque) {
-                if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "setOpaque(" + isOpaque + "): OLD:"
-                        + this + ". Called by " + Debug.getCallers(3));
-                mIsOpaque = isOpaque;
-            }
-            super.setOpaque(isOpaque);
-        }
-
-        @Override
-        public void setSecure(boolean isSecure) {
-            super.setSecure(isSecure);
-        }
-
-        @Override
-        public void setMatrix(float dsdx, float dtdx, float dsdy, float dtdy) {
-            if (dsdx != mDsdx || dtdx != mDtdx || dsdy != mDsdy || dtdy != mDtdy) {
-                if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "setMatrix(" + dsdx + "," + dtdx + ","
-                        + dsdy + "," + dtdy + "): OLD:" + this + ". Called by "
-                        + Debug.getCallers(3));
-                mDsdx = dsdx;
-                mDtdx = dtdx;
-                mDsdy = dsdy;
-                mDtdy = dtdy;
-            }
-            super.setMatrix(dsdx, dtdx, dsdy, dtdy);
-        }
-
-        @Override
-        public void hide() {
-            if (mShown) {
-                if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "hide: OLD:" + this + ". Called by "
-                        + Debug.getCallers(3));
-                mShown = false;
-            }
-            super.hide();
-        }
-
-        @Override
-        public void show() {
-            if (!mShown) {
-                if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "show: OLD:" + this + ". Called by "
-                        + Debug.getCallers(3));
-                mShown = true;
-            }
-            super.show();
-        }
-
-        @Override
-        public void destroy() {
-            super.destroy();
-            if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "destroy: " + this + ". Called by "
-                    + Debug.getCallers(3));
-            synchronized (sSurfaces) {
-                sSurfaces.remove(this);
-            }
-        }
-
-        @Override
-        public void release() {
-            super.release();
-            if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "release: " + this + ". Called by "
-                    + Debug.getCallers(3));
-            synchronized (sSurfaces) {
-                sSurfaces.remove(this);
-            }
-        }
-
-        @Override
-        public void setTransparentRegionHint(Region region) {
-            if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "setTransparentRegionHint(" + region
-                    + "): OLD: " + this + " . Called by " + Debug.getCallers(3));
-            super.setTransparentRegionHint(region);
-        }
-
-        static void dumpAllSurfaces(PrintWriter pw, String header) {
-            synchronized (sSurfaces) {
-                final int N = sSurfaces.size();
-                if (N <= 0) {
-                    return;
-                }
-                if (header != null) {
-                    pw.println(header);
-                }
-                pw.println("WINDOW MANAGER SURFACES (dumpsys window surfaces)");
-                for (int i = 0; i < N; i++) {
-                    SurfaceTrace s = sSurfaces.get(i);
-                    pw.print("  Surface #"); pw.print(i); pw.print(": #");
-                            pw.print(Integer.toHexString(System.identityHashCode(s)));
-                            pw.print(" "); pw.println(s.mName);
-                    pw.print("    mLayerStack="); pw.print(s.mLayerStack);
-                            pw.print(" mLayer="); pw.println(s.mLayer);
-                    pw.print("    mShown="); pw.print(s.mShown); pw.print(" mAlpha=");
-                            pw.print(s.mSurfaceTraceAlpha); pw.print(" mIsOpaque=");
-                            pw.println(s.mIsOpaque);
-                    pw.print("    mPosition="); pw.print(s.mPosition.x); pw.print(",");
-                            pw.print(s.mPosition.y);
-                            pw.print(" mSize="); pw.print(s.mSize.x); pw.print("x");
-                            pw.println(s.mSize.y);
-                    pw.print("    mCrop="); s.mWindowCrop.printShortString(pw); pw.println();
-                    pw.print("    mFinalCrop="); s.mFinalCrop.printShortString(pw); pw.println();
-                    pw.print("    Transform: ("); pw.print(s.mDsdx); pw.print(", ");
-                            pw.print(s.mDtdx); pw.print(", "); pw.print(s.mDsdy);
-                            pw.print(", "); pw.print(s.mDtdy); pw.println(")");
-                }
-            }
-        }
-
-        @Override
-        public String toString() {
-            return "Surface " + Integer.toHexString(System.identityHashCode(this)) + " "
-                    + mName + " (" + mLayerStack + "): shown=" + mShown + " layer=" + mLayer
-                    + " alpha=" + mSurfaceTraceAlpha + " " + mPosition.x + "," + mPosition.y
-                    + " " + mSize.x + "x" + mSize.y
-                    + " crop=" + mWindowCrop.toShortString()
-                    + " opaque=" + mIsOpaque
-                    + " (" + mDsdx + "," + mDtdx + "," + mDsdy + "," + mDtdy + ")";
-        }
-    }
 }
diff --git a/com/android/server/wm/WindowSurfacePlacer.java b/com/android/server/wm/WindowSurfacePlacer.java
index af1fa2f..fa33fe8 100644
--- a/com/android/server/wm/WindowSurfacePlacer.java
+++ b/com/android/server/wm/WindowSurfacePlacer.java
@@ -449,6 +449,9 @@
             //       animating?
             wtoken.setVisibility(animLp, false, transit, false, voiceInteraction);
             wtoken.updateReportedVisibilityLocked();
+            // setAllAppWinAnimators so the windows get onExitAnimationDone once the animation is
+            // done.
+            wtoken.setAllAppWinAnimators();
             // Force the allDrawn flag, because we want to start
             // this guy's animations regardless of whether it's
             // gotten drawn.
diff --git a/com/android/settingslib/CustomEditTextPreference.java b/com/android/settingslib/CustomEditTextPreference.java
index 253ca11..90124f1 100644
--- a/com/android/settingslib/CustomEditTextPreference.java
+++ b/com/android/settingslib/CustomEditTextPreference.java
@@ -20,6 +20,7 @@
 import android.content.Context;
 import android.content.DialogInterface;
 import android.os.Bundle;
+import android.support.annotation.CallSuper;
 import android.support.v14.preference.EditTextPreferenceDialogFragment;
 import android.support.v7.preference.EditTextPreference;
 import android.util.AttributeSet;
@@ -30,7 +31,8 @@
 
     private CustomPreferenceDialogFragment mFragment;
 
-    public CustomEditTextPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+    public CustomEditTextPreference(Context context, AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
     }
 
@@ -69,7 +71,12 @@
     protected void onClick(DialogInterface dialog, int which) {
     }
 
+    @CallSuper
     protected void onBindDialogView(View view) {
+        final EditText editText = view.findViewById(android.R.id.edit);
+        if (editText != null) {
+            editText.requestFocus();
+        }
     }
 
     private void setFragment(CustomPreferenceDialogFragment fragment) {
diff --git a/com/android/settingslib/Utils.java b/com/android/settingslib/Utils.java
index 8def036..d90386f 100644
--- a/com/android/settingslib/Utils.java
+++ b/com/android/settingslib/Utils.java
@@ -12,9 +12,7 @@
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
 import android.graphics.Color;
-import android.graphics.PorterDuff;
 import android.graphics.drawable.Drawable;
 import android.net.ConnectivityManager;
 import android.os.BatteryManager;
@@ -297,4 +295,9 @@
         }
         return defaultDays;
     }
+
+    public static boolean isWifiOnly(Context context) {
+        return !context.getSystemService(ConnectivityManager.class)
+                .isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
+    }
 }
diff --git a/com/android/settingslib/bluetooth/BluetoothEventManager.java b/com/android/settingslib/bluetooth/BluetoothEventManager.java
index 28105e2..f57d02b 100644
--- a/com/android/settingslib/bluetooth/BluetoothEventManager.java
+++ b/com/android/settingslib/bluetooth/BluetoothEventManager.java
@@ -107,14 +107,16 @@
         addHandler(Intent.ACTION_DOCK_EVENT, new DockEventHandler());
 
         mContext.registerReceiver(mBroadcastReceiver, mAdapterIntentFilter, null, mReceiverHandler);
+        mContext.registerReceiver(mProfileBroadcastReceiver, mProfileIntentFilter, null, mReceiverHandler);
     }
 
     void registerProfileIntentReceiver() {
-        mContext.registerReceiver(mBroadcastReceiver, mProfileIntentFilter, null, mReceiverHandler);
+        mContext.registerReceiver(mProfileBroadcastReceiver, mProfileIntentFilter, null, mReceiverHandler);
     }
 
     public void setReceiverHandler(android.os.Handler handler) {
         mContext.unregisterReceiver(mBroadcastReceiver);
+        mContext.unregisterReceiver(mProfileBroadcastReceiver);
         mReceiverHandler = handler;
         mContext.registerReceiver(mBroadcastReceiver, mAdapterIntentFilter, null, mReceiverHandler);
         registerProfileIntentReceiver();
@@ -148,11 +150,31 @@
         }
     };
 
+    private final BroadcastReceiver mProfileBroadcastReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            BluetoothDevice device = intent
+                    .getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+
+            Handler handler = mHandlerMap.get(action);
+            if (handler != null) {
+                handler.onReceive(context, intent, device);
+            }
+        }
+    };
+
     private class AdapterStateChangedHandler implements Handler {
         public void onReceive(Context context, Intent intent,
                 BluetoothDevice device) {
             int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
                                     BluetoothAdapter.ERROR);
+            // Reregister Profile Broadcast Receiver as part of TURN OFF
+            if (state == BluetoothAdapter.STATE_OFF)
+            {
+                context.unregisterReceiver(mProfileBroadcastReceiver);
+                registerProfileIntentReceiver();
+            }
             // update local profiles and get paired devices
             mLocalAdapter.setBluetoothStateInt(state);
             // send callback to update UI and possibly start scanning
diff --git a/com/android/settingslib/bluetooth/HidProfile.java b/com/android/settingslib/bluetooth/HidProfile.java
index d1621da..213002f 100644
--- a/com/android/settingslib/bluetooth/HidProfile.java
+++ b/com/android/settingslib/bluetooth/HidProfile.java
@@ -19,7 +19,7 @@
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothInputDevice;
+import android.bluetooth.BluetoothHidHost;
 import android.bluetooth.BluetoothProfile;
 import android.content.Context;
 import android.util.Log;
@@ -35,7 +35,7 @@
     private static final String TAG = "HidProfile";
     private static boolean V = true;
 
-    private BluetoothInputDevice mService;
+    private BluetoothHidHost mService;
     private boolean mIsProfileReady;
 
     private final LocalBluetoothAdapter mLocalAdapter;
@@ -53,7 +53,7 @@
 
         public void onServiceConnected(int profile, BluetoothProfile proxy) {
             if (V) Log.d(TAG,"Bluetooth service connected");
-            mService = (BluetoothInputDevice) proxy;
+            mService = (BluetoothHidHost) proxy;
             // We just bound to the service, so refresh the UI for any connected HID devices.
             List<BluetoothDevice> deviceList = mService.getConnectedDevices();
             while (!deviceList.isEmpty()) {
@@ -87,7 +87,7 @@
         mDeviceManager = deviceManager;
         mProfileManager = profileManager;
         adapter.getProfileProxy(context, new InputDeviceServiceListener(),
-                BluetoothProfile.INPUT_DEVICE);
+                BluetoothProfile.HID_HOST);
     }
 
     public boolean isConnectable() {
@@ -190,7 +190,7 @@
         if (V) Log.d(TAG, "finalize()");
         if (mService != null) {
             try {
-                BluetoothAdapter.getDefaultAdapter().closeProfileProxy(BluetoothProfile.INPUT_DEVICE,
+                BluetoothAdapter.getDefaultAdapter().closeProfileProxy(BluetoothProfile.HID_HOST,
                                                                        mService);
                 mService = null;
             }catch (Throwable t) {
diff --git a/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java b/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
index 0750dc7..9cda669 100644
--- a/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
+++ b/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
@@ -21,9 +21,9 @@
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHeadset;
 import android.bluetooth.BluetoothHeadsetClient;
+import android.bluetooth.BluetoothHidHost;
 import android.bluetooth.BluetoothMap;
 import android.bluetooth.BluetoothMapClient;
-import android.bluetooth.BluetoothInputDevice;
 import android.bluetooth.BluetoothPan;
 import android.bluetooth.BluetoothPbapClient;
 import android.bluetooth.BluetoothProfile;
@@ -123,7 +123,7 @@
         // Always add HID and PAN profiles
         mHidProfile = new HidProfile(context, mLocalAdapter, mDeviceManager, this);
         addProfile(mHidProfile, HidProfile.NAME,
-                BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED);
+                BluetoothHidHost.ACTION_CONNECTION_STATE_CHANGED);
 
         mPanProfile = new PanProfile(context);
         addPanProfile(mPanProfile, PanProfile.NAME,
diff --git a/com/android/settingslib/development/AbstractLogdSizePreferenceController.java b/com/android/settingslib/development/AbstractLogdSizePreferenceController.java
index 7998b2e..f79be7e 100644
--- a/com/android/settingslib/development/AbstractLogdSizePreferenceController.java
+++ b/com/android/settingslib/development/AbstractLogdSizePreferenceController.java
@@ -33,21 +33,27 @@
             + "AbstractLogdSizePreferenceController.LOGD_SIZE_UPDATED";
     public static final String EXTRA_CURRENT_LOGD_VALUE = "CURRENT_LOGD_VALUE";
 
+    @VisibleForTesting
+    static final String LOW_RAM_CONFIG_PROPERTY_KEY = "ro.config.low_ram";
     private static final String SELECT_LOGD_SIZE_KEY = "select_logd_size";
     @VisibleForTesting
     static final String SELECT_LOGD_SIZE_PROPERTY = "persist.logd.size";
     static final String SELECT_LOGD_TAG_PROPERTY = "persist.log.tag";
     // Tricky, isLoggable only checks for first character, assumes silence
     static final String SELECT_LOGD_TAG_SILENCE = "Settings";
-    private static final String SELECT_LOGD_SNET_TAG_PROPERTY = "persist.log.tag.snet_event_log";
+    @VisibleForTesting
+    static final String SELECT_LOGD_SNET_TAG_PROPERTY = "persist.log.tag.snet_event_log";
     private static final String SELECT_LOGD_RUNTIME_SNET_TAG_PROPERTY = "log.tag.snet_event_log";
     private static final String SELECT_LOGD_DEFAULT_SIZE_PROPERTY = "ro.logd.size";
     @VisibleForTesting
     static final String SELECT_LOGD_DEFAULT_SIZE_VALUE = "262144";
     private static final String SELECT_LOGD_SVELTE_DEFAULT_SIZE_VALUE = "65536";
     // 32768 is merely a menu marker, 64K is our lowest log buffer size we replace it with.
-    private static final String SELECT_LOGD_MINIMUM_SIZE_VALUE = "65536";
+    @VisibleForTesting
+    static final String SELECT_LOGD_MINIMUM_SIZE_VALUE = "65536";
     static final String SELECT_LOGD_OFF_SIZE_MARKER_VALUE = "32768";
+    @VisibleForTesting
+    static final String DEFAULT_SNET_TAG = "I";
 
     private ListPreference mLogdSize;
 
@@ -154,7 +160,7 @@
             if ((snetValue == null) || (snetValue.length() == 0)) {
                 snetValue = SystemProperties.get(SELECT_LOGD_RUNTIME_SNET_TAG_PROPERTY);
                 if ((snetValue == null) || (snetValue.length() == 0)) {
-                    SystemProperties.set(SELECT_LOGD_SNET_TAG_PROPERTY, "I");
+                    SystemProperties.set(SELECT_LOGD_SNET_TAG_PROPERTY, DEFAULT_SNET_TAG);
                 }
             }
             // Silence all log sources, security logs notwithstanding
diff --git a/com/android/settingslib/development/AbstractLogpersistPreferenceController.java b/com/android/settingslib/development/AbstractLogpersistPreferenceController.java
index 67553ad..77b2d86 100644
--- a/com/android/settingslib/development/AbstractLogpersistPreferenceController.java
+++ b/com/android/settingslib/development/AbstractLogpersistPreferenceController.java
@@ -68,7 +68,7 @@
 
     public AbstractLogpersistPreferenceController(Context context, Lifecycle lifecycle) {
         super(context);
-        if (isAvailable()) {
+        if (isAvailable() && lifecycle != null) {
             lifecycle.addObserver(this);
         }
     }
diff --git a/com/android/settingslib/deviceinfo/AbstractBluetoothAddressPreferenceController.java b/com/android/settingslib/deviceinfo/AbstractBluetoothAddressPreferenceController.java
new file mode 100644
index 0000000..ba358f8
--- /dev/null
+++ b/com/android/settingslib/deviceinfo/AbstractBluetoothAddressPreferenceController.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.deviceinfo;
+
+import android.annotation.SuppressLint;
+import android.bluetooth.BluetoothAdapter;
+import android.content.Context;
+import android.support.annotation.VisibleForTesting;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceScreen;
+import android.text.TextUtils;
+
+import com.android.settingslib.R;
+import com.android.settingslib.core.lifecycle.Lifecycle;
+
+/**
+ * Preference controller for bluetooth address
+ */
+public abstract class AbstractBluetoothAddressPreferenceController
+        extends AbstractConnectivityPreferenceController {
+
+    @VisibleForTesting
+    static final String KEY_BT_ADDRESS = "bt_address";
+
+    private static final String[] CONNECTIVITY_INTENTS = {
+            BluetoothAdapter.ACTION_STATE_CHANGED
+    };
+
+    private Preference mBtAddress;
+
+    public AbstractBluetoothAddressPreferenceController(Context context, Lifecycle lifecycle) {
+        super(context, lifecycle);
+    }
+
+    @Override
+    public boolean isAvailable() {
+        return BluetoothAdapter.getDefaultAdapter() != null;
+    }
+
+    @Override
+    public String getPreferenceKey() {
+        return KEY_BT_ADDRESS;
+    }
+
+    @Override
+    public void displayPreference(PreferenceScreen screen) {
+        super.displayPreference(screen);
+        mBtAddress = screen.findPreference(KEY_BT_ADDRESS);
+        updateConnectivity();
+    }
+
+    @Override
+    protected String[] getConnectivityIntents() {
+        return CONNECTIVITY_INTENTS;
+    }
+
+    @SuppressLint("HardwareIds")
+    @Override
+    protected void updateConnectivity() {
+        BluetoothAdapter bluetooth = BluetoothAdapter.getDefaultAdapter();
+        if (bluetooth != null && mBtAddress != null) {
+            String address = bluetooth.isEnabled() ? bluetooth.getAddress() : null;
+            if (!TextUtils.isEmpty(address)) {
+                // Convert the address to lowercase for consistency with the wifi MAC address.
+                mBtAddress.setSummary(address.toLowerCase());
+            } else {
+                mBtAddress.setSummary(R.string.status_unavailable);
+            }
+        }
+    }
+}
diff --git a/com/android/settingslib/deviceinfo/AbstractConnectivityPreferenceController.java b/com/android/settingslib/deviceinfo/AbstractConnectivityPreferenceController.java
new file mode 100644
index 0000000..c6552f7
--- /dev/null
+++ b/com/android/settingslib/deviceinfo/AbstractConnectivityPreferenceController.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.deviceinfo;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Handler;
+import android.os.Message;
+
+import com.android.internal.util.ArrayUtils;
+import com.android.settingslib.core.AbstractPreferenceController;
+import com.android.settingslib.core.lifecycle.Lifecycle;
+import com.android.settingslib.core.lifecycle.LifecycleObserver;
+import com.android.settingslib.core.lifecycle.events.OnStart;
+import com.android.settingslib.core.lifecycle.events.OnStop;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * Base class for preference controllers which listen to connectivity broadcasts
+ */
+public abstract class AbstractConnectivityPreferenceController
+        extends AbstractPreferenceController implements LifecycleObserver, OnStart, OnStop {
+
+    private final BroadcastReceiver mConnectivityReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if (ArrayUtils.contains(getConnectivityIntents(), action)) {
+                getHandler().sendEmptyMessage(EVENT_UPDATE_CONNECTIVITY);
+            }
+        }
+    };
+
+    private static final int EVENT_UPDATE_CONNECTIVITY = 600;
+
+    private Handler mHandler;
+
+    public AbstractConnectivityPreferenceController(Context context, Lifecycle lifecycle) {
+        super(context);
+        if (lifecycle != null) {
+            lifecycle.addObserver(this);
+        }
+    }
+
+    @Override
+    public void onStop() {
+        mContext.unregisterReceiver(mConnectivityReceiver);
+    }
+
+    @Override
+    public void onStart() {
+        final IntentFilter connectivityIntentFilter = new IntentFilter();
+        final String[] intents = getConnectivityIntents();
+        for (String intent : intents) {
+            connectivityIntentFilter.addAction(intent);
+        }
+
+        mContext.registerReceiver(mConnectivityReceiver, connectivityIntentFilter,
+                android.Manifest.permission.CHANGE_NETWORK_STATE, null);
+    }
+
+    protected abstract String[] getConnectivityIntents();
+
+    protected abstract void updateConnectivity();
+
+    private Handler getHandler() {
+        if (mHandler == null) {
+            mHandler = new ConnectivityEventHandler(this);
+        }
+        return mHandler;
+    }
+
+    private static class ConnectivityEventHandler extends Handler {
+        private WeakReference<AbstractConnectivityPreferenceController> mPreferenceController;
+
+        public ConnectivityEventHandler(AbstractConnectivityPreferenceController activity) {
+            mPreferenceController = new WeakReference<>(activity);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            AbstractConnectivityPreferenceController preferenceController
+                    = mPreferenceController.get();
+            if (preferenceController == null) {
+                return;
+            }
+
+            switch (msg.what) {
+                case EVENT_UPDATE_CONNECTIVITY:
+                    preferenceController.updateConnectivity();
+                    break;
+                default:
+                    throw new IllegalStateException("Unknown message " + msg.what);
+            }
+        }
+    }
+}
diff --git a/com/android/settingslib/deviceinfo/AbstractImsStatusPreferenceController.java b/com/android/settingslib/deviceinfo/AbstractImsStatusPreferenceController.java
new file mode 100644
index 0000000..bb8404b
--- /dev/null
+++ b/com/android/settingslib/deviceinfo/AbstractImsStatusPreferenceController.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.deviceinfo;
+
+import android.bluetooth.BluetoothAdapter;
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.wifi.WifiManager;
+import android.os.PersistableBundle;
+import android.support.annotation.VisibleForTesting;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceScreen;
+import android.telephony.CarrierConfigManager;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+
+import com.android.settingslib.R;
+import com.android.settingslib.core.lifecycle.Lifecycle;
+
+/**
+ * Preference controller for IMS status
+ */
+public abstract class AbstractImsStatusPreferenceController
+        extends AbstractConnectivityPreferenceController {
+
+    @VisibleForTesting
+    static final String KEY_IMS_REGISTRATION_STATE = "ims_reg_state";
+
+    private static final String[] CONNECTIVITY_INTENTS = {
+            BluetoothAdapter.ACTION_STATE_CHANGED,
+            ConnectivityManager.CONNECTIVITY_ACTION,
+            WifiManager.LINK_CONFIGURATION_CHANGED_ACTION,
+            WifiManager.NETWORK_STATE_CHANGED_ACTION,
+    };
+
+    private Preference mImsStatus;
+
+    public AbstractImsStatusPreferenceController(Context context,
+            Lifecycle lifecycle) {
+        super(context, lifecycle);
+    }
+
+    @Override
+    public boolean isAvailable() {
+        CarrierConfigManager configManager = mContext.getSystemService(CarrierConfigManager.class);
+        int subId = SubscriptionManager.getDefaultDataSubscriptionId();
+        PersistableBundle config = null;
+        if (configManager != null) {
+            config = configManager.getConfigForSubId(subId);
+        }
+        return config != null && config.getBoolean(
+                CarrierConfigManager.KEY_SHOW_IMS_REGISTRATION_STATUS_BOOL);
+    }
+
+    @Override
+    public String getPreferenceKey() {
+        return KEY_IMS_REGISTRATION_STATE;
+    }
+
+    @Override
+    public void displayPreference(PreferenceScreen screen) {
+        super.displayPreference(screen);
+        mImsStatus = screen.findPreference(KEY_IMS_REGISTRATION_STATE);
+        updateConnectivity();
+    }
+
+    @Override
+    protected String[] getConnectivityIntents() {
+        return CONNECTIVITY_INTENTS;
+    }
+
+    @Override
+    protected void updateConnectivity() {
+        int subId = SubscriptionManager.getDefaultDataSubscriptionId();
+        if (mImsStatus != null) {
+            TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
+            mImsStatus.setSummary((tm != null && tm.isImsRegistered(subId)) ?
+                    R.string.ims_reg_status_registered : R.string.ims_reg_status_not_registered);
+        }
+    }
+}
diff --git a/com/android/settingslib/deviceinfo/AbstractIpAddressPreferenceController.java b/com/android/settingslib/deviceinfo/AbstractIpAddressPreferenceController.java
new file mode 100644
index 0000000..ded3022
--- /dev/null
+++ b/com/android/settingslib/deviceinfo/AbstractIpAddressPreferenceController.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.deviceinfo;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.LinkProperties;
+import android.net.wifi.WifiManager;
+import android.support.annotation.VisibleForTesting;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceScreen;
+
+import com.android.settingslib.R;
+import com.android.settingslib.core.lifecycle.Lifecycle;
+
+import java.net.InetAddress;
+import java.util.Iterator;
+
+/**
+ * Preference controller for IP address
+ */
+public abstract class AbstractIpAddressPreferenceController
+        extends AbstractConnectivityPreferenceController {
+
+    @VisibleForTesting
+    static final String KEY_IP_ADDRESS = "wifi_ip_address";
+
+    private static final String[] CONNECTIVITY_INTENTS = {
+            ConnectivityManager.CONNECTIVITY_ACTION,
+            WifiManager.LINK_CONFIGURATION_CHANGED_ACTION,
+            WifiManager.NETWORK_STATE_CHANGED_ACTION,
+    };
+
+    private Preference mIpAddress;
+    private final ConnectivityManager mCM;
+
+    public AbstractIpAddressPreferenceController(Context context, Lifecycle lifecycle) {
+        super(context, lifecycle);
+        mCM = context.getSystemService(ConnectivityManager.class);
+    }
+
+    @Override
+    public boolean isAvailable() {
+        return true;
+    }
+
+    @Override
+    public String getPreferenceKey() {
+        return KEY_IP_ADDRESS;
+    }
+
+    @Override
+    public void displayPreference(PreferenceScreen screen) {
+        super.displayPreference(screen);
+        mIpAddress = screen.findPreference(KEY_IP_ADDRESS);
+        updateConnectivity();
+    }
+
+    @Override
+    protected String[] getConnectivityIntents() {
+        return CONNECTIVITY_INTENTS;
+    }
+
+    @Override
+    protected void updateConnectivity() {
+        String ipAddress = getDefaultIpAddresses(mCM);
+        if (ipAddress != null) {
+            mIpAddress.setSummary(ipAddress);
+        } else {
+            mIpAddress.setSummary(R.string.status_unavailable);
+        }
+    }
+
+    /**
+     * Returns the default link's IP addresses, if any, taking into account IPv4 and IPv6 style
+     * addresses.
+     * @param cm ConnectivityManager
+     * @return the formatted and newline-separated IP addresses, or null if none.
+     */
+    private static String getDefaultIpAddresses(ConnectivityManager cm) {
+        LinkProperties prop = cm.getActiveLinkProperties();
+        return formatIpAddresses(prop);
+    }
+
+    private static String formatIpAddresses(LinkProperties prop) {
+        if (prop == null) return null;
+        Iterator<InetAddress> iter = prop.getAllAddresses().iterator();
+        // If there are no entries, return null
+        if (!iter.hasNext()) return null;
+        // Concatenate all available addresses, newline separated
+        StringBuilder addresses = new StringBuilder();
+        while (iter.hasNext()) {
+            addresses.append(iter.next().getHostAddress());
+            if (iter.hasNext()) addresses.append("\n");
+        }
+        return addresses.toString();
+    }
+}
diff --git a/com/android/settingslib/deviceinfo/AbstractSimStatusImeiInfoPreferenceController.java b/com/android/settingslib/deviceinfo/AbstractSimStatusImeiInfoPreferenceController.java
new file mode 100644
index 0000000..a78440c
--- /dev/null
+++ b/com/android/settingslib/deviceinfo/AbstractSimStatusImeiInfoPreferenceController.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.deviceinfo;
+
+import android.content.Context;
+import android.os.UserManager;
+
+import com.android.settingslib.Utils;
+import com.android.settingslib.core.AbstractPreferenceController;
+
+public abstract class AbstractSimStatusImeiInfoPreferenceController
+        extends AbstractPreferenceController {
+    public AbstractSimStatusImeiInfoPreferenceController(Context context) {
+        super(context);
+    }
+
+    @Override
+    public boolean isAvailable() {
+        return mContext.getSystemService(UserManager.class).isAdminUser()
+                && !Utils.isWifiOnly(mContext);
+    }
+}
diff --git a/com/android/settingslib/deviceinfo/AbstractUptimePreferenceController.java b/com/android/settingslib/deviceinfo/AbstractUptimePreferenceController.java
new file mode 100644
index 0000000..ac61ade
--- /dev/null
+++ b/com/android/settingslib/deviceinfo/AbstractUptimePreferenceController.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.deviceinfo;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Message;
+import android.os.SystemClock;
+import android.support.annotation.VisibleForTesting;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceScreen;
+import android.text.format.DateUtils;
+
+import com.android.settingslib.core.AbstractPreferenceController;
+import com.android.settingslib.core.lifecycle.Lifecycle;
+import com.android.settingslib.core.lifecycle.LifecycleObserver;
+import com.android.settingslib.core.lifecycle.events.OnStart;
+import com.android.settingslib.core.lifecycle.events.OnStop;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * Preference controller for uptime
+ */
+public abstract class AbstractUptimePreferenceController extends AbstractPreferenceController
+        implements LifecycleObserver, OnStart, OnStop {
+
+    @VisibleForTesting
+    static final String KEY_UPTIME = "up_time";
+    private static final int EVENT_UPDATE_STATS = 500;
+
+    private Preference mUptime;
+    private Handler mHandler;
+
+    public AbstractUptimePreferenceController(Context context, Lifecycle lifecycle) {
+        super(context);
+        if (lifecycle != null) {
+            lifecycle.addObserver(this);
+        }
+    }
+
+    @Override
+    public void onStart() {
+        getHandler().sendEmptyMessage(EVENT_UPDATE_STATS);
+    }
+
+    @Override
+    public void onStop() {
+        getHandler().removeMessages(EVENT_UPDATE_STATS);
+    }
+
+    @Override
+    public boolean isAvailable() {
+        return true;
+    }
+
+    @Override
+    public String getPreferenceKey() {
+        return KEY_UPTIME;
+    }
+
+    @Override
+    public void displayPreference(PreferenceScreen screen) {
+        super.displayPreference(screen);
+        mUptime = screen.findPreference(KEY_UPTIME);
+        updateTimes();
+    }
+
+    private Handler getHandler() {
+        if (mHandler == null) {
+            mHandler = new MyHandler(this);
+        }
+        return mHandler;
+    }
+
+    private void updateTimes() {
+        mUptime.setSummary(DateUtils.formatDuration(SystemClock.elapsedRealtime()));
+    }
+
+    private static class MyHandler extends Handler {
+        private WeakReference<AbstractUptimePreferenceController> mStatus;
+
+        public MyHandler(AbstractUptimePreferenceController activity) {
+            mStatus = new WeakReference<>(activity);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            AbstractUptimePreferenceController status = mStatus.get();
+            if (status == null) {
+                return;
+            }
+
+            switch (msg.what) {
+                case EVENT_UPDATE_STATS:
+                    status.updateTimes();
+                    sendEmptyMessageDelayed(EVENT_UPDATE_STATS, 1000);
+                    break;
+
+                default:
+                    throw new IllegalStateException("Unknown message " + msg.what);
+            }
+        }
+    }
+}
diff --git a/com/android/settingslib/deviceinfo/AbstractWifiMacAddressPreferenceController.java b/com/android/settingslib/deviceinfo/AbstractWifiMacAddressPreferenceController.java
new file mode 100644
index 0000000..d57b64f
--- /dev/null
+++ b/com/android/settingslib/deviceinfo/AbstractWifiMacAddressPreferenceController.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.deviceinfo;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.support.annotation.VisibleForTesting;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceScreen;
+import android.text.TextUtils;
+
+import com.android.settingslib.R;
+import com.android.settingslib.core.lifecycle.Lifecycle;
+
+/**
+ * Preference controller for WIFI MAC address
+ */
+public abstract class AbstractWifiMacAddressPreferenceController
+        extends AbstractConnectivityPreferenceController {
+
+    @VisibleForTesting
+    static final String KEY_WIFI_MAC_ADDRESS = "wifi_mac_address";
+
+    private static final String[] CONNECTIVITY_INTENTS = {
+            ConnectivityManager.CONNECTIVITY_ACTION,
+            WifiManager.LINK_CONFIGURATION_CHANGED_ACTION,
+            WifiManager.NETWORK_STATE_CHANGED_ACTION,
+    };
+
+    private Preference mWifiMacAddress;
+    private final WifiManager mWifiManager;
+
+    public AbstractWifiMacAddressPreferenceController(Context context, Lifecycle lifecycle) {
+        super(context, lifecycle);
+        mWifiManager = context.getSystemService(WifiManager.class);
+    }
+
+    @Override
+    public boolean isAvailable() {
+        return true;
+    }
+
+    @Override
+    public String getPreferenceKey() {
+        return KEY_WIFI_MAC_ADDRESS;
+    }
+
+    @Override
+    public void displayPreference(PreferenceScreen screen) {
+        super.displayPreference(screen);
+        mWifiMacAddress = screen.findPreference(KEY_WIFI_MAC_ADDRESS);
+        updateConnectivity();
+    }
+
+    @Override
+    protected String[] getConnectivityIntents() {
+        return CONNECTIVITY_INTENTS;
+    }
+
+    @SuppressLint("HardwareIds")
+    @Override
+    protected void updateConnectivity() {
+        WifiInfo wifiInfo = mWifiManager.getConnectionInfo();
+        String macAddress = wifiInfo == null ? null : wifiInfo.getMacAddress();
+        if (!TextUtils.isEmpty(macAddress)) {
+            mWifiMacAddress.setSummary(macAddress);
+        } else {
+            mWifiMacAddress.setSummary(R.string.status_unavailable);
+        }
+    }
+}
diff --git a/com/android/settingslib/suggestions/SuggestionParser.java b/com/android/settingslib/suggestions/SuggestionParser.java
index 56b8441..9c34763 100644
--- a/com/android/settingslib/suggestions/SuggestionParser.java
+++ b/com/android/settingslib/suggestions/SuggestionParser.java
@@ -261,7 +261,7 @@
         if (requiredAccountType == null) {
             return true;
         }
-        AccountManager accountManager = AccountManager.get(mContext);
+        AccountManager accountManager = mContext.getSystemService(AccountManager.class);
         Account[] accounts = accountManager.getAccountsByType(requiredAccountType);
         boolean satisfiesRequiredAccount = accounts.length > 0;
         if (!satisfiesRequiredAccount) {
diff --git a/com/android/settingslib/wifi/WifiTracker.java b/com/android/settingslib/wifi/WifiTracker.java
index 12455d8..c56e1da 100644
--- a/com/android/settingslib/wifi/WifiTracker.java
+++ b/com/android/settingslib/wifi/WifiTracker.java
@@ -291,15 +291,6 @@
     }
 
     /**
-     * Force a scan for wifi networks to happen now.
-     */
-    public void forceScan() {
-        if (mWifiManager.isWifiEnabled() && mScanner != null) {
-            mScanner.forceScan();
-        }
-    }
-
-    /**
      * Temporarily stop scanning for wifi networks.
      */
     public void pauseScanning() {
@@ -789,14 +780,6 @@
         }
     }
 
-    public static List<AccessPoint> getCurrentAccessPoints(Context context, boolean includeSaved,
-            boolean includeScans) {
-        WifiTracker tracker = new WifiTracker(context, null, includeSaved, includeScans);
-        tracker.forceUpdate();
-        tracker.copyAndNotifyListeners(false /*notifyListeners*/);
-        return tracker.getAccessPoints();
-    }
-
     @VisibleForTesting
     final BroadcastReceiver mReceiver = new BroadcastReceiver() {
         @Override
@@ -986,11 +969,6 @@
             }
         }
 
-        void forceScan() {
-            removeMessages(MSG_SCAN);
-            sendEmptyMessage(MSG_SCAN);
-        }
-
         void pause() {
             mRetry = 0;
             removeMessages(MSG_SCAN);
diff --git a/com/android/setupwizardlib/util/WizardManagerHelper.java b/com/android/setupwizardlib/util/WizardManagerHelper.java
index a93694c..896c013 100644
--- a/com/android/setupwizardlib/util/WizardManagerHelper.java
+++ b/com/android/setupwizardlib/util/WizardManagerHelper.java
@@ -45,6 +45,8 @@
     static final String EXTRA_IS_FIRST_RUN = "firstRun";
     @VisibleForTesting
     static final String EXTRA_IS_DEFERRED_SETUP = "deferredSetup";
+    @VisibleForTesting
+    static final String EXTRA_IS_PRE_DEFERRED_SETUP = "preDeferredSetup";
 
     public static final String EXTRA_THEME = "theme";
     public static final String EXTRA_USE_IMMERSIVE_MODE = "useImmersiveMode";
@@ -213,6 +215,18 @@
     }
 
     /**
+     * Checks whether an intent is running in "pre-deferred" setup wizard flow.
+     *
+     * @param originalIntent The original intent that was used to start the step, usually via
+     *                       {@link android.app.Activity#getIntent()}.
+     * @return true if the intent passed in was running in "pre-deferred" setup wizard.
+     */
+    public static boolean isPreDeferredSetupWizard(Intent originalIntent) {
+        return originalIntent != null
+                && originalIntent.getBooleanExtra(EXTRA_IS_PRE_DEFERRED_SETUP, false);
+    }
+
+    /**
      * Checks the intent whether the extra indicates that the light theme should be used or not. If
      * the theme is not specified in the intent, or the theme specified is unknown, the value def
      * will be returned.
diff --git a/com/android/setupwizardlib/util/WizardManagerHelperTest.java b/com/android/setupwizardlib/util/WizardManagerHelperTest.java
index 4c460c8..6477b51 100644
--- a/com/android/setupwizardlib/util/WizardManagerHelperTest.java
+++ b/com/android/setupwizardlib/util/WizardManagerHelperTest.java
@@ -88,6 +88,14 @@
     }
 
     @Test
+    public void testIsPreDeferredSetupTrue() {
+        final Intent intent = new Intent();
+        intent.putExtra("preDeferredSetup", true);
+        assertTrue("Is pre-deferred setup wizard should be true",
+                WizardManagerHelper.isPreDeferredSetupWizard(intent));
+    }
+
+    @Test
     public void testIsSetupWizardFalse() {
         final Intent intent = new Intent();
         intent.putExtra("firstRun", false);
diff --git a/com/android/systemui/Dependency.java b/com/android/systemui/Dependency.java
index 2937a25..d8a47c5 100644
--- a/com/android/systemui/Dependency.java
+++ b/com/android/systemui/Dependency.java
@@ -22,6 +22,8 @@
 import android.os.Looper;
 import android.os.Process;
 import android.util.ArrayMap;
+import android.view.IWindowManager;
+import android.view.WindowManagerGlobal;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.NightDisplayController;
@@ -304,6 +306,8 @@
 
         mProviders.put(LightBarController.class, () -> new LightBarController(mContext));
 
+        mProviders.put(IWindowManager.class, () -> WindowManagerGlobal.getWindowManagerService());
+
         // Put all dependencies above here so the factory can override them if it wants.
         SystemUIFactory.getInstance().injectDependencies(mProviders, mContext);
     }
diff --git a/com/android/systemui/ImageWallpaper.java b/com/android/systemui/ImageWallpaper.java
index 907a79e..593bb50 100644
--- a/com/android/systemui/ImageWallpaper.java
+++ b/com/android/systemui/ImageWallpaper.java
@@ -16,11 +16,6 @@
 
 package com.android.systemui;
 
-import static android.opengl.GLES20.*;
-
-import static javax.microedition.khronos.egl.EGL10.*;
-
-import android.app.ActivityManager;
 import android.app.WallpaperManager;
 import android.content.ComponentCallbacks2;
 import android.graphics.Bitmap;
@@ -28,31 +23,18 @@
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.Region.Op;
-import android.opengl.GLUtils;
 import android.os.AsyncTask;
-import android.os.SystemProperties;
 import android.os.Trace;
-import android.renderscript.Matrix4f;
 import android.service.wallpaper.WallpaperService;
 import android.util.Log;
 import android.view.Display;
 import android.view.DisplayInfo;
-import android.view.MotionEvent;
 import android.view.SurfaceHolder;
 import android.view.WindowManager;
 
 import java.io.FileDescriptor;
 import java.io.IOException;
 import java.io.PrintWriter;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.nio.FloatBuffer;
-
-import javax.microedition.khronos.egl.EGL10;
-import javax.microedition.khronos.egl.EGLConfig;
-import javax.microedition.khronos.egl.EGLContext;
-import javax.microedition.khronos.egl.EGLDisplay;
-import javax.microedition.khronos.egl.EGLSurface;
 
 /**
  * Default built-in wallpaper that simply shows a static image.
@@ -64,24 +46,13 @@
     private static final boolean DEBUG = false;
     private static final String PROPERTY_KERNEL_QEMU = "ro.kernel.qemu";
 
-    static final boolean FIXED_SIZED_SURFACE = true;
-    static final boolean USE_OPENGL = true;
-
-    WallpaperManager mWallpaperManager;
-
-    DrawableEngine mEngine;
-
-    boolean mIsHwAccelerated;
+    private WallpaperManager mWallpaperManager;
+    private DrawableEngine mEngine;
 
     @Override
     public void onCreate() {
         super.onCreate();
-        mWallpaperManager = (WallpaperManager) getSystemService(WALLPAPER_SERVICE);
-
-        //noinspection PointlessBooleanExpression,ConstantConditions
-        if (FIXED_SIZED_SURFACE && USE_OPENGL) {
-            mIsHwAccelerated = ActivityManager.isHighEndGfx();
-        }
+        mWallpaperManager = getSystemService(WallpaperManager.class);
     }
 
     @Override
@@ -98,15 +69,12 @@
     }
 
     class DrawableEngine extends Engine {
-        static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
-        static final int EGL_OPENGL_ES2_BIT = 4;
-
         Bitmap mBackground;
         int mBackgroundWidth = -1, mBackgroundHeight = -1;
         int mLastSurfaceWidth = -1, mLastSurfaceHeight = -1;
         int mLastRotation = -1;
-        float mXOffset = 0.5f;
-        float mYOffset = 0.5f;
+        float mXOffset = 0f;
+        float mYOffset = 0f;
         float mScale = 1f;
 
         private Display mDefaultDisplay;
@@ -117,34 +85,6 @@
         int mLastXTranslation;
         int mLastYTranslation;
 
-        private EGL10 mEgl;
-        private EGLDisplay mEglDisplay;
-        private EGLConfig mEglConfig;
-        private EGLContext mEglContext;
-        private EGLSurface mEglSurface;
-
-        private static final String sSimpleVS =
-                "attribute vec4 position;\n" +
-                "attribute vec2 texCoords;\n" +
-                "varying vec2 outTexCoords;\n" +
-                "uniform mat4 projection;\n" +
-                "\nvoid main(void) {\n" +
-                "    outTexCoords = texCoords;\n" +
-                "    gl_Position = projection * position;\n" +
-                "}\n\n";
-        private static final String sSimpleFS =
-                "precision mediump float;\n\n" +
-                "varying vec2 outTexCoords;\n" +
-                "uniform sampler2D texture;\n" +
-                "\nvoid main(void) {\n" +
-                "    gl_FragColor = texture2D(texture, outTexCoords);\n" +
-                "}\n\n";
-
-        private static final int FLOAT_SIZE_BYTES = 4;
-        private static final int TRIANGLE_VERTICES_DATA_STRIDE_BYTES = 5 * FLOAT_SIZE_BYTES;
-        private static final int TRIANGLE_VERTICES_DATA_POS_OFFSET = 0;
-        private static final int TRIANGLE_VERTICES_DATA_UV_OFFSET = 3;
-
         private int mRotationAtLastSurfaceSizeUpdate = -1;
         private int mDisplayWidthAtLastSurfaceSizeUpdate = -1;
         private int mDisplayHeightAtLastSurfaceSizeUpdate = -1;
@@ -154,13 +94,14 @@
         private AsyncTask<Void, Void, Bitmap> mLoader;
         private boolean mNeedsDrawAfterLoadingWallpaper;
         private boolean mSurfaceValid;
+        private boolean mSurfaceRedrawNeeded;
 
-        public DrawableEngine() {
+        DrawableEngine() {
             super();
             setFixedSizeAllowed(true);
         }
 
-        public void trimMemory(int level) {
+        void trimMemory(int level) {
             if (level >= ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW
                     && level <= ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL
                     && mBackground != null) {
@@ -179,6 +120,7 @@
 
             super.onCreate(surfaceHolder);
 
+            //noinspection ConstantConditions
             mDefaultDisplay = getSystemService(WindowManager.class).getDefaultDisplay();
             setOffsetNotificationsEnabled(false);
 
@@ -199,7 +141,7 @@
             // Load background image dimensions, if we haven't saved them yet
             if (mBackgroundWidth <= 0 || mBackgroundHeight <= 0) {
                 // Need to load the image to get dimensions
-                loadWallpaper(forDraw, false /* needsReset */);
+                loadWallpaper(forDraw);
                 if (DEBUG) {
                     Log.d(TAG, "Reloading, redoing updateSurfaceSize later.");
                 }
@@ -210,16 +152,13 @@
             int surfaceWidth = Math.max(displayInfo.logicalWidth, mBackgroundWidth);
             int surfaceHeight = Math.max(displayInfo.logicalHeight, mBackgroundHeight);
 
-            if (FIXED_SIZED_SURFACE) {
-                // Used a fixed size surface, because we are special.  We can do
-                // this because we know the current design of window animations doesn't
-                // cause this to break.
-                surfaceHolder.setFixedSize(surfaceWidth, surfaceHeight);
-                mLastRequestedWidth = surfaceWidth;
-                mLastRequestedHeight = surfaceHeight;
-            } else {
-                surfaceHolder.setSizeFromLayout();
-            }
+            // Used a fixed size surface, because we are special.  We can do
+            // this because we know the current design of window animations doesn't
+            // cause this to break.
+            surfaceHolder.setFixedSize(surfaceWidth, surfaceHeight);
+            mLastRequestedWidth = surfaceWidth;
+            mLastRequestedHeight = surfaceHeight;
+
             return hasWallpaper;
         }
 
@@ -300,6 +239,13 @@
                 Log.d(TAG, "onSurfaceRedrawNeeded");
             }
             super.onSurfaceRedrawNeeded(holder);
+            // At the end of this method we should have drawn into the surface.
+            // This means that the bitmap should be loaded synchronously if
+            // it was already unloaded.
+            if (mBackground == null) {
+                updateBitmap(mWallpaperManager.getBitmap(true /* hardware */));
+            }
+            mSurfaceRedrawNeeded = true;
             drawFrame();
         }
 
@@ -336,7 +282,8 @@
                 boolean surfaceDimensionsChanged = dw != mLastSurfaceWidth
                         || dh != mLastSurfaceHeight;
 
-                boolean redrawNeeded = surfaceDimensionsChanged || newRotation != mLastRotation;
+                boolean redrawNeeded = surfaceDimensionsChanged || newRotation != mLastRotation
+                        || mSurfaceRedrawNeeded;
                 if (!redrawNeeded && !mOffsetsChanged) {
                     if (DEBUG) {
                         Log.d(TAG, "Suppressed drawFrame since redraw is not needed "
@@ -345,40 +292,24 @@
                     return;
                 }
                 mLastRotation = newRotation;
+                mSurfaceRedrawNeeded = false;
 
                 // Load bitmap if it is not yet loaded
                 if (mBackground == null) {
-                    if (DEBUG) {
-                        Log.d(TAG, "Reloading bitmap: mBackground, bgw, bgh, dw, dh = " +
-                                mBackground + ", " +
-                                ((mBackground == null) ? 0 : mBackground.getWidth()) + ", " +
-                                ((mBackground == null) ? 0 : mBackground.getHeight()) + ", " +
-                                dw + ", " + dh);
-                    }
-                    loadWallpaper(true /* needDraw */, true /* needReset */);
+                    loadWallpaper(true);
                     if (DEBUG) {
                         Log.d(TAG, "Reloading, resuming draw later");
                     }
                     return;
                 }
 
-                // Center the scaled image
+                // Left align the scaled image
                 mScale = Math.max(1f, Math.max(dw / (float) mBackground.getWidth(),
                         dh / (float) mBackground.getHeight()));
-                final int availw = dw - (int) (mBackground.getWidth() * mScale);
-                final int availh = dh - (int) (mBackground.getHeight() * mScale);
-                int xPixels = availw / 2;
-                int yPixels = availh / 2;
-
-                // Adjust the image for xOffset/yOffset values. If window manager is handling offsets,
-                // mXOffset and mYOffset are set to 0.5f by default and therefore xPixels and yPixels
-                // will remain unchanged
-                final int availwUnscaled = dw - mBackground.getWidth();
-                final int availhUnscaled = dh - mBackground.getHeight();
-                if (availwUnscaled < 0)
-                    xPixels += (int) (availwUnscaled * (mXOffset - .5f) + .5f);
-                if (availhUnscaled < 0)
-                    yPixels += (int) (availhUnscaled * (mYOffset - .5f) + .5f);
+                final int availw = (int) (mBackground.getWidth() * mScale) - dw;
+                final int availh = (int) (mBackground.getHeight() * mScale) - dh;
+                int xPixels = (int) (availw * mXOffset);
+                int yPixels = (int) (availh * mYOffset);
 
                 mOffsetsChanged = false;
                 if (surfaceDimensionsChanged) {
@@ -399,21 +330,7 @@
                     Log.d(TAG, "Redrawing wallpaper");
                 }
 
-                if (mIsHwAccelerated) {
-                    if (!drawWallpaperWithOpenGL(sh, availw, availh, xPixels, yPixels)) {
-                        drawWallpaperWithCanvas(sh, availw, availh, xPixels, yPixels);
-                    }
-                } else {
-                    drawWallpaperWithCanvas(sh, availw, availh, xPixels, yPixels);
-                    if (FIXED_SIZED_SURFACE) {
-                        // If the surface is fixed-size, we should only need to
-                        // draw it once and then we'll let the window manager
-                        // position it appropriately.  As such, we no longer needed
-                        // the loaded bitmap.  Yay!
-                        // hw-accelerated renderer retains bitmap for faster rotation
-                        unloadWallpaper(false /* forgetSize */);
-                    }
-                }
+                drawWallpaperWithCanvas(sh, availw, availh, xPixels, yPixels);
             } finally {
                 Trace.traceEnd(Trace.TRACE_TAG_VIEW);
             }
@@ -428,28 +345,20 @@
          *
          * If {@param needsReset} is set also clears the cache in WallpaperManager first.
          */
-        private void loadWallpaper(boolean needsDraw, boolean needsReset) {
+        private void loadWallpaper(boolean needsDraw) {
             mNeedsDrawAfterLoadingWallpaper |= needsDraw;
             if (mLoader != null) {
-                if (needsReset) {
-                    mLoader.cancel(false /* interrupt */);
-                    mLoader = null;
-                } else {
-                    if (DEBUG) {
-                        Log.d(TAG, "Skipping loadWallpaper, already in flight ");
-                    }
-                    return;
+                if (DEBUG) {
+                    Log.d(TAG, "Skipping loadWallpaper, already in flight ");
                 }
+                return;
             }
             mLoader = new AsyncTask<Void, Void, Bitmap>() {
                 @Override
                 protected Bitmap doInBackground(Void... params) {
                     Throwable exception;
                     try {
-                        if (needsReset) {
-                            mWallpaperManager.forgetLoadedWallpaper();
-                        }
-                        return mWallpaperManager.getBitmap();
+                        return mWallpaperManager.getBitmap(true /* hardware */);
                     } catch (RuntimeException | OutOfMemoryError e) {
                         exception = e;
                     }
@@ -458,48 +367,33 @@
                         return null;
                     }
 
-                    if (exception != null) {
-                        // Note that if we do fail at this, and the default wallpaper can't
-                        // be loaded, we will go into a cycle.  Don't do a build where the
-                        // default wallpaper can't be loaded.
-                        Log.w(TAG, "Unable to load wallpaper!", exception);
-                        try {
-                            mWallpaperManager.clear();
-                        } catch (IOException ex) {
-                            // now we're really screwed.
-                            Log.w(TAG, "Unable reset to default wallpaper!", ex);
-                        }
+                    // Note that if we do fail at this, and the default wallpaper can't
+                    // be loaded, we will go into a cycle.  Don't do a build where the
+                    // default wallpaper can't be loaded.
+                    Log.w(TAG, "Unable to load wallpaper!", exception);
+                    try {
+                        mWallpaperManager.clear();
+                    } catch (IOException ex) {
+                        // now we're really screwed.
+                        Log.w(TAG, "Unable reset to default wallpaper!", ex);
+                    }
 
-                        if (isCancelled()) {
-                            return null;
-                        }
+                    if (isCancelled()) {
+                        return null;
+                    }
 
-                        try {
-                            return mWallpaperManager.getBitmap();
-                        } catch (RuntimeException | OutOfMemoryError e) {
-                            Log.w(TAG, "Unable to load default wallpaper!", e);
-                        }
+                    try {
+                        return mWallpaperManager.getBitmap(true /* hardware */);
+                    } catch (RuntimeException | OutOfMemoryError e) {
+                        Log.w(TAG, "Unable to load default wallpaper!", e);
                     }
                     return null;
                 }
 
                 @Override
                 protected void onPostExecute(Bitmap b) {
-                    mBackground = null;
-                    mBackgroundWidth = -1;
-                    mBackgroundHeight = -1;
+                    updateBitmap(b);
 
-                    if (b != null) {
-                        mBackground = b;
-                        mBackgroundWidth = mBackground.getWidth();
-                        mBackgroundHeight = mBackground.getHeight();
-                    }
-
-                    if (DEBUG) {
-                        Log.d(TAG, "Wallpaper loaded: " + mBackground);
-                    }
-                    updateSurfaceSize(getSurfaceHolder(), getDefaultDisplayInfo(),
-                            false /* forDraw */);
                     if (mNeedsDrawAfterLoadingWallpaper) {
                         drawFrame();
                     }
@@ -510,6 +404,24 @@
             }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
         }
 
+        private void updateBitmap(Bitmap bitmap) {
+            mBackground = null;
+            mBackgroundWidth = -1;
+            mBackgroundHeight = -1;
+
+            if (bitmap != null) {
+                mBackground = bitmap;
+                mBackgroundWidth = mBackground.getWidth();
+                mBackgroundHeight = mBackground.getHeight();
+            }
+
+            if (DEBUG) {
+                Log.d(TAG, "Wallpaper loaded: " + mBackground);
+            }
+            updateSurfaceSize(getSurfaceHolder(), getDefaultDisplayInfo(),
+                    false /* forDraw */);
+        }
+
         private void unloadWallpaper(boolean forgetSize) {
             if (mLoader != null) {
                 mLoader.cancel(false);
@@ -564,7 +476,7 @@
         }
 
         private void drawWallpaperWithCanvas(SurfaceHolder sh, int w, int h, int left, int top) {
-            Canvas c = sh.lockCanvas();
+            Canvas c = sh.lockHardwareCanvas();
             if (c != null) {
                 try {
                     if (DEBUG) {
@@ -590,278 +502,5 @@
                 }
             }
         }
-
-        private boolean drawWallpaperWithOpenGL(SurfaceHolder sh, int w, int h, int left, int top) {
-            if (!initGL(sh)) return false;
-
-            final float right = left + mBackground.getWidth() * mScale;
-            final float bottom = top + mBackground.getHeight() * mScale;
-
-            final Rect frame = sh.getSurfaceFrame();
-            final Matrix4f ortho = new Matrix4f();
-            ortho.loadOrtho(0.0f, frame.width(), frame.height(), 0.0f, -1.0f, 1.0f);
-
-            final FloatBuffer triangleVertices = createMesh(left, top, right, bottom);
-
-            final int texture = loadTexture(mBackground);
-            final int program = buildProgram(sSimpleVS, sSimpleFS);
-
-            final int attribPosition = glGetAttribLocation(program, "position");
-            final int attribTexCoords = glGetAttribLocation(program, "texCoords");
-            final int uniformTexture = glGetUniformLocation(program, "texture");
-            final int uniformProjection = glGetUniformLocation(program, "projection");
-
-            checkGlError();
-
-            glViewport(0, 0, frame.width(), frame.height());
-            glBindTexture(GL_TEXTURE_2D, texture);
-
-            glUseProgram(program);
-            glEnableVertexAttribArray(attribPosition);
-            glEnableVertexAttribArray(attribTexCoords);
-            glUniform1i(uniformTexture, 0);
-            glUniformMatrix4fv(uniformProjection, 1, false, ortho.getArray(), 0);
-
-            checkGlError();
-
-            if (w > 0 || h > 0) {
-                glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
-                glClear(GL_COLOR_BUFFER_BIT);
-            }
-
-            // drawQuad
-            triangleVertices.position(TRIANGLE_VERTICES_DATA_POS_OFFSET);
-            glVertexAttribPointer(attribPosition, 3, GL_FLOAT, false,
-                    TRIANGLE_VERTICES_DATA_STRIDE_BYTES, triangleVertices);
-
-            triangleVertices.position(TRIANGLE_VERTICES_DATA_UV_OFFSET);
-            glVertexAttribPointer(attribTexCoords, 3, GL_FLOAT, false,
-                    TRIANGLE_VERTICES_DATA_STRIDE_BYTES, triangleVertices);
-
-            glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
-
-            boolean status = mEgl.eglSwapBuffers(mEglDisplay, mEglSurface);
-            checkEglError();
-
-            finishGL(texture, program);
-
-            return status;
-        }
-
-        private FloatBuffer createMesh(int left, int top, float right, float bottom) {
-            final float[] verticesData = {
-                    // X, Y, Z, U, V
-                     left,  bottom, 0.0f, 0.0f, 1.0f,
-                     right, bottom, 0.0f, 1.0f, 1.0f,
-                     left,  top,    0.0f, 0.0f, 0.0f,
-                     right, top,    0.0f, 1.0f, 0.0f,
-            };
-
-            final int bytes = verticesData.length * FLOAT_SIZE_BYTES;
-            final FloatBuffer triangleVertices = ByteBuffer.allocateDirect(bytes).order(
-                    ByteOrder.nativeOrder()).asFloatBuffer();
-            triangleVertices.put(verticesData).position(0);
-            return triangleVertices;
-        }
-
-        private int loadTexture(Bitmap bitmap) {
-            int[] textures = new int[1];
-
-            glActiveTexture(GL_TEXTURE0);
-            glGenTextures(1, textures, 0);
-            checkGlError();
-
-            int texture = textures[0];
-            glBindTexture(GL_TEXTURE_2D, texture);
-            checkGlError();
-
-            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
-            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
-
-            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
-            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
-
-            GLUtils.texImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bitmap, GL_UNSIGNED_BYTE, 0);
-            checkGlError();
-
-            return texture;
-        }
-
-        private int buildProgram(String vertex, String fragment) {
-            int vertexShader = buildShader(vertex, GL_VERTEX_SHADER);
-            if (vertexShader == 0) return 0;
-
-            int fragmentShader = buildShader(fragment, GL_FRAGMENT_SHADER);
-            if (fragmentShader == 0) return 0;
-
-            int program = glCreateProgram();
-            glAttachShader(program, vertexShader);
-            glAttachShader(program, fragmentShader);
-            glLinkProgram(program);
-            checkGlError();
-
-            glDeleteShader(vertexShader);
-            glDeleteShader(fragmentShader);
-
-            int[] status = new int[1];
-            glGetProgramiv(program, GL_LINK_STATUS, status, 0);
-            if (status[0] != GL_TRUE) {
-                String error = glGetProgramInfoLog(program);
-                Log.d(GL_LOG_TAG, "Error while linking program:\n" + error);
-                glDeleteProgram(program);
-                return 0;
-            }
-
-            return program;
-        }
-
-        private int buildShader(String source, int type) {
-            int shader = glCreateShader(type);
-
-            glShaderSource(shader, source);
-            checkGlError();
-
-            glCompileShader(shader);
-            checkGlError();
-
-            int[] status = new int[1];
-            glGetShaderiv(shader, GL_COMPILE_STATUS, status, 0);
-            if (status[0] != GL_TRUE) {
-                String error = glGetShaderInfoLog(shader);
-                Log.d(GL_LOG_TAG, "Error while compiling shader:\n" + error);
-                glDeleteShader(shader);
-                return 0;
-            }
-
-            return shader;
-        }
-
-        private void checkEglError() {
-            int error = mEgl.eglGetError();
-            if (error != EGL_SUCCESS) {
-                Log.w(GL_LOG_TAG, "EGL error = " + GLUtils.getEGLErrorString(error));
-            }
-        }
-
-        private void checkGlError() {
-            int error = glGetError();
-            if (error != GL_NO_ERROR) {
-                Log.w(GL_LOG_TAG, "GL error = 0x" + Integer.toHexString(error), new Throwable());
-            }
-        }
-
-        private void finishGL(int texture, int program) {
-            int[] textures = new int[1];
-            textures[0] = texture;
-            glDeleteTextures(1, textures, 0);
-            glDeleteProgram(program);
-            mEgl.eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
-            mEgl.eglDestroySurface(mEglDisplay, mEglSurface);
-            mEgl.eglDestroyContext(mEglDisplay, mEglContext);
-            mEgl.eglTerminate(mEglDisplay);
-        }
-
-        private boolean initGL(SurfaceHolder surfaceHolder) {
-            mEgl = (EGL10) EGLContext.getEGL();
-
-            mEglDisplay = mEgl.eglGetDisplay(EGL_DEFAULT_DISPLAY);
-            if (mEglDisplay == EGL_NO_DISPLAY) {
-                throw new RuntimeException("eglGetDisplay failed " +
-                        GLUtils.getEGLErrorString(mEgl.eglGetError()));
-            }
-
-            int[] version = new int[2];
-            if (!mEgl.eglInitialize(mEglDisplay, version)) {
-                throw new RuntimeException("eglInitialize failed " +
-                        GLUtils.getEGLErrorString(mEgl.eglGetError()));
-            }
-
-            mEglConfig = chooseEglConfig();
-            if (mEglConfig == null) {
-                throw new RuntimeException("eglConfig not initialized");
-            }
-
-            mEglContext = createContext(mEgl, mEglDisplay, mEglConfig);
-            if (mEglContext == EGL_NO_CONTEXT) {
-                throw new RuntimeException("createContext failed " +
-                        GLUtils.getEGLErrorString(mEgl.eglGetError()));
-            }
-
-            int attribs[] = {
-                EGL_WIDTH, 1,
-                EGL_HEIGHT, 1,
-                EGL_NONE
-            };
-            EGLSurface tmpSurface = mEgl.eglCreatePbufferSurface(mEglDisplay, mEglConfig, attribs);
-            mEgl.eglMakeCurrent(mEglDisplay, tmpSurface, tmpSurface, mEglContext);
-
-            int[] maxSize = new int[1];
-            Rect frame = surfaceHolder.getSurfaceFrame();
-            glGetIntegerv(GL_MAX_TEXTURE_SIZE, maxSize, 0);
-
-            mEgl.eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
-            mEgl.eglDestroySurface(mEglDisplay, tmpSurface);
-
-            if(frame.width() > maxSize[0] || frame.height() > maxSize[0]) {
-                mEgl.eglDestroyContext(mEglDisplay, mEglContext);
-                mEgl.eglTerminate(mEglDisplay);
-                Log.e(GL_LOG_TAG, "requested  texture size " +
-                    frame.width() + "x" + frame.height() + " exceeds the support maximum of " +
-                    maxSize[0] + "x" + maxSize[0]);
-                return false;
-            }
-
-            mEglSurface = mEgl.eglCreateWindowSurface(mEglDisplay, mEglConfig, surfaceHolder, null);
-            if (mEglSurface == null || mEglSurface == EGL_NO_SURFACE) {
-                int error = mEgl.eglGetError();
-                if (error == EGL_BAD_NATIVE_WINDOW || error == EGL_BAD_ALLOC) {
-                    Log.e(GL_LOG_TAG, "createWindowSurface returned " +
-                                         GLUtils.getEGLErrorString(error) + ".");
-                    return false;
-                }
-                throw new RuntimeException("createWindowSurface failed " +
-                        GLUtils.getEGLErrorString(error));
-            }
-
-            if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
-                throw new RuntimeException("eglMakeCurrent failed " +
-                        GLUtils.getEGLErrorString(mEgl.eglGetError()));
-            }
-
-            return true;
-        }
-
-
-        EGLContext createContext(EGL10 egl, EGLDisplay eglDisplay, EGLConfig eglConfig) {
-            int[] attrib_list = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE };
-            return egl.eglCreateContext(eglDisplay, eglConfig, EGL_NO_CONTEXT, attrib_list);
-        }
-
-        private EGLConfig chooseEglConfig() {
-            int[] configsCount = new int[1];
-            EGLConfig[] configs = new EGLConfig[1];
-            int[] configSpec = getConfig();
-            if (!mEgl.eglChooseConfig(mEglDisplay, configSpec, configs, 1, configsCount)) {
-                throw new IllegalArgumentException("eglChooseConfig failed " +
-                        GLUtils.getEGLErrorString(mEgl.eglGetError()));
-            } else if (configsCount[0] > 0) {
-                return configs[0];
-            }
-            return null;
-        }
-
-        private int[] getConfig() {
-            return new int[] {
-                    EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
-                    EGL_RED_SIZE, 8,
-                    EGL_GREEN_SIZE, 8,
-                    EGL_BLUE_SIZE, 8,
-                    EGL_ALPHA_SIZE, 0,
-                    EGL_DEPTH_SIZE, 0,
-                    EGL_STENCIL_SIZE, 0,
-                    EGL_CONFIG_CAVEAT, EGL_NONE,
-                    EGL_NONE
-            };
-        }
     }
 }
diff --git a/com/android/systemui/SwipeHelper.java b/com/android/systemui/SwipeHelper.java
index 4b37715..592dda0 100644
--- a/com/android/systemui/SwipeHelper.java
+++ b/com/android/systemui/SwipeHelper.java
@@ -83,7 +83,6 @@
 
     private boolean mMenuRowIntercepting;
     private boolean mLongPressSent;
-    private LongPressListener mLongPressListener;
     private Runnable mWatchLongPress;
     private final long mLongPressTimeout;
 
@@ -115,10 +114,6 @@
         mFlingAnimationUtils = new FlingAnimationUtils(context, getMaxEscapeAnimDuration() / 1000f);
     }
 
-    public void setLongPressListener(LongPressListener listener) {
-        mLongPressListener = listener;
-    }
-
     public void setDensityScale(float densityScale) {
         mDensityScale = densityScale;
     }
@@ -257,7 +252,7 @@
         }
     }
 
-    public void removeLongPressCallback() {
+    public void cancelLongPress() {
         if (mWatchLongPress != null) {
             mHandler.removeCallbacks(mWatchLongPress);
             mWatchLongPress = null;
@@ -288,33 +283,27 @@
                     mInitialTouchPos = getPos(ev);
                     mPerpendicularInitialTouchPos = getPerpendicularPos(ev);
                     mTranslation = getTranslation(mCurrView);
-                    if (mLongPressListener != null) {
-                        if (mWatchLongPress == null) {
-                            mWatchLongPress = new Runnable() {
-                                @Override
-                                public void run() {
-                                    if (mCurrView != null && !mLongPressSent) {
-                                        mLongPressSent = true;
+                    if (mWatchLongPress == null) {
+                        mWatchLongPress = new Runnable() {
+                            @Override
+                            public void run() {
+                                if (mCurrView != null && !mLongPressSent) {
+                                    mLongPressSent = true;
+                                    mCurrView.getLocationOnScreen(mTmpPos);
+                                    final int x = (int) ev.getRawX() - mTmpPos[0];
+                                    final int y = (int) ev.getRawY() - mTmpPos[1];
+                                    if (mCurrView instanceof ExpandableNotificationRow) {
                                         mCurrView.sendAccessibilityEvent(
                                                 AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
-                                        mCurrView.getLocationOnScreen(mTmpPos);
-                                        final int x = (int) ev.getRawX() - mTmpPos[0];
-                                        final int y = (int) ev.getRawY() - mTmpPos[1];
-                                        MenuItem menuItem = null;
-                                        if (mCurrView instanceof ExpandableNotificationRow) {
-                                            menuItem = ((ExpandableNotificationRow) mCurrView)
-                                                    .getProvider().getLongpressMenuItem(mContext);
-                                        }
-                                        if (menuItem != null) {
-                                            mLongPressListener.onLongPress(mCurrView, x, y,
-                                                    menuItem);
-                                        }
+                                        ExpandableNotificationRow currRow =
+                                                (ExpandableNotificationRow) mCurrView;
+                                        currRow.doLongClickCallback(x, y);
                                     }
                                 }
-                            };
-                        }
-                        mHandler.postDelayed(mWatchLongPress, mLongPressTimeout);
+                            }
+                        };
                     }
+                    mHandler.postDelayed(mWatchLongPress, mLongPressTimeout);
                 }
                 break;
 
@@ -331,7 +320,7 @@
                         mDragging = true;
                         mInitialTouchPos = getPos(ev);
                         mTranslation = getTranslation(mCurrView);
-                        removeLongPressCallback();
+                        cancelLongPress();
                     }
                 }
                 break;
@@ -343,7 +332,7 @@
                 mCurrView = null;
                 mLongPressSent = false;
                 mMenuRowIntercepting = false;
-                removeLongPressCallback();
+                cancelLongPress();
                 if (captured) return true;
                 break;
         }
@@ -586,7 +575,7 @@
 
                 // We are not doing anything, make sure the long press callback
                 // is not still ticking like a bomb waiting to go off.
-                removeLongPressCallback();
+                cancelLongPress();
                 return false;
             }
         }
@@ -734,15 +723,4 @@
          */
         float getFalsingThresholdFactor();
     }
-
-    /**
-     * Equivalent to View.OnLongClickListener with coordinates
-     */
-    public interface LongPressListener {
-        /**
-         * Equivalent to {@link View.OnLongClickListener#onLongClick(View)} with coordinates
-         * @return whether the longpress was handled
-         */
-        boolean onLongPress(View v, int x, int y, MenuItem item);
-    }
 }
diff --git a/com/android/systemui/doze/DozeUi.java b/com/android/systemui/doze/DozeUi.java
index dc626fb..851b78c 100644
--- a/com/android/systemui/doze/DozeUi.java
+++ b/com/android/systemui/doze/DozeUi.java
@@ -84,9 +84,6 @@
             case DOZE_REQUEST_PULSE:
                 pulseWhileDozing(mMachine.getPulseReason());
                 break;
-            case DOZE_PULSE_DONE:
-                mHost.abortPulsing();
-                break;
             case INITIALIZED:
                 mHost.startDozing();
                 break;
diff --git a/com/android/systemui/globalactions/GlobalActionsComponent.java b/com/android/systemui/globalactions/GlobalActionsComponent.java
index 09a08f0..f06cda0 100644
--- a/com/android/systemui/globalactions/GlobalActionsComponent.java
+++ b/com/android/systemui/globalactions/GlobalActionsComponent.java
@@ -31,6 +31,7 @@
 
 public class GlobalActionsComponent extends SystemUI implements Callbacks, GlobalActionsManager {
 
+    private GlobalActions mPlugin;
     private Extension<GlobalActions> mExtension;
     private IStatusBarService mBarService;
 
@@ -41,10 +42,19 @@
         mExtension = Dependency.get(ExtensionController.class).newExtension(GlobalActions.class)
                 .withPlugin(GlobalActions.class)
                 .withDefault(() -> new GlobalActionsImpl(mContext))
+                .withCallback(this::onExtensionCallback)
                 .build();
+        mPlugin = mExtension.get();
         SysUiServiceProvider.getComponent(mContext, CommandQueue.class).addCallbacks(this);
     }
 
+    private void onExtensionCallback(GlobalActions newPlugin) {
+        if (mPlugin != null) {
+            mPlugin.destroy();
+        }
+        mPlugin = newPlugin;
+    }
+
     @Override
     public void handleShowShutdownUi(boolean isReboot, String reason) {
         mExtension.get().showShutdownUi(isReboot, reason);
diff --git a/com/android/systemui/globalactions/GlobalActionsDialog.java b/com/android/systemui/globalactions/GlobalActionsDialog.java
index 189badf..d82f9cd 100644
--- a/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -16,27 +16,6 @@
 
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
 
-import com.android.internal.R;
-import com.android.internal.colorextraction.ColorExtractor;
-import com.android.internal.colorextraction.ColorExtractor.GradientColors;
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.internal.util.EmergencyAffordanceManager;
-import com.android.internal.telephony.TelephonyIntents;
-import com.android.internal.telephony.TelephonyProperties;
-import com.android.internal.widget.LockPatternUtils;
-import com.android.systemui.Dependency;
-import com.android.systemui.HardwareUiLayout;
-import com.android.systemui.Interpolators;
-import com.android.systemui.colorextraction.SysuiColorExtractor;
-import com.android.systemui.plugins.GlobalActions.GlobalActionsManager;
-import com.android.systemui.statusbar.notification.NotificationUtils;
-import com.android.systemui.statusbar.phone.ScrimController;
-import com.android.systemui.volume.VolumeDialogMotion.LogAccelerateInterpolator;
-import com.android.systemui.volume.VolumeDialogMotion.LogDecelerateInterpolator;
-
-import android.animation.ValueAnimator;
-import android.animation.ValueAnimator.AnimatorUpdateListener;
 import android.app.ActivityManager;
 import android.app.Dialog;
 import android.app.WallpaperManager;
@@ -69,7 +48,6 @@
 import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.Log;
-import android.util.MathUtils;
 import android.view.ContextThemeWrapper;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -86,7 +64,23 @@
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
+import com.android.internal.R;
+import com.android.internal.colorextraction.ColorExtractor;
+import com.android.internal.colorextraction.ColorExtractor.GradientColors;
 import com.android.internal.colorextraction.drawable.GradientDrawable;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.telephony.TelephonyIntents;
+import com.android.internal.telephony.TelephonyProperties;
+import com.android.internal.util.EmergencyAffordanceManager;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.systemui.Dependency;
+import com.android.systemui.HardwareUiLayout;
+import com.android.systemui.Interpolators;
+import com.android.systemui.colorextraction.SysuiColorExtractor;
+import com.android.systemui.plugins.GlobalActions.GlobalActionsManager;
+import com.android.systemui.statusbar.phone.ScrimController;
+import com.android.systemui.volume.VolumeDialogMotion.LogAccelerateInterpolator;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -96,7 +90,8 @@
  * may show depending on whether the keyguard is showing, and whether the device
  * is provisioned.
  */
-class GlobalActionsDialog implements DialogInterface.OnDismissListener, DialogInterface.OnClickListener {
+class GlobalActionsDialog implements DialogInterface.OnDismissListener,
+        DialogInterface.OnClickListener {
 
     static public final String SYSTEM_DIALOG_REASON_KEY = "reason";
     static public final String SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS = "globalactions";
@@ -195,6 +190,14 @@
         }
     }
 
+    /**
+     * Dismiss the global actions dialog, if it's currently shown
+     */
+    public void dismissDialog() {
+        mHandler.removeMessages(MESSAGE_DISMISS);
+        mHandler.sendEmptyMessage(MESSAGE_DISMISS);
+    }
+
     private void awakenIfNecessary() {
         if (mDreamManager != null) {
             try {
diff --git a/com/android/systemui/globalactions/GlobalActionsImpl.java b/com/android/systemui/globalactions/GlobalActionsImpl.java
index 2cf230c..3563437 100644
--- a/com/android/systemui/globalactions/GlobalActionsImpl.java
+++ b/com/android/systemui/globalactions/GlobalActionsImpl.java
@@ -14,12 +14,12 @@
 
 package com.android.systemui.globalactions;
 
+import static android.app.StatusBarManager.DISABLE2_GLOBAL_ACTIONS;
+
 import android.app.Dialog;
 import android.app.KeyguardManager;
-import android.app.WallpaperColors;
 import android.app.WallpaperManager;
 import android.content.Context;
-import android.graphics.Color;
 import android.graphics.Point;
 import android.view.ViewGroup;
 import android.view.Window;
@@ -32,12 +32,14 @@
 import com.android.internal.colorextraction.drawable.GradientDrawable;
 import com.android.settingslib.Utils;
 import com.android.systemui.Dependency;
+import com.android.systemui.SysUiServiceProvider;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.plugins.GlobalActions;
+import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.KeyguardMonitor;
 
-public class GlobalActionsImpl implements GlobalActions {
+public class GlobalActionsImpl implements GlobalActions, CommandQueue.Callbacks {
 
     private static final float SHUTDOWN_SCRIM_ALPHA = 0.95f;
 
@@ -45,15 +47,23 @@
     private final KeyguardMonitor mKeyguardMonitor;
     private final DeviceProvisionedController mDeviceProvisionedController;
     private GlobalActionsDialog mGlobalActions;
+    private boolean mDisabled;
 
     public GlobalActionsImpl(Context context) {
         mContext = context;
         mKeyguardMonitor = Dependency.get(KeyguardMonitor.class);
         mDeviceProvisionedController = Dependency.get(DeviceProvisionedController.class);
+        SysUiServiceProvider.getComponent(context, CommandQueue.class).addCallbacks(this);
+    }
+
+    @Override
+    public void destroy() {
+        SysUiServiceProvider.getComponent(mContext, CommandQueue.class).removeCallbacks(this);
     }
 
     @Override
     public void showGlobalActions(GlobalActionsManager manager) {
+        if (mDisabled) return;
         if (mGlobalActions == null) {
             mGlobalActions = new GlobalActionsDialog(mContext, manager);
         }
@@ -107,4 +117,14 @@
 
         d.show();
     }
+
+    @Override
+    public void disable(int state1, int state2, boolean animate) {
+        final boolean disabled = (state2 & DISABLE2_GLOBAL_ACTIONS) != 0;
+        if (disabled == mDisabled) return;
+        mDisabled = disabled;
+        if (disabled && mGlobalActions != null) {
+            mGlobalActions.dismissDialog();
+        }
+    }
 }
diff --git a/com/android/systemui/keyguard/WorkLockActivityController.java b/com/android/systemui/keyguard/WorkLockActivityController.java
index f198229..4c3d5ba 100644
--- a/com/android/systemui/keyguard/WorkLockActivityController.java
+++ b/com/android/systemui/keyguard/WorkLockActivityController.java
@@ -16,7 +16,6 @@
 
 package com.android.systemui.keyguard;
 
-import android.app.Activity;
 import android.app.ActivityManager;
 import android.app.ActivityOptions;
 import android.app.IActivityManager;
@@ -29,9 +28,8 @@
 import android.os.UserHandle;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.recents.events.EventBus;
 import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener;
+import com.android.systemui.recents.misc.TaskStackChangeListener;
 
 public class WorkLockActivityController {
     private final Context mContext;
@@ -98,7 +96,7 @@
         }
     }
 
-    private final TaskStackListener mLockListener = new TaskStackListener() {
+    private final TaskStackChangeListener mLockListener = new TaskStackChangeListener() {
         @Override
         public void onTaskProfileLocked(int taskId, int userId) {
             startWorkChallengeInTask(taskId, userId);
diff --git a/com/android/systemui/pip/phone/InputConsumerController.java b/com/android/systemui/pip/phone/InputConsumerController.java
index abc5667..e6d6c55 100644
--- a/com/android/systemui/pip/phone/InputConsumerController.java
+++ b/com/android/systemui/pip/phone/InputConsumerController.java
@@ -28,8 +28,6 @@
 import android.view.IWindowManager;
 import android.view.MotionEvent;
 
-import com.android.systemui.recents.misc.Utilities;
-
 import java.io.PrintWriter;
 
 /**
diff --git a/com/android/systemui/pip/phone/PipManager.java b/com/android/systemui/pip/phone/PipManager.java
index f8996aa..7e87666 100644
--- a/com/android/systemui/pip/phone/PipManager.java
+++ b/com/android/systemui/pip/phone/PipManager.java
@@ -41,8 +41,7 @@
 import com.android.systemui.recents.events.EventBus;
 import com.android.systemui.recents.events.component.ExpandPipEvent;
 import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener;
-import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.recents.misc.TaskStackChangeListener;
 
 import java.io.PrintWriter;
 
@@ -70,7 +69,7 @@
     /**
      * Handler for system task stack changes.
      */
-    TaskStackListener mTaskStackListener = new TaskStackListener() {
+    TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
         @Override
         public void onActivityPinned(String packageName, int userId, int taskId, int stackId) {
             mTouchHandler.onActivityPinned();
diff --git a/com/android/systemui/pip/tv/PipManager.java b/com/android/systemui/pip/tv/PipManager.java
index e0445c1..312b990 100644
--- a/com/android/systemui/pip/tv/PipManager.java
+++ b/com/android/systemui/pip/tv/PipManager.java
@@ -20,7 +20,6 @@
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.ActivityManager.StackInfo;
 import android.app.IActivityManager;
-import android.app.RemoteAction;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
@@ -47,7 +46,7 @@
 import com.android.systemui.R;
 import com.android.systemui.pip.BasePipManager;
 import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener;
+import com.android.systemui.recents.misc.TaskStackChangeListener;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -621,7 +620,7 @@
         return false;
     }
 
-    private TaskStackListener mTaskStackListener = new TaskStackListener() {
+    private TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
         @Override
         public void onTaskStackChanged() {
             if (DEBUG) Log.d(TAG, "onTaskStackChanged()");
diff --git a/com/android/systemui/plugins/GlobalActions.java b/com/android/systemui/plugins/GlobalActions.java
index 1f633da..95ff13b 100644
--- a/com/android/systemui/plugins/GlobalActions.java
+++ b/com/android/systemui/plugins/GlobalActions.java
@@ -29,6 +29,9 @@
     default void showShutdownUi(boolean isReboot, String reason) {
     }
 
+    default void destroy() {
+    }
+
     @ProvidesInterface(version = GlobalActionsManager.VERSION)
     public interface GlobalActionsManager {
         int VERSION = 1;
diff --git a/com/android/systemui/plugins/qs/QSTile.java b/com/android/systemui/plugins/qs/QSTile.java
index b4b4e19..c52c0aa 100644
--- a/com/android/systemui/plugins/qs/QSTile.java
+++ b/com/android/systemui/plugins/qs/QSTile.java
@@ -26,6 +26,7 @@
 import com.android.systemui.plugins.qs.QSTile.State;
 
 import java.util.Objects;
+import java.util.function.Supplier;
 
 @ProvidesInterface(version = QSTile.VERSION)
 @DependsOn(target = QSIconView.class)
@@ -104,6 +105,7 @@
     public static class State {
         public static final int VERSION = 1;
         public Icon icon;
+        public Supplier<Icon> iconSupplier;
         public int state = Tile.STATE_ACTIVE;
         public CharSequence label;
         public CharSequence contentDescription;
@@ -118,6 +120,7 @@
             if (other == null) throw new IllegalArgumentException();
             if (!other.getClass().equals(getClass())) throw new IllegalArgumentException();
             final boolean changed = !Objects.equals(other.icon, icon)
+                    || !Objects.equals(other.iconSupplier, iconSupplier)
                     || !Objects.equals(other.label, label)
                     || !Objects.equals(other.contentDescription, contentDescription)
                     || !Objects.equals(other.dualLabelContentDescription,
@@ -130,6 +133,7 @@
                     || !Objects.equals(other.dualTarget, dualTarget)
                     || !Objects.equals(other.slash, slash);
             other.icon = icon;
+            other.iconSupplier = iconSupplier;
             other.label = label;
             other.contentDescription = contentDescription;
             other.dualLabelContentDescription = dualLabelContentDescription;
@@ -150,6 +154,7 @@
         protected StringBuilder toStringBuilder() {
             final StringBuilder sb = new StringBuilder(getClass().getSimpleName()).append('[');
             sb.append(",icon=").append(icon);
+            sb.append(",iconSupplier=").append(iconSupplier);
             sb.append(",label=").append(label);
             sb.append(",contentDescription=").append(contentDescription);
             sb.append(",dualLabelContentDescription=").append(dualLabelContentDescription);
diff --git a/com/android/systemui/power/PowerUI.java b/com/android/systemui/power/PowerUI.java
index f378268..c1a3623 100644
--- a/com/android/systemui/power/PowerUI.java
+++ b/com/android/systemui/power/PowerUI.java
@@ -28,8 +28,14 @@
 import android.os.BatteryManager;
 import android.os.Handler;
 import android.os.HardwarePropertiesManager;
+import android.os.IBinder;
+import android.os.IThermalEventListener;
+import android.os.IThermalService;
 import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.os.SystemClock;
+import android.os.Temperature;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.text.format.DateUtils;
@@ -75,6 +81,7 @@
     private float[] mRecentTemps = new float[MAX_RECENT_TEMPS];
     private int mNumTemps;
     private long mNextLogTime;
+    private IThermalService mThermalService;
 
     // We create a method reference here so that we are guaranteed that we can remove a callback
     // by using the same instance (method references are not guaranteed to be the same object
@@ -263,7 +270,7 @@
                 resources.getInteger(R.integer.config_warningTemperature));
 
         if (mThresholdTemp < 0f) {
-            // Get the throttling temperature. No need to check if we're not throttling.
+            // Get the shutdown temperature, adjust for warning tolerance.
             float[] throttlingTemps = mHardwarePropertiesManager.getDeviceTemperatures(
                     HardwarePropertiesManager.DEVICE_TEMPERATURE_SKIN,
                     HardwarePropertiesManager.TEMPERATURE_SHUTDOWN);
@@ -276,6 +283,25 @@
                     resources.getInteger(R.integer.config_warningTemperatureTolerance);
         }
 
+        if (mThermalService == null) {
+            // Enable push notifications of throttling from vendor thermal
+            // management subsystem via thermalservice, in addition to our
+            // usual polling, to react to temperature jumps more quickly.
+            IBinder b = ServiceManager.getService("thermalservice");
+
+            if (b != null) {
+                mThermalService = IThermalService.Stub.asInterface(b);
+                try {
+                    mThermalService.registerThermalEventListener(
+                        new ThermalEventListener());
+                } catch (RemoteException e) {
+                    // Should never happen.
+                }
+            } else {
+                Slog.w(TAG, "cannot find thermalservice, no throttling push notifications");
+            }
+        }
+
         setNextLogTime();
 
         // This initialization method may be called on a configuration change. Only one set of
@@ -414,5 +440,15 @@
         void dump(PrintWriter pw);
         void userSwitched();
     }
-}
 
+    // Thermal event received from vendor thermal management subsystem
+    private final class ThermalEventListener extends IThermalEventListener.Stub {
+        @Override public void notifyThrottling(boolean isThrottling, Temperature temp) {
+            // Trigger an update of the temperature warning.  Only one
+            // callback can be enabled at a time, so remove any existing
+            // callback; updateTemperatureWarning will schedule another one.
+            mHandler.removeCallbacks(mUpdateTempCallback);
+            updateTemperatureWarning();
+        }
+    }
+}
diff --git a/com/android/systemui/qs/QSDetailItems.java b/com/android/systemui/qs/QSDetailItems.java
index b4cc4b1..8869e8d 100644
--- a/com/android/systemui/qs/QSDetailItems.java
+++ b/com/android/systemui/qs/QSDetailItems.java
@@ -34,6 +34,7 @@
 import android.widget.TextView;
 import com.android.systemui.FontSizeUtils;
 import com.android.systemui.R;
+import com.android.systemui.plugins.qs.QSTile;
 
 /**
  * Quick settings common detail view with line items.
@@ -185,7 +186,7 @@
             view.setVisibility(mItemsVisible ? VISIBLE : INVISIBLE);
             final ImageView iv = (ImageView) view.findViewById(android.R.id.icon);
             if (item.iconDrawable != null) {
-                iv.setImageDrawable(item.iconDrawable);
+                iv.setImageDrawable(item.iconDrawable.getDrawable(iv.getContext()));
             } else {
                 iv.setImageResource(item.icon);
             }
@@ -258,7 +259,7 @@
 
     public static class Item {
         public int icon;
-        public Drawable iconDrawable;
+        public QSTile.Icon iconDrawable;
         public Drawable overlay;
         public CharSequence line1;
         public CharSequence line2;
diff --git a/com/android/systemui/qs/external/CustomTile.java b/com/android/systemui/qs/external/CustomTile.java
index 176112b..b3244a5 100644
--- a/com/android/systemui/qs/external/CustomTile.java
+++ b/com/android/systemui/qs/external/CustomTile.java
@@ -305,7 +305,15 @@
             state.state = Tile.STATE_UNAVAILABLE;
             drawable = mDefaultIcon.loadDrawable(mContext);
         }
-        state.icon = new DrawableIcon(drawable);
+
+        final Drawable drawableF = drawable;
+        state.iconSupplier = () -> {
+            Drawable.ConstantState cs = drawableF.getConstantState();
+            if (cs != null) {
+                return new DrawableIcon(cs.newDrawable());
+            }
+            return null;
+        };
         state.label = mTile.getLabel();
         if (mTile.getContentDescription() != null) {
             state.contentDescription = mTile.getContentDescription();
diff --git a/com/android/systemui/qs/tileimpl/QSIconViewImpl.java b/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
index e8c8b90..c249e37 100644
--- a/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
+++ b/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
@@ -87,14 +87,15 @@
     }
 
     protected void updateIcon(ImageView iv, State state) {
-        if (!Objects.equals(state.icon, iv.getTag(R.id.qs_icon_tag))
+        final QSTile.Icon icon = state.iconSupplier != null ? state.iconSupplier.get() : state.icon;
+        if (!Objects.equals(icon, iv.getTag(R.id.qs_icon_tag))
                 || !Objects.equals(state.slash, iv.getTag(R.id.qs_slash_tag))) {
             boolean shouldAnimate = iv.isShown() && mAnimationEnabled
                     && iv.getDrawable() != null;
-            Drawable d = state.icon != null
-                    ? shouldAnimate ? state.icon.getDrawable(mContext)
-                    : state.icon.getInvisibleDrawable(mContext) : null;
-            int padding = state.icon != null ? state.icon.getPadding() : 0;
+            Drawable d = icon != null
+                    ? shouldAnimate ? icon.getDrawable(mContext)
+                    : icon.getInvisibleDrawable(mContext) : null;
+            int padding = icon != null ? icon.getPadding() : 0;
             if (d != null) {
                 d.setAutoMirrored(false);
                 d.setLayoutDirection(getLayoutDirection());
@@ -107,7 +108,7 @@
                 iv.setImageDrawable(d);
             }
 
-            iv.setTag(R.id.qs_icon_tag, state.icon);
+            iv.setTag(R.id.qs_icon_tag, icon);
             iv.setTag(R.id.qs_slash_tag, state.slash);
             iv.setPadding(0, padding, 0, padding);
             if (d instanceof Animatable2) {
diff --git a/com/android/systemui/qs/tiles/BluetoothTile.java b/com/android/systemui/qs/tiles/BluetoothTile.java
index 81b8622..bc3ccb4 100644
--- a/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -134,7 +134,9 @@
                 if (lastDevice != null) {
                     int batteryLevel = lastDevice.getBatteryLevel();
                     if (batteryLevel != BluetoothDevice.BATTERY_LEVEL_UNKNOWN) {
-                        state.icon = new BluetoothBatteryDrawable(batteryLevel);
+                        state.icon = new BluetoothBatteryDrawable(batteryLevel,
+                                mContext.getResources().getFraction(
+                                        R.fraction.bt_battery_scale_fraction, 1, 1));
                     }
                 }
                 state.contentDescription = mContext.getString(
@@ -212,17 +214,21 @@
 
     private class BluetoothBatteryDrawable extends Icon {
         private int mLevel;
+        private float mIconScale;
 
         BluetoothBatteryDrawable(int level) {
+            this(level, 1 /* iconScale */);
+        }
+
+        BluetoothBatteryDrawable(int level, float iconScale) {
             mLevel = level;
+            mIconScale = iconScale;
         }
 
         @Override
         public Drawable getDrawable(Context context) {
             return createLayerDrawable(context,
-                    R.drawable.ic_qs_bluetooth_connected, mLevel,
-                    context.getResources().getFraction(
-                            R.fraction.bt_battery_scale_fraction, 1, 1));
+                    R.drawable.ic_qs_bluetooth_connected, mLevel, mIconScale);
         }
     }
 
@@ -304,8 +310,7 @@
                         item.icon = R.drawable.ic_qs_bluetooth_connected;
                         int batteryLevel = device.getBatteryLevel();
                         if (batteryLevel != BluetoothDevice.BATTERY_LEVEL_UNKNOWN) {
-                            item.iconDrawable = createLayerDrawable(mContext, item.icon,
-                                    batteryLevel);
+                            item.iconDrawable = new BluetoothBatteryDrawable(batteryLevel);
                             item.line2 = mContext.getString(
                                     R.string.quick_settings_connected_battery_level,
                                     Utils.formatPercentage(batteryLevel));
diff --git a/com/android/systemui/recents/Recents.java b/com/android/systemui/recents/Recents.java
index 283ac0c..ce1438a 100644
--- a/com/android/systemui/recents/Recents.java
+++ b/com/android/systemui/recents/Recents.java
@@ -29,14 +29,13 @@
 import android.content.ServiceConnection;
 import android.content.pm.ActivityInfo;
 import android.content.res.Configuration;
+import android.content.res.Resources;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.hardware.display.DisplayManager;
-import android.os.Build;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteException;
-import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.util.EventLog;
@@ -53,6 +52,7 @@
 import com.android.systemui.SystemUI;
 import com.android.systemui.recents.events.EventBus;
 import com.android.systemui.recents.events.activity.ConfigurationChangedEvent;
+import com.android.systemui.recents.events.activity.DockedFirstAnimationFrameEvent;
 import com.android.systemui.recents.events.activity.DockedTopTaskEvent;
 import com.android.systemui.recents.events.activity.LaunchTaskFailedEvent;
 import com.android.systemui.recents.events.activity.RecentsActivityStartingEvent;
@@ -62,7 +62,7 @@
 import com.android.systemui.recents.events.component.ShowUserToastEvent;
 import com.android.systemui.recents.events.ui.RecentsDrawnEvent;
 import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.model.RecentsTaskLoader;
+import com.android.systemui.shared.recents.model.RecentsTaskLoader;
 import com.android.systemui.stackdivider.Divider;
 import com.android.systemui.statusbar.CommandQueue;
 
@@ -81,23 +81,15 @@
         implements RecentsComponent, CommandQueue.Callbacks {
 
     private final static String TAG = "Recents";
-    private final static boolean DEBUG = false;
 
     public final static int EVENT_BUS_PRIORITY = 1;
     public final static int BIND_TO_SYSTEM_USER_RETRY_DELAY = 5000;
-    public final static int RECENTS_GROW_TARGET_INVALID = -1;
 
     public final static Set<String> RECENTS_ACTIVITIES = new HashSet<>();
     static {
         RECENTS_ACTIVITIES.add(RecentsImpl.RECENTS_ACTIVITY);
     }
 
-    // Purely for experimentation
-    private final static String RECENTS_OVERRIDE_SYSPROP_KEY = "persist.recents_override_pkg";
-    private final static String ACTION_SHOW_RECENTS = "com.android.systemui.recents.ACTION_SHOW";
-    private final static String ACTION_HIDE_RECENTS = "com.android.systemui.recents.ACTION_HIDE";
-    private final static String ACTION_TOGGLE_RECENTS = "com.android.systemui.recents.ACTION_TOGGLE";
-
     private static final String COUNTER_WINDOW_SUPPORTED = "window_enter_supported";
     private static final String COUNTER_WINDOW_UNSUPPORTED = "window_enter_unsupported";
     private static final String COUNTER_WINDOW_INCOMPATIBLE = "window_enter_incompatible";
@@ -107,11 +99,6 @@
     private static RecentsTaskLoader sTaskLoader;
     private static RecentsConfiguration sConfiguration;
 
-    // For experiments only, allows another package to handle recents if it is defined in the system
-    // properties.  This is limited to show/toggle/hide, and does not tie into the ActivityManager,
-    // and does not reside in the home stack.
-    private String mOverrideRecentsPackageName;
-
     private Handler mHandler;
     private RecentsImpl mImpl;
     private int mDraggingInRecentsCurrentUser;
@@ -204,21 +191,23 @@
 
     @Override
     public void start() {
-        sDebugFlags = new RecentsDebugFlags(mContext);
+        final Resources res = mContext.getResources();
+        final int defaultTaskBarBackgroundColor =
+                mContext.getColor(R.color.recents_task_bar_default_background_color);
+        final int defaultTaskViewBackgroundColor =
+                mContext.getColor(R.color.recents_task_view_default_background_color);
+        sDebugFlags = new RecentsDebugFlags();
         sSystemServicesProxy = SystemServicesProxy.getInstance(mContext);
         sConfiguration = new RecentsConfiguration(mContext);
-        sTaskLoader = new RecentsTaskLoader(mContext);
+        sTaskLoader = new RecentsTaskLoader(mContext,
+                // TODO: Once we start building the AAR, move these into the loader
+                res.getInteger(R.integer.config_recents_max_thumbnail_count),
+                res.getInteger(R.integer.config_recents_max_icon_count),
+                res.getInteger(R.integer.recents_svelte_level));
+        sTaskLoader.setDefaultColors(defaultTaskBarBackgroundColor, defaultTaskViewBackgroundColor);
         mHandler = new Handler();
         mImpl = new RecentsImpl(mContext);
 
-        // Check if there is a recents override package
-        if (Build.IS_USERDEBUG || Build.IS_ENG) {
-            String cnStr = SystemProperties.get(RECENTS_OVERRIDE_SYSPROP_KEY);
-            if (!cnStr.isEmpty()) {
-                mOverrideRecentsPackageName = cnStr;
-            }
-        }
-
         // Register with the event bus
         EventBus.getDefault().register(this, EVENT_BUS_PRIORITY);
         EventBus.getDefault().register(sSystemServicesProxy, EVENT_BUS_PRIORITY);
@@ -257,16 +246,8 @@
             return;
         }
 
-        if (proxyToOverridePackage(ACTION_SHOW_RECENTS)) {
-            return;
-        }
-        try {
-            ActivityManager.getService().closeSystemDialogs(SYSTEM_DIALOG_REASON_RECENT_APPS);
-        } catch (RemoteException e) {
-        }
-
+        sSystemServicesProxy.sendCloseSystemWindows(SYSTEM_DIALOG_REASON_RECENT_APPS);
         int recentsGrowTarget = getComponent(Divider.class).getView().growsRecents();
-
         int currentUser = sSystemServicesProxy.getCurrentUser();
         if (sSystemServicesProxy.isSystemUser(currentUser)) {
             mImpl.showRecents(triggeredFromAltTab, false /* draggingInRecents */,
@@ -301,10 +282,6 @@
             return;
         }
 
-        if (proxyToOverridePackage(ACTION_HIDE_RECENTS)) {
-            return;
-        }
-
         int currentUser = sSystemServicesProxy.getCurrentUser();
         if (sSystemServicesProxy.isSystemUser(currentUser)) {
             mImpl.hideRecents(triggeredFromAltTab, triggeredFromHomeKey);
@@ -336,12 +313,7 @@
             return;
         }
 
-        if (proxyToOverridePackage(ACTION_TOGGLE_RECENTS)) {
-            return;
-        }
-
         int growTarget = getComponent(Divider.class).getView().growsRecents();
-
         int currentUser = sSystemServicesProxy.getCurrentUser();
         if (sSystemServicesProxy.isSystemUser(currentUser)) {
             mImpl.toggleRecents(growTarget);
@@ -634,6 +606,23 @@
         }
     }
 
+    public final void onBusEvent(DockedFirstAnimationFrameEvent event) {
+        SystemServicesProxy ssp = Recents.getSystemServices();
+        int processUser = ssp.getProcessUser();
+        if (!ssp.isSystemUser(processUser)) {
+            postToSystemUser(new Runnable() {
+                @Override
+                public void run() {
+                    try {
+                        mUserToSystemCallbacks.sendDockedFirstAnimationFrameEvent();
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "Callback failed", e);
+                    }
+                }
+            });
+        }
+    }
+
     /**
      * Handle screen pinning request.
      */
@@ -820,21 +809,6 @@
                 (Settings.Secure.getInt(cr, Settings.Secure.USER_SETUP_COMPLETE, 0) != 0);
     }
 
-    /**
-     * Attempts to proxy the following action to the override recents package.
-     * @return whether the proxying was successful
-     */
-    private boolean proxyToOverridePackage(String action) {
-        if (mOverrideRecentsPackageName != null) {
-            Intent intent = new Intent(action);
-            intent.setPackage(mOverrideRecentsPackageName);
-            intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
-            mContext.sendBroadcast(intent);
-            return true;
-        }
-        return false;
-    }
-
     @Override
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.println("Recents");
diff --git a/com/android/systemui/recents/RecentsActivity.java b/com/android/systemui/recents/RecentsActivity.java
index 86b7790..b75a142 100644
--- a/com/android/systemui/recents/RecentsActivity.java
+++ b/com/android/systemui/recents/RecentsActivity.java
@@ -17,7 +17,6 @@
 package com.android.systemui.recents;
 
 import android.app.Activity;
-import android.app.ActivityManager;
 import android.app.ActivityOptions;
 import android.app.TaskStackBuilder;
 import android.app.WallpaperManager;
@@ -29,10 +28,10 @@
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.Looper;
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.provider.Settings;
-import android.provider.Settings.Secure;
 import android.util.Log;
 import android.view.KeyEvent;
 import android.view.View;
@@ -42,6 +41,7 @@
 import android.view.WindowManager.LayoutParams;
 
 import com.android.internal.colorextraction.ColorExtractor;
+import com.android.internal.content.PackageMonitor;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.keyguard.LatencyTracker;
@@ -53,7 +53,6 @@
 import com.android.systemui.recents.events.EventBus;
 import com.android.systemui.recents.events.activity.CancelEnterRecentsWindowAnimationEvent;
 import com.android.systemui.recents.events.activity.ConfigurationChangedEvent;
-import com.android.systemui.recents.events.activity.DebugFlagsChangedEvent;
 import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimationStarted;
 import com.android.systemui.recents.events.activity.DockedFirstAnimationFrameEvent;
 import com.android.systemui.recents.events.activity.DockedTopTaskEvent;
@@ -61,10 +60,10 @@
 import com.android.systemui.recents.events.activity.EnterRecentsWindowLastAnimationFrameEvent;
 import com.android.systemui.recents.events.activity.ExitRecentsWindowFirstAnimationFrameEvent;
 import com.android.systemui.recents.events.activity.HideRecentsEvent;
-import com.android.systemui.recents.events.activity.IterateRecentsEvent;
 import com.android.systemui.recents.events.activity.LaunchTaskFailedEvent;
 import com.android.systemui.recents.events.activity.LaunchTaskSucceededEvent;
 import com.android.systemui.recents.events.activity.MultiWindowStateChangedEvent;
+import com.android.systemui.recents.events.activity.PackagesChangedEvent;
 import com.android.systemui.recents.events.activity.RecentsActivityStartingEvent;
 import com.android.systemui.recents.events.activity.ToggleRecentsEvent;
 import com.android.systemui.recents.events.component.ActivityUnpinnedEvent;
@@ -79,28 +78,24 @@
 import com.android.systemui.recents.events.ui.ShowIncompatibleAppOverlayEvent;
 import com.android.systemui.recents.events.ui.StackViewScrolledEvent;
 import com.android.systemui.recents.events.ui.TaskViewDismissedEvent;
-import com.android.systemui.recents.events.ui.UpdateFreeformTaskViewVisibilityEvent;
 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.events.ui.focus.NavigateTaskViewEvent;
 import com.android.systemui.recents.events.ui.focus.NavigateTaskViewEvent.Direction;
-import com.android.systemui.recents.misc.DozeTrigger;
 import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.Utilities;
-import com.android.systemui.recents.model.RecentsPackageMonitor;
-import com.android.systemui.recents.model.RecentsTaskLoadPlan;
-import com.android.systemui.recents.model.RecentsTaskLoader;
-import com.android.systemui.recents.model.Task;
-import com.android.systemui.recents.model.TaskStack;
+import com.android.systemui.shared.recents.utilities.Utilities;
+import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan;
+import com.android.systemui.shared.recents.model.RecentsTaskLoader;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.model.TaskStack;
 import com.android.systemui.recents.views.RecentsView;
 import com.android.systemui.recents.views.SystemBarScrimViews;
 import com.android.systemui.statusbar.phone.StatusBar;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
-import java.util.List;
 
 /**
  * The main Recents activity that is started from RecentsComponent.
@@ -114,7 +109,23 @@
     public final static int EVENT_BUS_PRIORITY = Recents.EVENT_BUS_PRIORITY + 1;
     public final static int INCOMPATIBLE_APP_ALPHA_DURATION = 150;
 
-    private RecentsPackageMonitor mPackageMonitor;
+    private PackageMonitor mPackageMonitor = new PackageMonitor() {
+            @Override
+            public void onPackageRemoved(String packageName, int uid) {
+                RecentsActivity.this.onPackageChanged(packageName, getChangingUserId());
+            }
+
+            @Override
+            public boolean onPackageChanged(String packageName, int uid, String[] components) {
+                RecentsActivity.this.onPackageChanged(packageName, getChangingUserId());
+                return true;
+            }
+
+            @Override
+            public void onPackageModified(String packageName) {
+                RecentsActivity.this.onPackageChanged(packageName, getChangingUserId());
+            }
+        };
     private Handler mHandler = new Handler();
     private long mLastTabKeyEventTime;
     private boolean mFinishedOnStartup;
@@ -133,7 +144,6 @@
 
     // The trigger to automatically launch the current task
     private int mFocusTimerDuration;
-    private DozeTrigger mIterateTrigger;
     private final UserInteractionEvent mUserInteractionEvent = new UserInteractionEvent();
 
     // Theme and colors
@@ -188,41 +198,6 @@
             } else if (action.equals(Intent.ACTION_USER_SWITCHED)) {
                 // When switching users, dismiss Recents to Home similar to screen off
                 finish();
-            } else if (action.equals(Intent.ACTION_TIME_CHANGED)) {
-                // If the time shifts but the currentTime >= lastStackActiveTime, then that boundary
-                // is still valid.  Otherwise, we need to reset the lastStackactiveTime to the
-                // currentTime and remove the old tasks in between which would not be previously
-                // visible, but currently would be in the new currentTime
-                int currentUser = SystemServicesProxy.getInstance(RecentsActivity.this)
-                        .getCurrentUser();
-                long oldLastStackActiveTime = Settings.Secure.getLongForUser(getContentResolver(),
-                        Secure.OVERVIEW_LAST_STACK_ACTIVE_TIME, -1, currentUser);
-                if (oldLastStackActiveTime != -1) {
-                    long currentTime = System.currentTimeMillis();
-                    if (currentTime < oldLastStackActiveTime) {
-                        // We are only removing tasks that are between the new current time
-                        // and the old last stack active time, they were not visible and in the
-                        // TaskStack so we don't need to remove any associated TaskViews but we do
-                        // need to load the task id's from the system
-                        RecentsTaskLoader loader = Recents.getTaskLoader();
-                        RecentsTaskLoadPlan loadPlan = loader.createLoadPlan(ctx);
-                        loader.preloadRawTasks(loadPlan, false /* includeFrontMostExcludedTask */);
-                        List<ActivityManager.RecentTaskInfo> tasks = loadPlan.getRawTasks();
-                        for (int i = tasks.size() - 1; i >= 0; i--) {
-                            ActivityManager.RecentTaskInfo task = tasks.get(i);
-                            if (currentTime <= task.lastActiveTime && task.lastActiveTime <
-                                    oldLastStackActiveTime) {
-                                Recents.getSystemServices().removeTask(task.persistentId);
-                            }
-                        }
-                        Recents.getSystemServices().updateOverviewLastStackActiveTimeAsync(
-                                currentTime, currentUser);
-
-                        // Clear the last PiP task time, it's an edge case and we'd rather it
-                        // not relaunch the PiP task if the user double taps
-                        RecentsImpl.clearLastPipTime();
-                    }
-                }
             }
         }
     };
@@ -340,8 +315,8 @@
         EventBus.getDefault().register(this, EVENT_BUS_PRIORITY);
 
         // Initialize the package monitor
-        mPackageMonitor = new RecentsPackageMonitor();
-        mPackageMonitor.register(this);
+        mPackageMonitor.register(this, Looper.getMainLooper(), UserHandle.ALL,
+                true /* externalStorage */);
 
         // Select theme based on wallpaper colors
         mColorExtractor = Dependency.get(SysuiColorExtractor.class);
@@ -363,13 +338,6 @@
         }
 
         mLastConfig = new Configuration(Utilities.getAppConfiguration(this));
-        mFocusTimerDuration = getResources().getInteger(R.integer.recents_auto_advance_duration);
-        mIterateTrigger = new DozeTrigger(mFocusTimerDuration, new Runnable() {
-            @Override
-            public void run() {
-                dismissRecentsToFocusedTask(MetricsEvent.OVERVIEW_SELECT_TIMEOUT);
-            }
-        });
 
         // Set the window background
         mRecentsView.updateBackgroundScrim(getWindow(), isInMultiWindowMode());
@@ -383,7 +351,6 @@
         // Register the broadcast receiver to handle messages when the screen is turned off
         IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_SCREEN_OFF);
-        filter.addAction(Intent.ACTION_TIME_CHANGED);
         filter.addAction(Intent.ACTION_USER_SWITCHED);
         registerReceiver(mSystemBroadcastReceiver, filter);
 
@@ -460,22 +427,21 @@
         RecentsTaskLoader loader = Recents.getTaskLoader();
         RecentsTaskLoadPlan loadPlan = RecentsImpl.consumeInstanceLoadPlan();
         if (loadPlan == null) {
-            loadPlan = loader.createLoadPlan(this);
+            loadPlan = new RecentsTaskLoadPlan(this);
         }
 
         // Start loading tasks according to the load plan
         RecentsConfiguration config = Recents.getConfiguration();
         RecentsActivityLaunchState launchState = config.getLaunchState();
         if (!loadPlan.hasTasks()) {
-            loader.preloadTasks(loadPlan, launchState.launchedToTaskId,
-                    !launchState.launchedFromHome && !launchState.launchedViaDockGesture);
+            loader.preloadTasks(loadPlan, launchState.launchedToTaskId);
         }
 
         RecentsTaskLoadPlan.Options loadOpts = new RecentsTaskLoadPlan.Options();
         loadOpts.runningTaskId = launchState.launchedToTaskId;
         loadOpts.numVisibleTasks = launchState.launchedNumVisibleTasks;
         loadOpts.numVisibleTaskThumbnails = launchState.launchedNumVisibleThumbnails;
-        loader.loadTasks(this, loadPlan, loadOpts);
+        loader.loadTasks(loadPlan, loadOpts);
         TaskStack stack = loadPlan.getTaskStack();
         mRecentsView.onReload(stack, mIsVisible);
 
@@ -540,7 +506,6 @@
         super.onPause();
 
         mIgnoreAltTabRelease = false;
-        mIterateTrigger.stopDozing();
     }
 
     @Override
@@ -643,8 +608,7 @@
                     if (backward) {
                         EventBus.getDefault().send(new FocusPreviousTaskViewEvent());
                     } else {
-                        EventBus.getDefault().send(
-                                new FocusNextTaskViewEvent(0 /* timerIndicatorDuration */));
+                        EventBus.getDefault().send(new FocusNextTaskViewEvent());
                     }
                     mLastTabKeyEventTime = SystemClock.elapsedRealtime();
 
@@ -702,38 +666,10 @@
         }
     }
 
-    public final void onBusEvent(IterateRecentsEvent event) {
-        final RecentsDebugFlags debugFlags = Recents.getDebugFlags();
-
-        // Start dozing after the recents button is clicked
-        int timerIndicatorDuration = 0;
-        if (debugFlags.isFastToggleRecentsEnabled()) {
-            timerIndicatorDuration = getResources().getInteger(
-                    R.integer.recents_subsequent_auto_advance_duration);
-
-            mIterateTrigger.setDozeDuration(timerIndicatorDuration);
-            if (!mIterateTrigger.isDozing()) {
-                mIterateTrigger.startDozing();
-            } else {
-                mIterateTrigger.poke();
-            }
-        }
-
-        // Focus the next task
-        EventBus.getDefault().send(new FocusNextTaskViewEvent(timerIndicatorDuration));
-
-        MetricsLogger.action(this, MetricsEvent.ACTION_OVERVIEW_PAGE);
-    }
-
     public final void onBusEvent(RecentsActivityStartingEvent event) {
         mRecentsStartRequested = true;
     }
 
-    public final void onBusEvent(UserInteractionEvent event) {
-        // Stop the fast-toggle dozer
-        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
@@ -751,15 +687,11 @@
     }
 
     public final void onBusEvent(EnterRecentsWindowLastAnimationFrameEvent event) {
-        EventBus.getDefault().send(new UpdateFreeformTaskViewVisibilityEvent(true));
         mRecentsView.getViewTreeObserver().addOnPreDrawListener(this);
         mRecentsView.invalidate();
     }
 
     public final void onBusEvent(ExitRecentsWindowFirstAnimationFrameEvent event) {
-        if (mRecentsView.isLastTaskLaunchedFreeform()) {
-            EventBus.getDefault().send(new UpdateFreeformTaskViewVisibilityEvent(false));
-        }
         mRecentsView.getViewTreeObserver().addOnPreDrawListener(this);
         mRecentsView.invalidate();
     }
@@ -859,11 +791,6 @@
         MetricsLogger.count(this, "overview_screen_pinned", 1);
     }
 
-    public final void onBusEvent(DebugFlagsChangedEvent event) {
-        // Just finish recents so that we can reload the flags anew on the next instantiation
-        finish();
-    }
-
     public final void onBusEvent(StackViewScrolledEvent event) {
         // Once the user has scrolled while holding alt-tab, then we should ignore the release of
         // the key
@@ -888,14 +815,13 @@
         RecentsConfiguration config = Recents.getConfiguration();
         RecentsActivityLaunchState launchState = config.getLaunchState();
         RecentsTaskLoader loader = Recents.getTaskLoader();
-        RecentsTaskLoadPlan loadPlan = loader.createLoadPlan(this);
-        loader.preloadTasks(loadPlan, -1 /* runningTaskId */,
-                false /* includeFrontMostExcludedTask */);
+        RecentsTaskLoadPlan loadPlan = new RecentsTaskLoadPlan(this);
+        loader.preloadTasks(loadPlan, -1 /* runningTaskId */);
 
         RecentsTaskLoadPlan.Options loadOpts = new RecentsTaskLoadPlan.Options();
         loadOpts.numVisibleTasks = launchState.launchedNumVisibleTasks;
         loadOpts.numVisibleTaskThumbnails = launchState.launchedNumVisibleThumbnails;
-        loader.loadTasks(this, loadPlan, loadOpts);
+        loader.loadTasks(loadPlan, loadOpts);
 
         TaskStack stack = loadPlan.getTaskStack();
         int numStackTasks = stack.getStackTaskCount();
@@ -924,6 +850,11 @@
         return true;
     }
 
+    public void onPackageChanged(String packageName, int userId) {
+        Recents.getTaskLoader().onPackageChanged(packageName);
+        EventBus.getDefault().send(new PackagesChangedEvent(packageName, userId));
+    }
+
     @Override
     public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
         super.dump(prefix, fd, writer, args);
@@ -931,13 +862,9 @@
         Recents.getTaskLoader().dump(prefix, writer);
 
         String id = Integer.toHexString(System.identityHashCode(this));
-        long lastStackActiveTime = Settings.Secure.getLongForUser(getContentResolver(),
-                Secure.OVERVIEW_LAST_STACK_ACTIVE_TIME, -1,
-                SystemServicesProxy.getInstance(this).getCurrentUser());
 
         writer.print(prefix); writer.print(TAG);
         writer.print(" visible="); writer.print(mIsVisible ? "Y" : "N");
-        writer.print(" lastStackTaskActiveTime="); writer.print(lastStackActiveTime);
         writer.print(" currentTime="); writer.print(System.currentTimeMillis());
         writer.print(" [0x"); writer.print(id); writer.print("]");
         writer.println();
diff --git a/com/android/systemui/recents/RecentsActivityLaunchState.java b/com/android/systemui/recents/RecentsActivityLaunchState.java
index 5b8ed94..d2326ce 100644
--- a/com/android/systemui/recents/RecentsActivityLaunchState.java
+++ b/com/android/systemui/recents/RecentsActivityLaunchState.java
@@ -33,7 +33,6 @@
     public boolean launchedFromPipApp;
     // Set if the next activity that quick-switch will launch is the PiP activity
     public boolean launchedWithNextPipApp;
-    public boolean launchedFromBlacklistedApp;
     public boolean launchedFromHome;
     public boolean launchedViaDragGesture;
     public boolean launchedViaDockGesture;
@@ -44,7 +43,6 @@
     public void reset() {
         launchedFromHome = false;
         launchedFromApp = false;
-        launchedFromBlacklistedApp = false;
         launchedFromPipApp = false;
         launchedWithNextPipApp = false;
         launchedToTaskId = -1;
@@ -60,18 +58,6 @@
         RecentsDebugFlags debugFlags = Recents.getDebugFlags();
         RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
         if (launchedFromApp) {
-            if (!launchState.launchedWithAltTab && debugFlags.isFastToggleRecentsEnabled()) {
-                // If fast toggling, focus the front most task so that the next tap will launch the
-                // task
-                return numTasks - 1;
-            }
-
-            if (launchState.launchedFromBlacklistedApp) {
-                // If we are launching from a blacklisted app, focus the front most task so that the
-                // next tap will launch the task
-                return numTasks - 1;
-            }
-
             if (useGridLayout) {
                 // If coming from another app to the grid layout, focus the front most task
                 return numTasks - 1;
@@ -80,12 +66,6 @@
             // If coming from another app, focus the next task
             return Math.max(0, numTasks - 2);
         } else {
-            if (!launchState.launchedWithAltTab && debugFlags.isFastToggleRecentsEnabled()) {
-                // If fast toggling, defer focusing until the next tap (which will automatically
-                // focus the front most task)
-                return -1;
-            }
-
             // If coming from home, focus the front most task
             return numTasks - 1;
         }
diff --git a/com/android/systemui/recents/RecentsConfiguration.java b/com/android/systemui/recents/RecentsConfiguration.java
index 5dc6f31..68df1d5 100644
--- a/com/android/systemui/recents/RecentsConfiguration.java
+++ b/com/android/systemui/recents/RecentsConfiguration.java
@@ -20,31 +20,31 @@
 import android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.Resources;
-import android.graphics.Rect;
 
 import android.os.SystemProperties;
 
 import com.android.systemui.R;
 import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.model.TaskStack;
+import com.android.systemui.recents.views.DockState;
+import com.android.systemui.shared.recents.model.TaskStack;
 
 /**
  * Represents the dock regions for each orientation.
  */
 class DockRegion {
-    public static TaskStack.DockState[] PHONE_LANDSCAPE = {
+    public static DockState[] PHONE_LANDSCAPE = {
             // We only allow docking to the left in landscape for now on small devices
-            TaskStack.DockState.LEFT
+            DockState.LEFT
     };
-    public static TaskStack.DockState[] PHONE_PORTRAIT = {
+    public static DockState[] PHONE_PORTRAIT = {
             // We only allow docking to the top for now on small devices
-            TaskStack.DockState.TOP
+            DockState.TOP
     };
-    public static TaskStack.DockState[] TABLET_LANDSCAPE = {
-            TaskStack.DockState.LEFT,
-            TaskStack.DockState.RIGHT
+    public static DockState[] TABLET_LANDSCAPE = {
+            DockState.LEFT,
+            DockState.RIGHT
     };
-    public static TaskStack.DockState[] TABLET_PORTRAIT = PHONE_PORTRAIT;
+    public static DockState[] TABLET_PORTRAIT = PHONE_PORTRAIT;
 }
 
 /**
@@ -56,18 +56,6 @@
     private static final int LARGE_SCREEN_MIN_DP = 600;
     private static final int XLARGE_SCREEN_MIN_DP = 720;
 
-    /** Levels of svelte in increasing severity/austerity. */
-    // No svelting.
-    public static final int SVELTE_NONE = 0;
-    // Limit thumbnail cache to number of visible thumbnails when Recents was loaded, disable
-    // caching thumbnails as you scroll.
-    public static final int SVELTE_LIMIT_CACHE = 1;
-    // Disable the thumbnail cache, load thumbnails asynchronously when the activity loads and
-    // evict all thumbnails when hidden.
-    public static final int SVELTE_DISABLE_CACHE = 2;
-    // Disable all thumbnail loading.
-    public static final int SVELTE_DISABLE_LOADING = 3;
-
     // Launch states
     public RecentsActivityLaunchState mLaunchState = new RecentsActivityLaunchState();
 
@@ -125,7 +113,7 @@
      * Returns the preferred dock states for the current orientation.
      * @return a list of dock states for device and its orientation
      */
-    public TaskStack.DockState[] getDockStatesForCurrentOrientation() {
+    public DockState[] getDockStatesForCurrentOrientation() {
         boolean isLandscape = mAppContext.getResources().getConfiguration().orientation ==
                 Configuration.ORIENTATION_LANDSCAPE;
         RecentsConfiguration config = Recents.getConfiguration();
diff --git a/com/android/systemui/recents/RecentsDebugFlags.java b/com/android/systemui/recents/RecentsDebugFlags.java
index 0262a09..1918593 100644
--- a/com/android/systemui/recents/RecentsDebugFlags.java
+++ b/com/android/systemui/recents/RecentsDebugFlags.java
@@ -16,75 +16,14 @@
 
 package com.android.systemui.recents;
 
-import android.content.Context;
-
-import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.events.activity.DebugFlagsChangedEvent;
-import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.tuner.TunerService;
-
-/**
- * Tunable debug flags
- */
-public class RecentsDebugFlags implements TunerService.Tunable {
+public class RecentsDebugFlags {
 
     public static class Static {
         // Enables debug drawing for the transition thumbnail
         public static final boolean EnableTransitionThumbnailDebugMode = false;
-        // This disables the bitmap and icon caches
-        public static final boolean DisableBackgroundCache = false;
-        // Enables the task affiliations
-        public static final boolean EnableAffiliatedTaskGroups = false;
-        // Enables the button above the stack
-        public static final boolean EnableStackActionButton = true;
-        // Overrides the Tuner flags and enables the timeout
-        private static final boolean EnableFastToggleTimeout = false;
-        // Overrides the Tuner flags and enables the paging via the Recents button
-        private static final boolean EnablePaging = false;
+
         // Disables enter and exit transitions for other tasks for low ram devices
         public static final boolean DisableRecentsLowRamEnterExitAnimation = false;
 
-        // Enables us to create mock recents tasks
-        public static final boolean EnableMockTasks = false;
-        // Defines the number of mock recents packages to create
-        public static final int MockTasksPackageCount = 3;
-        // Defines the number of mock recents tasks to create
-        public static final int MockTaskCount = 100;
-        // Enables the simulated task affiliations
-        public static final boolean EnableMockTaskGroups = false;
-        // Defines the number of mock task affiliations per group
-        public static final int MockTaskGroupsTaskCount = 12;
-    }
-
-    /**
-     * We read the prefs once when we start the activity, then update them as the tuner changes
-     * the flags.
-     */
-    public RecentsDebugFlags(Context context) {
-        // Register all our flags, this will also call onTuningChanged() for each key, which will
-        // initialize the current state of each flag
-    }
-
-    /**
-     * @return whether we are enabling fast toggling.
-     */
-    public boolean isFastToggleRecentsEnabled() {
-        SystemServicesProxy ssp = Recents.getSystemServices();
-        if (ssp.hasFreeformWorkspaceSupport() || ssp.isTouchExplorationEnabled()) {
-            return false;
-        }
-        return Static.EnableFastToggleTimeout;
-    }
-
-    /**
-     * @return whether we are enabling paging.
-     */
-    public boolean isPagingEnabled() {
-        return Static.EnablePaging;
-    }
-
-    @Override
-    public void onTuningChanged(String key, String newValue) {
-        EventBus.getDefault().send(new DebugFlagsChangedEvent());
     }
 }
diff --git a/com/android/systemui/recents/RecentsImpl.java b/com/android/systemui/recents/RecentsImpl.java
index 3e2a5f3..868ed64 100644
--- a/com/android/systemui/recents/RecentsImpl.java
+++ b/com/android/systemui/recents/RecentsImpl.java
@@ -18,7 +18,6 @@
 
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.view.View.MeasureSpec;
 
 import android.app.ActivityManager;
@@ -56,7 +55,6 @@
 import com.android.systemui.recents.events.activity.DockedTopTaskEvent;
 import com.android.systemui.recents.events.activity.EnterRecentsWindowLastAnimationFrameEvent;
 import com.android.systemui.recents.events.activity.HideRecentsEvent;
-import com.android.systemui.recents.events.activity.IterateRecentsEvent;
 import com.android.systemui.recents.events.activity.LaunchMostRecentTaskRequestEvent;
 import com.android.systemui.recents.events.activity.LaunchNextTaskRequestEvent;
 import com.android.systemui.recents.events.activity.RecentsActivityStartingEvent;
@@ -72,20 +70,18 @@
 import com.android.systemui.recents.misc.DozeTrigger;
 import com.android.systemui.recents.misc.ForegroundThread;
 import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener;
-import com.android.systemui.recents.model.RecentsTaskLoadPlan;
-import com.android.systemui.recents.model.RecentsTaskLoader;
-import com.android.systemui.recents.model.Task;
-import com.android.systemui.recents.model.Task.TaskKey;
-import com.android.systemui.recents.model.TaskGrouping;
-import com.android.systemui.recents.model.TaskStack;
-import com.android.systemui.recents.model.ThumbnailData;
+import com.android.systemui.recents.misc.TaskStackChangeListener;
+import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan;
+import com.android.systemui.shared.recents.model.RecentsTaskLoader;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.model.Task.TaskKey;
+import com.android.systemui.shared.recents.model.TaskStack;
+import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.recents.views.RecentsTransitionHelper;
 import com.android.systemui.recents.views.RecentsTransitionHelper.AppTransitionAnimationSpecsFuture;
 import com.android.systemui.recents.views.TaskStackLayoutAlgorithm;
 import com.android.systemui.recents.views.TaskStackLayoutAlgorithm.VisibilityReport;
 import com.android.systemui.recents.views.TaskStackView;
-import com.android.systemui.recents.views.TaskStackViewScroller;
 import com.android.systemui.recents.views.TaskViewHeader;
 import com.android.systemui.recents.views.TaskViewTransform;
 import com.android.systemui.recents.views.grid.TaskGridLayoutAlgorithm;
@@ -117,10 +113,10 @@
     public final static String RECENTS_ACTIVITY = "com.android.systemui.recents.RecentsActivity";
 
     /**
-     * An implementation of TaskStackListener, that allows us to listen for changes to the system
+     * An implementation of TaskStackChangeListener, that allows us to listen for changes to the system
      * task stacks and update recents accordingly.
      */
-    class TaskStackListenerImpl extends TaskStackListener {
+    class TaskStackListenerImpl extends TaskStackChangeListener {
 
         @Override
         public void onTaskStackChangedBackground() {
@@ -131,7 +127,7 @@
 
             // Preloads the next task
             RecentsConfiguration config = Recents.getConfiguration();
-            if (config.svelteLevel == RecentsConfiguration.SVELTE_NONE) {
+            if (config.svelteLevel == RecentsTaskLoader.SVELTE_NONE) {
                 Rect windowRect = getWindowRect(null /* windowRectOverride */);
                 if (windowRect.isEmpty()) {
                     return;
@@ -141,8 +137,8 @@
                 SystemServicesProxy ssp = Recents.getSystemServices();
                 ActivityManager.RunningTaskInfo runningTaskInfo = ssp.getRunningTask();
                 RecentsTaskLoader loader = Recents.getTaskLoader();
-                RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
-                loader.preloadTasks(plan, -1, false /* includeFrontMostExcludedTask */);
+                RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(mContext);
+                loader.preloadTasks(plan, -1);
                 TaskStack stack = plan.getTaskStack();
                 RecentsActivityLaunchState launchState = new RecentsActivityLaunchState();
                 RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
@@ -168,7 +164,7 @@
                     launchOpts.onlyLoadPausedActivities = true;
                     launchOpts.loadThumbnails = true;
                 }
-                loader.loadTasks(mContext, plan, launchOpts);
+                loader.loadTasks(plan, launchOpts);
             }
         }
 
@@ -207,7 +203,7 @@
             }
 
             EventBus.getDefault().send(new TaskSnapshotChangedEvent(taskId,
-                    ThumbnailData.createFromTaskSnapshot(snapshot)));
+                    new ThumbnailData(snapshot)));
         }
     }
 
@@ -282,13 +278,13 @@
         // When we start, preload the data associated with the previous recent tasks.
         // We can use a new plan since the caches will be the same.
         RecentsTaskLoader loader = Recents.getTaskLoader();
-        RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
-        loader.preloadTasks(plan, -1, false /* includeFrontMostExcludedTask */);
+        RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(mContext);
+        loader.preloadTasks(plan, -1);
         RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
         launchOpts.numVisibleTasks = loader.getIconCacheSize();
         launchOpts.numVisibleTaskThumbnails = loader.getThumbnailCacheSize();
         launchOpts.onlyLoadForCache = true;
-        loader.loadTasks(mContext, plan, launchOpts);
+        loader.loadTasks(plan, launchOpts);
     }
 
     public void onConfigurationChanged() {
@@ -409,22 +405,17 @@
                 RecentsConfiguration config = Recents.getConfiguration();
                 RecentsActivityLaunchState launchState = config.getLaunchState();
                 if (!launchState.launchedWithAltTab) {
-                    // Has the user tapped quickly?
-                    boolean isQuickTap = elapsedTime < ViewConfiguration.getDoubleTapTimeout();
                     if (Recents.getConfiguration().isGridEnabled) {
+                        // Has the user tapped quickly?
+                        boolean isQuickTap = elapsedTime < ViewConfiguration.getDoubleTapTimeout();
                         if (isQuickTap) {
                             EventBus.getDefault().post(new LaunchNextTaskRequestEvent());
                         } else {
                             EventBus.getDefault().post(new LaunchMostRecentTaskRequestEvent());
                         }
                     } else {
-                        if (!debugFlags.isPagingEnabled() || isQuickTap) {
-                            // Launch the next focused task
-                            EventBus.getDefault().post(new LaunchNextTaskRequestEvent());
-                        } else {
-                            // Notify recents to move onto the next task
-                            EventBus.getDefault().post(new IterateRecentsEvent());
-                        }
+                        // Launch the next focused task
+                        EventBus.getDefault().post(new LaunchNextTaskRequestEvent());
                     }
                 } else {
                     // If the user has toggled it too quickly, then just eat up the event here (it's
@@ -473,16 +464,15 @@
         // RecentsActivity) only if there is a task to animate to.  Post this to ensure that we
         // don't block the touch feedback on the nav bar button which triggers this.
         mHandler.post(() -> {
-            MutableBoolean isHomeStackVisible = new MutableBoolean(true);
-            if (!ssp.isRecentsActivityVisible(isHomeStackVisible)) {
+            if (!ssp.isRecentsActivityVisible(null)) {
                 ActivityManager.RunningTaskInfo runningTask = ssp.getRunningTask();
                 if (runningTask == null) {
                     return;
                 }
 
                 RecentsTaskLoader loader = Recents.getTaskLoader();
-                sInstanceLoadPlan = loader.createLoadPlan(mContext);
-                loader.preloadTasks(sInstanceLoadPlan, runningTask.id, !isHomeStackVisible.value);
+                sInstanceLoadPlan = new RecentsTaskLoadPlan(mContext);
+                loader.preloadTasks(sInstanceLoadPlan, runningTask.id);
                 TaskStack stack = sInstanceLoadPlan.getTaskStack();
                 if (stack.getTaskCount() > 0) {
                     // Only preload the icon (but not the thumbnail since it may not have been taken
@@ -521,8 +511,8 @@
     public void showNextTask() {
         SystemServicesProxy ssp = Recents.getSystemServices();
         RecentsTaskLoader loader = Recents.getTaskLoader();
-        RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
-        loader.preloadTasks(plan, -1, false /* includeFrontMostExcludedTask */);
+        RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(mContext);
+        loader.preloadTasks(plan, -1);
         TaskStack focusedStack = plan.getTaskStack();
 
         // Return early if there are no tasks in the focused stack
@@ -576,8 +566,8 @@
     public void showRelativeAffiliatedTask(boolean showNextTask) {
         SystemServicesProxy ssp = Recents.getSystemServices();
         RecentsTaskLoader loader = Recents.getTaskLoader();
-        RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
-        loader.preloadTasks(plan, -1, false /* includeFrontMostExcludedTask */);
+        RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(mContext);
+        loader.preloadTasks(plan, -1);
         TaskStack focusedStack = plan.getTaskStack();
 
         // Return early if there are no tasks in the focused stack
@@ -595,43 +585,38 @@
         Task toTask = null;
         ActivityOptions launchOpts = null;
         int taskCount = tasks.size();
-        int numAffiliatedTasks = 0;
         for (int i = 0; i < taskCount; i++) {
             Task task = tasks.get(i);
             if (task.key.id == runningTask.id) {
-                TaskGrouping group = task.group;
-                Task.TaskKey toTaskKey;
                 if (showNextTask) {
-                    toTaskKey = group.getNextTaskInGroup(task);
-                    launchOpts = ActivityOptions.makeCustomAnimation(mContext,
-                            R.anim.recents_launch_next_affiliated_task_target,
-                            R.anim.recents_launch_next_affiliated_task_source);
+                    if ((i + 1) < taskCount) {
+                        toTask = tasks.get(i + 1);
+                        launchOpts = ActivityOptions.makeCustomAnimation(mContext,
+                                R.anim.recents_launch_next_affiliated_task_target,
+                                R.anim.recents_launch_next_affiliated_task_source);
+                    }
                 } else {
-                    toTaskKey = group.getPrevTaskInGroup(task);
-                    launchOpts = ActivityOptions.makeCustomAnimation(mContext,
-                            R.anim.recents_launch_prev_affiliated_task_target,
-                            R.anim.recents_launch_prev_affiliated_task_source);
+                    if ((i - 1) >= 0) {
+                        toTask = tasks.get(i - 1);
+                        launchOpts = ActivityOptions.makeCustomAnimation(mContext,
+                                R.anim.recents_launch_prev_affiliated_task_target,
+                                R.anim.recents_launch_prev_affiliated_task_source);
+                    }
                 }
-                if (toTaskKey != null) {
-                    toTask = focusedStack.findTaskWithId(toTaskKey.id);
-                }
-                numAffiliatedTasks = group.getTaskCount();
                 break;
             }
         }
 
         // Return early if there is no next task
         if (toTask == null) {
-            if (numAffiliatedTasks > 1) {
-                if (showNextTask) {
-                    ssp.startInPlaceAnimationOnFrontMostApplication(
-                            ActivityOptions.makeCustomInPlaceAnimation(mContext,
-                                    R.anim.recents_launch_next_affiliated_task_bounce));
-                } else {
-                    ssp.startInPlaceAnimationOnFrontMostApplication(
-                            ActivityOptions.makeCustomInPlaceAnimation(mContext,
-                                    R.anim.recents_launch_prev_affiliated_task_bounce));
-                }
+            if (showNextTask) {
+                ssp.startInPlaceAnimationOnFrontMostApplication(
+                        ActivityOptions.makeCustomInPlaceAnimation(mContext,
+                                R.anim.recents_launch_next_affiliated_task_bounce));
+            } else {
+                ssp.startInPlaceAnimationOnFrontMostApplication(
+                        ActivityOptions.makeCustomInPlaceAnimation(mContext,
+                                R.anim.recents_launch_prev_affiliated_task_bounce));
             }
             return;
         }
@@ -753,8 +738,7 @@
             stackLayout.getTaskStackBounds(displayRect, windowRect, systemInsets.top,
                     systemInsets.left, systemInsets.right, mTmpBounds);
             stackLayout.reset();
-            stackLayout.initialize(displayRect, windowRect, mTmpBounds,
-                    TaskStackLayoutAlgorithm.StackState.getStackStateForStack(stack));
+            stackLayout.initialize(displayRect, windowRect, mTmpBounds);
         }
     }
 
@@ -843,7 +827,7 @@
         launchOpts.runningTaskId = runningTaskId;
         launchOpts.loadThumbnails = false;
         launchOpts.onlyLoadForCache = true;
-        Recents.getTaskLoader().loadTasks(mContext, sInstanceLoadPlan, launchOpts);
+        Recents.getTaskLoader().loadTasks(sInstanceLoadPlan, launchOpts);
     }
 
     /**
@@ -873,61 +857,29 @@
             getThumbnailTransitionActivityOptions(ActivityManager.RunningTaskInfo runningTask,
                     Rect windowOverrideRect) {
         final boolean isLowRamDevice = Recents.getConfiguration().isLowRamDevice;
-        if (runningTask != null
-                && runningTask.configuration.windowConfiguration.getWindowingMode()
-                == WINDOWING_MODE_FREEFORM) {
-            ArrayList<AppTransitionAnimationSpec> specs = new ArrayList<>();
-            ArrayList<Task> tasks = mDummyStackView.getStack().getStackTasks();
-            TaskStackLayoutAlgorithm stackLayout = mDummyStackView.getStackAlgorithm();
-            TaskStackViewScroller stackScroller = mDummyStackView.getScroller();
 
-            mDummyStackView.updateLayoutAlgorithm(true /* boundScroll */);
-            mDummyStackView.updateToInitialState();
+        // Update the destination rect
+        Task toTask = new Task();
+        TaskViewTransform toTransform = getThumbnailTransitionTransform(mDummyStackView, toTask,
+                windowOverrideRect);
 
-            for (int i = tasks.size() - 1; i >= 0; i--) {
-                Task task = tasks.get(i);
-                if (task.isFreeformTask()) {
-                    mTmpTransform = stackLayout.getStackTransformScreenCoordinates(task,
-                            stackScroller.getStackScroll(), mTmpTransform, null,
-                            windowOverrideRect);
-                    GraphicBuffer thumbnail = drawThumbnailTransitionBitmap(task, mTmpTransform);
-                    Rect toTaskRect = new Rect();
-                    mTmpTransform.rect.round(toTaskRect);
-                    specs.add(new AppTransitionAnimationSpec(task.key.id, thumbnail, toTaskRect));
-                }
-            }
-            AppTransitionAnimationSpec[] specsArray = new AppTransitionAnimationSpec[specs.size()];
-            specs.toArray(specsArray);
+        RectF toTaskRect = toTransform.rect;
+        AppTransitionAnimationSpecsFuture future =
+                new RecentsTransitionHelper(mContext).getAppTransitionFuture(
+                        () -> {
+                    Rect rect = new Rect();
+                    toTaskRect.round(rect);
+                    GraphicBuffer thumbnail = drawThumbnailTransitionBitmap(toTask,
+                            toTransform);
+                    return Lists.newArrayList(new AppTransitionAnimationSpec(
+                            toTask.key.id, thumbnail, rect));
+                });
 
-            // For low end ram devices, wait for transition flag is reset when Recents entrance
-            // animation is complete instead of when the transition animation starts
-            return new Pair<>(ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView,
-                    specsArray, mHandler, isLowRamDevice ? null : mResetToggleFlagListener, this),
-                    null);
-        } else {
-            // Update the destination rect
-            Task toTask = new Task();
-            TaskViewTransform toTransform = getThumbnailTransitionTransform(mDummyStackView, toTask,
-                    windowOverrideRect);
-
-            RectF toTaskRect = toTransform.rect;
-            AppTransitionAnimationSpecsFuture future =
-                    new RecentsTransitionHelper(mContext).getAppTransitionFuture(
-                            () -> {
-                        Rect rect = new Rect();
-                        toTaskRect.round(rect);
-                        GraphicBuffer thumbnail = drawThumbnailTransitionBitmap(toTask,
-                                toTransform);
-                        return Lists.newArrayList(new AppTransitionAnimationSpec(
-                                toTask.key.id, thumbnail, rect));
-                    });
-
-            // For low end ram devices, wait for transition flag is reset when Recents entrance
-            // animation is complete instead of when the transition animation starts
-            return new Pair<>(ActivityOptions.makeMultiThumbFutureAspectScaleAnimation(mContext,
-                    mHandler, future.getFuture(), isLowRamDevice ? null : mResetToggleFlagListener,
-                    false /* scaleUp */), future);
-        }
+        // For low end ram devices, wait for transition flag is reset when Recents entrance
+        // animation is complete instead of when the transition animation starts
+        return new Pair<>(ActivityOptions.makeMultiThumbFutureAspectScaleAnimation(mContext,
+                mHandler, future.getFuture(), isLowRamDevice ? null : mResetToggleFlagListener,
+                false /* scaleUp */), future);
     }
 
     /**
@@ -942,7 +894,7 @@
             runningTaskOut.copyFrom(launchTask);
         } else {
             // If no task is specified or we can not find the task just use the front most one
-            launchTask = stack.getStackFrontMostTask(true /* includeFreeform */);
+            launchTask = stack.getStackFrontMostTask();
             runningTaskOut.copyFrom(launchTask);
         }
 
@@ -995,12 +947,8 @@
             boolean isHomeStackVisible, boolean animate, int growTarget) {
         RecentsTaskLoader loader = Recents.getTaskLoader();
         RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
-        SystemServicesProxy ssp = Recents.getSystemServices();
-        boolean isBlacklisted = (runningTask != null)
-                ? ssp.isBlackListedActivity(runningTask.baseActivity.getClassName())
-                : false;
 
-        int runningTaskId = !mLaunchedWhileDocking && !isBlacklisted && (runningTask != null)
+        int runningTaskId = !mLaunchedWhileDocking && (runningTask != null)
                 ? runningTask.id
                 : -1;
 
@@ -1009,10 +957,10 @@
         // the stacks might have changed.
         if (mLaunchedWhileDocking || mTriggeredFromAltTab || sInstanceLoadPlan == null) {
             // Create a new load plan if preloadRecents() was never triggered
-            sInstanceLoadPlan = loader.createLoadPlan(mContext);
+            sInstanceLoadPlan = new RecentsTaskLoadPlan(mContext);
         }
         if (mLaunchedWhileDocking || mTriggeredFromAltTab || !sInstanceLoadPlan.hasTasks()) {
-            loader.preloadTasks(sInstanceLoadPlan, runningTaskId, !isHomeStackVisible);
+            loader.preloadTasks(sInstanceLoadPlan, runningTaskId);
         }
 
         TaskStack stack = sInstanceLoadPlan.getTaskStack();
@@ -1023,7 +971,6 @@
         // Update the launch state that we need in updateHeaderBarLayout()
         launchState.launchedFromHome = !useThumbnailTransition && !mLaunchedWhileDocking;
         launchState.launchedFromApp = useThumbnailTransition || mLaunchedWhileDocking;
-        launchState.launchedFromBlacklistedApp = launchState.launchedFromApp && isBlacklisted;
         launchState.launchedFromPipApp = false;
         launchState.launchedWithNextPipApp =
                 stack.isNextLaunchTargetPip(RecentsImpl.getLastPipTime());
@@ -1059,9 +1006,7 @@
         }
 
         Pair<ActivityOptions, AppTransitionAnimationSpecsFuture> pair;
-        if (isBlacklisted) {
-            pair = new Pair<>(getUnknownTransitionActivityOptions(), null);
-        } else if (useThumbnailTransition) {
+        if (useThumbnailTransition) {
             // Try starting with a thumbnail transition
             pair = getThumbnailTransitionActivityOptions(runningTask, windowOverrideRect);
         } else {
diff --git a/com/android/systemui/recents/RecentsSystemUser.java b/com/android/systemui/recents/RecentsSystemUser.java
index 1285626..ff1f7dc 100644
--- a/com/android/systemui/recents/RecentsSystemUser.java
+++ b/com/android/systemui/recents/RecentsSystemUser.java
@@ -27,6 +27,7 @@
 import com.android.systemui.EventLogConstants;
 import com.android.systemui.EventLogTags;
 import com.android.systemui.recents.events.EventBus;
+import com.android.systemui.recents.events.activity.DockedFirstAnimationFrameEvent;
 import com.android.systemui.recents.events.activity.DockedTopTaskEvent;
 import com.android.systemui.recents.events.activity.RecentsActivityStartingEvent;
 import com.android.systemui.recents.events.component.SetWaitingForTransitionStartEvent;
@@ -108,6 +109,11 @@
     }
 
     @Override
+    public void sendDockedFirstAnimationFrameEvent() throws RemoteException {
+        EventBus.getDefault().post(new DockedFirstAnimationFrameEvent());
+    }
+
+    @Override
     public void setWaitingForTransitionStartEvent(boolean waitingForTransitionStart) {
         EventBus.getDefault().post(new SetWaitingForTransitionStartEvent(
                 waitingForTransitionStart));
diff --git a/com/android/systemui/recents/events/activity/CancelEnterRecentsWindowAnimationEvent.java b/com/android/systemui/recents/events/activity/CancelEnterRecentsWindowAnimationEvent.java
index 7604de1..fec34e3 100644
--- a/com/android/systemui/recents/events/activity/CancelEnterRecentsWindowAnimationEvent.java
+++ b/com/android/systemui/recents/events/activity/CancelEnterRecentsWindowAnimationEvent.java
@@ -17,7 +17,7 @@
 package com.android.systemui.recents.events.activity;
 
 import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.model.Task;
+import com.android.systemui.shared.recents.model.Task;
 
 /**
  * This is sent when we want to cancel the enter-recents window animation for the launch task.
diff --git a/com/android/systemui/recents/events/activity/DebugFlagsChangedEvent.java b/com/android/systemui/recents/events/activity/DebugFlagsChangedEvent.java
deleted file mode 100644
index fe3bf26..0000000
--- a/com/android/systemui/recents/events/activity/DebugFlagsChangedEvent.java
+++ /dev/null
@@ -1,26 +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.systemui.recents.events.activity;
-
-import com.android.systemui.recents.events.EventBus;
-
-/**
- * This is sent when the SystemUI tuner changes a flag.
- */
-public class DebugFlagsChangedEvent extends EventBus.Event {
-    // Simple event
-}
diff --git a/com/android/systemui/recents/events/activity/IterateRecentsEvent.java b/com/android/systemui/recents/events/activity/IterateRecentsEvent.java
deleted file mode 100644
index f7b2706..0000000
--- a/com/android/systemui/recents/events/activity/IterateRecentsEvent.java
+++ /dev/null
@@ -1,27 +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.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/com/android/systemui/recents/events/activity/LaunchTaskEvent.java b/com/android/systemui/recents/events/activity/LaunchTaskEvent.java
index 862a1ee..2409f39 100644
--- a/com/android/systemui/recents/events/activity/LaunchTaskEvent.java
+++ b/com/android/systemui/recents/events/activity/LaunchTaskEvent.java
@@ -22,7 +22,7 @@
 import android.graphics.Rect;
 
 import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.model.Task;
+import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.recents.views.TaskView;
 
 /**
diff --git a/com/android/systemui/recents/events/activity/MultiWindowStateChangedEvent.java b/com/android/systemui/recents/events/activity/MultiWindowStateChangedEvent.java
index 64eeafa..e4972b1 100644
--- a/com/android/systemui/recents/events/activity/MultiWindowStateChangedEvent.java
+++ b/com/android/systemui/recents/events/activity/MultiWindowStateChangedEvent.java
@@ -17,7 +17,7 @@
 package com.android.systemui.recents.events.activity;
 
 import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.model.TaskStack;
+import com.android.systemui.shared.recents.model.TaskStack;
 
 /**
  * This is sent by the activity whenever the multi-window state has changed.
diff --git a/com/android/systemui/recents/events/activity/PackagesChangedEvent.java b/com/android/systemui/recents/events/activity/PackagesChangedEvent.java
index 3b68574..47670e0 100644
--- a/com/android/systemui/recents/events/activity/PackagesChangedEvent.java
+++ b/com/android/systemui/recents/events/activity/PackagesChangedEvent.java
@@ -17,22 +17,20 @@
 package com.android.systemui.recents.events.activity;
 
 import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.model.RecentsPackageMonitor;
 import com.android.systemui.recents.views.TaskStackView;
+import com.android.systemui.recents.RecentsActivity;
 
 /**
- * This event is sent by {@link RecentsPackageMonitor} when a package on the the system changes.
+ * This event is sent by {@link RecentsActivity} when a package on the the system changes.
  * {@link TaskStackView}s listen for this event, and remove the tasks associated with the removed
  * packages.
  */
 public class PackagesChangedEvent extends EventBus.Event {
 
-    public final RecentsPackageMonitor monitor;
     public final String packageName;
     public final int userId;
 
-    public PackagesChangedEvent(RecentsPackageMonitor monitor, String packageName, int userId) {
-        this.monitor = monitor;
+    public PackagesChangedEvent(String packageName, int userId) {
         this.packageName = packageName;
         this.userId = userId;
     }
diff --git a/com/android/systemui/recents/events/activity/TaskStackUpdatedEvent.java b/com/android/systemui/recents/events/activity/TaskStackUpdatedEvent.java
index 0d614e8..51d02b5 100644
--- a/com/android/systemui/recents/events/activity/TaskStackUpdatedEvent.java
+++ b/com/android/systemui/recents/events/activity/TaskStackUpdatedEvent.java
@@ -17,7 +17,7 @@
 package com.android.systemui.recents.events.activity;
 
 import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.model.TaskStack;
+import com.android.systemui.shared.recents.model.TaskStack;
 
 /**
  * This is sent by the activity whenever the task stach has changed.
diff --git a/com/android/systemui/recents/events/ui/DeleteTaskDataEvent.java b/com/android/systemui/recents/events/ui/DeleteTaskDataEvent.java
index 4ed0270..b52e83b 100644
--- a/com/android/systemui/recents/events/ui/DeleteTaskDataEvent.java
+++ b/com/android/systemui/recents/events/ui/DeleteTaskDataEvent.java
@@ -17,7 +17,7 @@
 package com.android.systemui.recents.events.ui;
 
 import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.model.Task;
+import com.android.systemui.shared.recents.model.Task;
 
 /**
  * This is sent when the data associated with a given {@link Task} should be deleted from the
diff --git a/com/android/systemui/recents/events/ui/ShowApplicationInfoEvent.java b/com/android/systemui/recents/events/ui/ShowApplicationInfoEvent.java
index 40c30b8..da19384 100644
--- a/com/android/systemui/recents/events/ui/ShowApplicationInfoEvent.java
+++ b/com/android/systemui/recents/events/ui/ShowApplicationInfoEvent.java
@@ -17,7 +17,7 @@
 package com.android.systemui.recents.events.ui;
 
 import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.model.Task;
+import com.android.systemui.shared.recents.model.Task;
 
 /**
  * This is sent when a user wants to show the application info for a {@link Task}.
diff --git a/com/android/systemui/recents/events/ui/TaskSnapshotChangedEvent.java b/com/android/systemui/recents/events/ui/TaskSnapshotChangedEvent.java
index e0ed7a9..f082928 100644
--- a/com/android/systemui/recents/events/ui/TaskSnapshotChangedEvent.java
+++ b/com/android/systemui/recents/events/ui/TaskSnapshotChangedEvent.java
@@ -17,7 +17,7 @@
 package com.android.systemui.recents.events.ui;
 
 import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.model.ThumbnailData;
+import com.android.systemui.shared.recents.model.ThumbnailData;
 
 /**
  * Sent when a task snapshot has changed.
diff --git a/com/android/systemui/recents/events/ui/TaskViewDismissedEvent.java b/com/android/systemui/recents/events/ui/TaskViewDismissedEvent.java
index 0628c50..881a64a 100644
--- a/com/android/systemui/recents/events/ui/TaskViewDismissedEvent.java
+++ b/com/android/systemui/recents/events/ui/TaskViewDismissedEvent.java
@@ -17,8 +17,8 @@
 package com.android.systemui.recents.events.ui;
 
 import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.model.Task;
-import com.android.systemui.recents.views.AnimationProps;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.utilities.AnimationProps;
 import com.android.systemui.recents.views.TaskView;
 
 /**
diff --git a/com/android/systemui/recents/events/ui/UpdateFreeformTaskViewVisibilityEvent.java b/com/android/systemui/recents/events/ui/UpdateFreeformTaskViewVisibilityEvent.java
deleted file mode 100644
index b42da9c..0000000
--- a/com/android/systemui/recents/events/ui/UpdateFreeformTaskViewVisibilityEvent.java
+++ /dev/null
@@ -1,31 +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.systemui.recents.events.ui;
-
-import com.android.systemui.recents.events.EventBus;
-
-/**
- * This is sent to update the visibility of all visible freeform task views.
- */
-public class UpdateFreeformTaskViewVisibilityEvent extends EventBus.Event {
-
-    public final boolean visible;
-
-    public UpdateFreeformTaskViewVisibilityEvent(boolean visible) {
-        this.visible = visible;
-    }
-}
diff --git a/com/android/systemui/recents/events/ui/dragndrop/DragDropTargetChangedEvent.java b/com/android/systemui/recents/events/ui/dragndrop/DragDropTargetChangedEvent.java
index 216be61..cf61b1e 100644
--- a/com/android/systemui/recents/events/ui/dragndrop/DragDropTargetChangedEvent.java
+++ b/com/android/systemui/recents/events/ui/dragndrop/DragDropTargetChangedEvent.java
@@ -17,7 +17,7 @@
 package com.android.systemui.recents.events.ui.dragndrop;
 
 import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.model.Task;
+import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.recents.views.DropTarget;
 
 /**
diff --git a/com/android/systemui/recents/events/ui/dragndrop/DragEndCancelledEvent.java b/com/android/systemui/recents/events/ui/dragndrop/DragEndCancelledEvent.java
index edd7995..297afc5 100644
--- a/com/android/systemui/recents/events/ui/dragndrop/DragEndCancelledEvent.java
+++ b/com/android/systemui/recents/events/ui/dragndrop/DragEndCancelledEvent.java
@@ -17,9 +17,8 @@
 package com.android.systemui.recents.events.ui.dragndrop;
 
 import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.model.Task;
-import com.android.systemui.recents.model.TaskStack;
-import com.android.systemui.recents.views.DropTarget;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.model.TaskStack;
 import com.android.systemui.recents.views.TaskView;
 
 /**
diff --git a/com/android/systemui/recents/events/ui/dragndrop/DragEndEvent.java b/com/android/systemui/recents/events/ui/dragndrop/DragEndEvent.java
index 73c282f..73cbde9 100644
--- a/com/android/systemui/recents/events/ui/dragndrop/DragEndEvent.java
+++ b/com/android/systemui/recents/events/ui/dragndrop/DragEndEvent.java
@@ -17,7 +17,7 @@
 package com.android.systemui.recents.events.ui.dragndrop;
 
 import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.model.Task;
+import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.recents.views.DropTarget;
 import com.android.systemui.recents.views.TaskView;
 
diff --git a/com/android/systemui/recents/events/ui/dragndrop/DragStartEvent.java b/com/android/systemui/recents/events/ui/dragndrop/DragStartEvent.java
index e57fa2d..021be77 100644
--- a/com/android/systemui/recents/events/ui/dragndrop/DragStartEvent.java
+++ b/com/android/systemui/recents/events/ui/dragndrop/DragStartEvent.java
@@ -19,7 +19,7 @@
 import android.graphics.Point;
 
 import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.model.Task;
+import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.recents.views.TaskView;
 
 /**
diff --git a/com/android/systemui/recents/events/ui/dragndrop/DragStartInitializeDropTargetsEvent.java b/com/android/systemui/recents/events/ui/dragndrop/DragStartInitializeDropTargetsEvent.java
index 7030729..64ba574 100644
--- a/com/android/systemui/recents/events/ui/dragndrop/DragStartInitializeDropTargetsEvent.java
+++ b/com/android/systemui/recents/events/ui/dragndrop/DragStartInitializeDropTargetsEvent.java
@@ -17,7 +17,7 @@
 package com.android.systemui.recents.events.ui.dragndrop;
 
 import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.model.Task;
+import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.recents.views.RecentsViewTouchHandler;
 import com.android.systemui.recents.views.TaskView;
 
diff --git a/com/android/systemui/recents/events/ui/focus/FocusNextTaskViewEvent.java b/com/android/systemui/recents/events/ui/focus/FocusNextTaskViewEvent.java
index a1e4957..171ab5e 100644
--- a/com/android/systemui/recents/events/ui/focus/FocusNextTaskViewEvent.java
+++ b/com/android/systemui/recents/events/ui/focus/FocusNextTaskViewEvent.java
@@ -22,10 +22,5 @@
  * Focuses the next task view in the stack.
  */
 public class FocusNextTaskViewEvent extends EventBus.Event {
-
-    public final int timerIndicatorDuration;
-
-    public FocusNextTaskViewEvent(int timerIndicatorDuration) {
-        this.timerIndicatorDuration = timerIndicatorDuration;
-    }
+    // Simple event
 }
diff --git a/com/android/systemui/recents/misc/NamedCounter.java b/com/android/systemui/recents/misc/NamedCounter.java
deleted file mode 100644
index ec3c39c..0000000
--- a/com/android/systemui/recents/misc/NamedCounter.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2014 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.misc;
-
-/**
- * Used to generate successive incremented names.
- */
-public class NamedCounter {
-
-    int mCount;
-    String mPrefix = "";
-    String mSuffix = "";
-
-    public NamedCounter(String prefix, String suffix) {
-        mPrefix = prefix;
-        mSuffix = suffix;
-    }
-
-    /** Returns the next name. */
-    public String nextName() {
-        String name = mPrefix + mCount + mSuffix;
-        mCount++;
-        return name;
-    }
-}
diff --git a/com/android/systemui/recents/misc/SystemServicesProxy.java b/com/android/systemui/recents/misc/SystemServicesProxy.java
index bddf9a5..87f24fd 100644
--- a/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -25,27 +25,20 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-import static android.provider.Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT;
 
-import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.ActivityManager.StackInfo;
-import android.app.ActivityManager.TaskSnapshot;
 import android.app.ActivityOptions;
 import android.app.AppGlobals;
 import android.app.IActivityManager;
-import android.app.KeyguardManager;
 import android.app.WindowConfiguration;
 import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
@@ -55,24 +48,18 @@
 import android.graphics.PorterDuff;
 import android.graphics.PorterDuffXfermode;
 import android.graphics.Rect;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.os.Handler;
 import android.os.IRemoteCallback;
-import android.os.Message;
+import android.os.Looper;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemProperties;
-import android.os.Trace;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
-import android.provider.Settings.Secure;
 import android.service.dreams.DreamService;
 import android.service.dreams.IDreamManager;
-import android.util.ArraySet;
-import android.util.IconDrawableFactory;
 import android.util.Log;
 import android.util.MutableBoolean;
 import android.view.Display;
@@ -89,19 +76,12 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.UiOffloadThread;
-import com.android.systemui.pip.tv.PipMenuActivity;
 import com.android.systemui.recents.Recents;
-import com.android.systemui.recents.RecentsDebugFlags;
 import com.android.systemui.recents.RecentsImpl;
-import com.android.systemui.recents.model.Task;
-import com.android.systemui.recents.model.ThumbnailData;
+import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.statusbar.policy.UserInfoController;
 
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Iterator;
 import java.util.List;
-import java.util.Random;
 
 /**
  * Acts as a shim around the real system services that we need to access data from, and provides
@@ -117,42 +97,32 @@
         sBitmapOptions.inPreferredConfig = Bitmap.Config.RGB_565;
     }
 
-    final static List<String> sRecentsBlacklist;
-    static {
-        sRecentsBlacklist = new ArrayList<>();
-        sRecentsBlacklist.add(PipMenuActivity.class.getName());
-    }
-
     private static SystemServicesProxy sSystemServicesProxy;
 
     AccessibilityManager mAccm;
     ActivityManager mAm;
     IActivityManager mIam;
     PackageManager mPm;
-    IconDrawableFactory mDrawableFactory;
     IPackageManager mIpm;
     private final IDreamManager mDreamManager;
     private final Context mContext;
     AssistUtils mAssistUtils;
     WindowManager mWm;
     IWindowManager mIwm;
-    KeyguardManager mKgm;
     UserManager mUm;
     Display mDisplay;
     String mRecentsPackage;
-    ComponentName mAssistComponent;
+    private TaskStackChangeListeners mTaskStackChangeListeners;
     private int mCurrentUserId;
 
     boolean mIsSafeMode;
-    boolean mHasFreeformWorkspaceSupport;
 
-    Bitmap mDummyIcon;
     int mDummyThumbnailWidth;
     int mDummyThumbnailHeight;
     Paint mBgProtectionPaint;
     Canvas mBgProtectionCanvas;
 
-    private final Handler mHandler = new H();
+    private final Handler mHandler = new Handler();
     private final Runnable mGcRunnable = new Runnable() {
         @Override
         public void run() {
@@ -163,144 +133,10 @@
 
     private final UiOffloadThread mUiOffloadThread = Dependency.get(UiOffloadThread.class);
 
-    /**
-     * An abstract class to track task stack changes.
-     * Classes should implement this instead of {@link android.app.ITaskStackListener}
-     * to reduce IPC calls from system services. These callbacks will be called on the main thread.
-     */
-    public abstract static class TaskStackListener {
-        /**
-         * NOTE: This call is made of the thread that the binder call comes in on.
-         */
-        public void onTaskStackChangedBackground() { }
-        public void onTaskStackChanged() { }
-        public void onTaskSnapshotChanged(int taskId, TaskSnapshot snapshot) { }
-        public void onActivityPinned(String packageName, int userId, int taskId, int stackId) { }
-        public void onActivityUnpinned() { }
-        public void onPinnedActivityRestartAttempt(boolean clearedTask) { }
-        public void onPinnedStackAnimationStarted() { }
-        public void onPinnedStackAnimationEnded() { }
-        public void onActivityForcedResizable(String packageName, int taskId, int reason) { }
-        public void onActivityDismissingDockedStack() { }
-        public void onActivityLaunchOnSecondaryDisplayFailed() { }
-        public void onTaskProfileLocked(int taskId, int userId) { }
-
-        /**
-         * Checks that the current user matches the user's SystemUI process. Since
-         * {@link android.app.ITaskStackListener} is not multi-user aware, handlers of
-         * TaskStackListener should make this call to verify that we don't act on events from other
-         * user's processes.
-         */
-        protected final boolean checkCurrentUserId(Context context, boolean debug) {
-            int processUserId = UserHandle.myUserId();
-            int currentUserId = SystemServicesProxy.getInstance(context).getCurrentUser();
-            if (processUserId != currentUserId) {
-                if (debug) {
-                    Log.d(TAG, "UID mismatch. SystemUI is running uid=" + processUserId
-                            + " and the current user is uid=" + currentUserId);
-                }
-                return false;
-            }
-            return true;
-        }
-    }
-
-    /**
-     * Implementation of {@link android.app.ITaskStackListener} to listen task stack changes from
-     * ActivityManagerService.
-     * This simply passes callbacks to listeners through {@link H}.
-     * */
-    private android.app.TaskStackListener mTaskStackListener = new android.app.TaskStackListener() {
-
-        private final List<SystemServicesProxy.TaskStackListener> mTmpListeners = new ArrayList<>();
-
-        @Override
-        public void onTaskStackChanged() throws RemoteException {
-            // Call the task changed callback for the non-ui thread listeners first
-            synchronized (mTaskStackListeners) {
-                mTmpListeners.clear();
-                mTmpListeners.addAll(mTaskStackListeners);
-            }
-            for (int i = mTmpListeners.size() - 1; i >= 0; i--) {
-                mTmpListeners.get(i).onTaskStackChangedBackground();
-            }
-
-            mHandler.removeMessages(H.ON_TASK_STACK_CHANGED);
-            mHandler.sendEmptyMessage(H.ON_TASK_STACK_CHANGED);
-        }
-
-        @Override
-        public void onActivityPinned(String packageName, int userId, int taskId, int stackId)
-                throws RemoteException {
-            mHandler.removeMessages(H.ON_ACTIVITY_PINNED);
-            mHandler.obtainMessage(H.ON_ACTIVITY_PINNED,
-                    new PinnedActivityInfo(packageName, userId, taskId, stackId)).sendToTarget();
-        }
-
-        @Override
-        public void onActivityUnpinned() throws RemoteException {
-            mHandler.removeMessages(H.ON_ACTIVITY_UNPINNED);
-            mHandler.sendEmptyMessage(H.ON_ACTIVITY_UNPINNED);
-        }
-
-        @Override
-        public void onPinnedActivityRestartAttempt(boolean clearedTask)
-                throws RemoteException{
-            mHandler.removeMessages(H.ON_PINNED_ACTIVITY_RESTART_ATTEMPT);
-            mHandler.obtainMessage(H.ON_PINNED_ACTIVITY_RESTART_ATTEMPT, clearedTask ? 1 : 0, 0)
-                    .sendToTarget();
-        }
-
-        @Override
-        public void onPinnedStackAnimationStarted() throws RemoteException {
-            mHandler.removeMessages(H.ON_PINNED_STACK_ANIMATION_STARTED);
-            mHandler.sendEmptyMessage(H.ON_PINNED_STACK_ANIMATION_STARTED);
-        }
-
-        @Override
-        public void onPinnedStackAnimationEnded() throws RemoteException {
-            mHandler.removeMessages(H.ON_PINNED_STACK_ANIMATION_ENDED);
-            mHandler.sendEmptyMessage(H.ON_PINNED_STACK_ANIMATION_ENDED);
-        }
-
-        @Override
-        public void onActivityForcedResizable(String packageName, int taskId, int reason)
-                throws RemoteException {
-            mHandler.obtainMessage(H.ON_ACTIVITY_FORCED_RESIZABLE, taskId, reason, packageName)
-                    .sendToTarget();
-        }
-
-        @Override
-        public void onActivityDismissingDockedStack() throws RemoteException {
-            mHandler.sendEmptyMessage(H.ON_ACTIVITY_DISMISSING_DOCKED_STACK);
-        }
-
-        @Override
-        public void onActivityLaunchOnSecondaryDisplayFailed() throws RemoteException {
-            mHandler.sendEmptyMessage(H.ON_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_FAILED);
-        }
-
-        @Override
-        public void onTaskProfileLocked(int taskId, int userId) {
-            mHandler.obtainMessage(H.ON_TASK_PROFILE_LOCKED, taskId, userId).sendToTarget();
-        }
-
-        @Override
-        public void onTaskSnapshotChanged(int taskId, TaskSnapshot snapshot)
-                throws RemoteException {
-            mHandler.obtainMessage(H.ON_TASK_SNAPSHOT_CHANGED, taskId, 0, snapshot).sendToTarget();
-        }
-    };
-
     private final UserInfoController.OnUserInfoChangedListener mOnUserInfoChangedListener =
             (String name, Drawable picture, String userAccount) ->
                     mCurrentUserId = mAm.getCurrentUser();
 
-    /**
-     * List of {@link TaskStackListener} registered from {@link #registerTaskStackListener}.
-     */
-    private List<TaskStackListener> mTaskStackListeners = new ArrayList<>();
-
     /** Private constructor */
     private SystemServicesProxy(Context context) {
         mContext = context.getApplicationContext();
@@ -308,23 +144,18 @@
         mAm = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
         mIam = ActivityManager.getService();
         mPm = context.getPackageManager();
-        mDrawableFactory = IconDrawableFactory.newInstance(context);
         mIpm = AppGlobals.getPackageManager();
         mAssistUtils = new AssistUtils(context);
         mWm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
         mIwm = WindowManagerGlobal.getWindowManagerService();
-        mKgm = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);
         mUm = UserManager.get(context);
         mDreamManager = IDreamManager.Stub.asInterface(
                 ServiceManager.checkService(DreamService.DREAM_SERVICE));
         mDisplay = mWm.getDefaultDisplay();
         mRecentsPackage = context.getPackageName();
-        mHasFreeformWorkspaceSupport =
-                mPm.hasSystemFeature(PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT) ||
-                        Settings.Global.getInt(context.getContentResolver(),
-                                DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT, 0) != 0;
         mIsSafeMode = mPm.isSafeMode();
         mCurrentUserId = mAm.getCurrentUser();
+        mTaskStackChangeListeners = new TaskStackChangeListeners(Looper.getMainLooper());
 
         // Get the dummy thumbnail width/heights
         Resources res = context.getResources();
@@ -339,23 +170,11 @@
         mBgProtectionPaint.setColor(0xFFffffff);
         mBgProtectionCanvas = new Canvas();
 
-        // Resolve the assist intent
-        mAssistComponent = mAssistUtils.getAssistComponentForUser(UserHandle.myUserId());
-
         // Since SystemServicesProxy can be accessed from a per-SysUI process component, create a
         // per-process listener to keep track of the current user id to reduce the number of binder
         // calls to fetch it.
         UserInfoController userInfoController = Dependency.get(UserInfoController.class);
         userInfoController.addCallback(mOnUserInfoChangedListener);
-
-        if (RecentsDebugFlags.Static.EnableMockTasks) {
-            // Create a dummy icon
-            mDummyIcon = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
-            mDummyIcon.eraseColor(0xFF999999);
-        }
-
-        Collections.addAll(sRecentsBlacklist,
-                res.getStringArray(R.array.recents_blacklist_array));
     }
 
     /**
@@ -377,110 +196,6 @@
     }
 
     /**
-     * @return whether the provided {@param className} is blacklisted
-     */
-    public boolean isBlackListedActivity(String className) {
-        return sRecentsBlacklist.contains(className);
-    }
-
-    /**
-     * Returns a list of the recents tasks.
-     *
-     * @param includeFrontMostExcludedTask if set, will ensure that the front most excluded task
-     *                                     will be visible, otherwise no excluded tasks will be
-     *                                     visible.
-     */
-    public List<ActivityManager.RecentTaskInfo> getRecentTasks(int numLatestTasks, int userId,
-            boolean includeFrontMostExcludedTask, ArraySet<Integer> quietProfileIds) {
-        if (mAm == null) return null;
-
-        // If we are mocking, then create some recent tasks
-        if (RecentsDebugFlags.Static.EnableMockTasks) {
-            ArrayList<ActivityManager.RecentTaskInfo> tasks =
-                    new ArrayList<ActivityManager.RecentTaskInfo>();
-            int count = Math.min(numLatestTasks, RecentsDebugFlags.Static.MockTaskCount);
-            for (int i = 0; i < count; i++) {
-                // Create a dummy component name
-                int packageIndex = i % RecentsDebugFlags.Static.MockTasksPackageCount;
-                ComponentName cn = new ComponentName("com.android.test" + packageIndex,
-                        "com.android.test" + i + ".Activity");
-                String description = "" + i + " - " +
-                        Long.toString(Math.abs(new Random().nextLong()), 36);
-                // Create the recent task info
-                ActivityManager.RecentTaskInfo rti = new ActivityManager.RecentTaskInfo();
-                rti.id = rti.persistentId = rti.affiliatedTaskId = i;
-                rti.baseIntent = new Intent();
-                rti.baseIntent.setComponent(cn);
-                rti.description = description;
-                rti.firstActiveTime = rti.lastActiveTime = i;
-                if (i % 2 == 0) {
-                    rti.taskDescription = new ActivityManager.TaskDescription(description,
-                            Bitmap.createBitmap(mDummyIcon), null,
-                            0xFF000000 | (0xFFFFFF & new Random().nextInt()),
-                            0xFF000000 | (0xFFFFFF & new Random().nextInt()),
-                            0, 0);
-                } else {
-                    rti.taskDescription = new ActivityManager.TaskDescription();
-                }
-                tasks.add(rti);
-            }
-            return tasks;
-        }
-
-        // Remove home/recents/excluded tasks
-        int minNumTasksToQuery = 10;
-        int numTasksToQuery = Math.max(minNumTasksToQuery, numLatestTasks);
-        int flags = ActivityManager.RECENT_IGNORE_HOME_AND_RECENTS_STACK_TASKS |
-                ActivityManager.RECENT_INGORE_DOCKED_STACK_TOP_TASK |
-                ActivityManager.RECENT_INGORE_PINNED_STACK_TASKS |
-                ActivityManager.RECENT_IGNORE_UNAVAILABLE |
-                ActivityManager.RECENT_INCLUDE_PROFILES;
-        if (includeFrontMostExcludedTask) {
-            flags |= ActivityManager.RECENT_WITH_EXCLUDED;
-        }
-        List<ActivityManager.RecentTaskInfo> tasks = null;
-        try {
-            tasks = mAm.getRecentTasksForUser(numTasksToQuery, flags, userId);
-        } catch (Exception e) {
-            Log.e(TAG, "Failed to get recent tasks", e);
-        }
-
-        // Break early if we can't get a valid set of tasks
-        if (tasks == null) {
-            return new ArrayList<>();
-        }
-
-        boolean isFirstValidTask = true;
-        Iterator<ActivityManager.RecentTaskInfo> iter = tasks.iterator();
-        while (iter.hasNext()) {
-            ActivityManager.RecentTaskInfo t = iter.next();
-
-            // NOTE: The order of these checks happens in the expected order of the traversal of the
-            // tasks
-
-            // Remove the task if it or it's package are blacklsited
-            if (sRecentsBlacklist.contains(t.realActivity.getClassName()) ||
-                    sRecentsBlacklist.contains(t.realActivity.getPackageName())) {
-                iter.remove();
-                continue;
-            }
-
-            // Remove the task if it is marked as excluded, unless it is the first most task and we
-            // are requested to include it
-            boolean isExcluded = (t.baseIntent.getFlags() & Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
-                    == Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
-            isExcluded |= quietProfileIds.contains(t.userId);
-            if (isExcluded && (!isFirstValidTask || !includeFrontMostExcludedTask)) {
-                iter.remove();
-            }
-
-            isFirstValidTask = false;
-        }
-
-        return tasks.subList(0, Math.min(tasks.size(), numLatestTasks));
-    }
-
-    /**
      * Returns the top running task.
      */
     public ActivityManager.RunningTaskInfo getRunningTask() {
@@ -572,13 +287,6 @@
     }
 
     /**
-     * Returns whether this device has freeform workspaces.
-     */
-    public boolean hasFreeformWorkspaceSupport() {
-        return mHasFreeformWorkspaceSupport;
-    }
-
-    /**
      * Returns whether this device is in the safe mode.
      */
     public boolean isInSafeMode() {
@@ -646,7 +354,7 @@
      */
     public boolean hasSoftNavigationBar() {
         try {
-            return WindowManagerGlobal.getWindowManagerService().hasNavigationBar();
+            return mIwm.hasNavigationBar();
         } catch (RemoteException e) {
             e.printStackTrace();
         }
@@ -689,43 +397,6 @@
         }
     }
 
-    /** Returns the top task thumbnail for the given task id */
-    public ThumbnailData getTaskThumbnail(int taskId, boolean reduced) {
-        if (mAm == null) return null;
-
-        // If we are mocking, then just return a dummy thumbnail
-        if (RecentsDebugFlags.Static.EnableMockTasks) {
-            ThumbnailData thumbnailData = new ThumbnailData();
-            thumbnailData.thumbnail = Bitmap.createBitmap(mDummyThumbnailWidth,
-                    mDummyThumbnailHeight, Bitmap.Config.ARGB_8888);
-            thumbnailData.thumbnail.eraseColor(0xff333333);
-            return thumbnailData;
-        }
-
-        return getThumbnail(taskId, reduced);
-    }
-
-    /**
-     * Returns a task thumbnail from the activity manager
-     */
-    public @NonNull ThumbnailData getThumbnail(int taskId, boolean reducedResolution) {
-        if (mAm == null) {
-            return new ThumbnailData();
-        }
-
-        ActivityManager.TaskSnapshot snapshot = null;
-        try {
-            snapshot = ActivityManager.getService().getTaskSnapshot(taskId, reducedResolution);
-        } catch (RemoteException e) {
-            Log.w(TAG, "Failed to retrieve snapshot", e);
-        }
-        if (snapshot != null) {
-            return ThumbnailData.createFromTaskSnapshot(snapshot);
-        } else {
-            return new ThumbnailData();
-        }
-    }
-
     /** Set the task's windowing mode. */
     public void setTaskWindowingMode(int taskId, int windowingMode) {
         if (mIam == null) return;
@@ -740,11 +411,14 @@
     /** Removes the task */
     public void removeTask(final int taskId) {
         if (mAm == null) return;
-        if (RecentsDebugFlags.Static.EnableMockTasks) return;
 
         // Remove the task.
         mUiOffloadThread.submit(() -> {
-            mAm.removeTask(taskId);
+            try {
+                mIam.removeTask(taskId);
+            } catch (RemoteException e) {
+                e.printStackTrace();
+            }
         });
     }
 
@@ -760,145 +434,6 @@
         });
     }
 
-    /**
-     * Returns the activity info for a given component name.
-     *
-     * @param cn The component name of the activity.
-     * @param userId The userId of the user that this is for.
-     */
-    public ActivityInfo getActivityInfo(ComponentName cn, int userId) {
-        if (mIpm == null) return null;
-        if (RecentsDebugFlags.Static.EnableMockTasks) return new ActivityInfo();
-
-        try {
-            return mIpm.getActivityInfo(cn, PackageManager.GET_META_DATA, userId);
-        } catch (RemoteException e) {
-            e.printStackTrace();
-            return null;
-        }
-    }
-
-    /**
-     * Returns the activity info for a given component name.
-     *
-     * @param cn The component name of the activity.
-     */
-    public ActivityInfo getActivityInfo(ComponentName cn) {
-        if (mPm == null) return null;
-        if (RecentsDebugFlags.Static.EnableMockTasks) return new ActivityInfo();
-
-        try {
-            return mPm.getActivityInfo(cn, PackageManager.GET_META_DATA);
-        } catch (PackageManager.NameNotFoundException e) {
-            e.printStackTrace();
-            return null;
-        }
-    }
-
-    /**
-     * Returns the activity label, badging if necessary.
-     */
-    public String getBadgedActivityLabel(ActivityInfo info, int userId) {
-        if (mPm == null) return null;
-
-        // If we are mocking, then return a mock label
-        if (RecentsDebugFlags.Static.EnableMockTasks) {
-            return "Recent Task: " + userId;
-        }
-
-        return getBadgedLabel(info.loadLabel(mPm).toString(), userId);
-    }
-
-    /**
-     * Returns the application label, badging if necessary.
-     */
-    public String getBadgedApplicationLabel(ApplicationInfo appInfo, int userId) {
-        if (mPm == null) return null;
-
-        // If we are mocking, then return a mock label
-        if (RecentsDebugFlags.Static.EnableMockTasks) {
-            return "Recent Task App: " + userId;
-        }
-
-        return getBadgedLabel(appInfo.loadLabel(mPm).toString(), userId);
-    }
-
-    /**
-     * Returns the content description for a given task, badging it if necessary.  The content
-     * description joins the app and activity labels.
-     */
-    public String getBadgedContentDescription(ActivityInfo info, int userId,
-            ActivityManager.TaskDescription td, Resources res) {
-        // If we are mocking, then return a mock label
-        if (RecentsDebugFlags.Static.EnableMockTasks) {
-            return "Recent Task Content Description: " + userId;
-        }
-
-        String activityLabel;
-        if (td != null && td.getLabel() != null) {
-            activityLabel = td.getLabel();
-        } else {
-            activityLabel = info.loadLabel(mPm).toString();
-        }
-        String applicationLabel = info.applicationInfo.loadLabel(mPm).toString();
-        String badgedApplicationLabel = getBadgedLabel(applicationLabel, userId);
-        return applicationLabel.equals(activityLabel) ? badgedApplicationLabel
-                : res.getString(R.string.accessibility_recents_task_header,
-                        badgedApplicationLabel, activityLabel);
-    }
-
-    /**
-     * Returns the activity icon for the ActivityInfo for a user, badging if
-     * necessary.
-     */
-    public Drawable getBadgedActivityIcon(ActivityInfo info, int userId) {
-        if (mPm == null) return null;
-
-        // If we are mocking, then return a mock label
-        if (RecentsDebugFlags.Static.EnableMockTasks) {
-            return new ColorDrawable(0xFF666666);
-        }
-
-        return mDrawableFactory.getBadgedIcon(info, info.applicationInfo, userId);
-    }
-
-    /**
-     * Returns the application icon for the ApplicationInfo for a user, badging if
-     * necessary.
-     */
-    public Drawable getBadgedApplicationIcon(ApplicationInfo appInfo, int userId) {
-        if (mPm == null) return null;
-
-        // If we are mocking, then return a mock label
-        if (RecentsDebugFlags.Static.EnableMockTasks) {
-            return new ColorDrawable(0xFF666666);
-        }
-
-        return mDrawableFactory.getBadgedIcon(appInfo, userId);
-    }
-
-    /**
-     * Returns the task description icon, loading and badging it if it necessary.
-     */
-    public Drawable getBadgedTaskDescriptionIcon(ActivityManager.TaskDescription taskDescription,
-            int userId, Resources res) {
-
-        // If we are mocking, then return a mock label
-        if (RecentsDebugFlags.Static.EnableMockTasks) {
-            return new ColorDrawable(0xFF666666);
-        }
-
-        Bitmap tdIcon = taskDescription.getInMemoryIcon();
-        if (tdIcon == null) {
-            tdIcon = ActivityManager.TaskDescription.loadTaskDescriptionIcon(
-                    taskDescription.getIconFilename(), userId);
-        }
-        if (tdIcon != null) {
-            return getBadgedIcon(new BitmapDrawable(res, tdIcon), userId);
-        }
-        return null;
-    }
-
     public ActivityManager.TaskDescription getTaskDescription(int taskId) {
         try {
             return mIam.getTaskDescription(taskId);
@@ -908,85 +443,6 @@
     }
 
     /**
-     * Returns the given icon for a user, badging if necessary.
-     */
-    private Drawable getBadgedIcon(Drawable icon, int userId) {
-        if (userId != UserHandle.myUserId()) {
-            icon = mPm.getUserBadgedIcon(icon, new UserHandle(userId));
-        }
-        return icon;
-    }
-
-    /**
-     * Returns a banner used on TV for the specified Activity.
-     */
-    public Drawable getActivityBanner(ActivityInfo info) {
-        if (mPm == null) return null;
-
-        // If we are mocking, then return a mock banner
-        if (RecentsDebugFlags.Static.EnableMockTasks) {
-            return new ColorDrawable(0xFF666666);
-        }
-
-        Drawable banner = info.loadBanner(mPm);
-        return banner;
-    }
-
-    /**
-     * Returns a logo used on TV for the specified Activity.
-     */
-    public Drawable getActivityLogo(ActivityInfo info) {
-        if (mPm == null) return null;
-
-        // If we are mocking, then return a mock logo
-        if (RecentsDebugFlags.Static.EnableMockTasks) {
-            return new ColorDrawable(0xFF666666);
-        }
-
-        Drawable logo = info.loadLogo(mPm);
-        return logo;
-    }
-
-
-    /**
-     * Returns the given label for a user, badging if necessary.
-     */
-    private String getBadgedLabel(String label, int userId) {
-        if (userId != UserHandle.myUserId()) {
-            label = mPm.getUserBadgedLabel(label, new UserHandle(userId)).toString();
-        }
-        return label;
-    }
-
-    /**
-     * Returns whether the provided {@param userId} is currently locked (and showing Keyguard).
-     */
-    public boolean isDeviceLocked(int userId) {
-        if (mKgm == null) {
-            return false;
-        }
-        return mKgm.isDeviceLocked(userId);
-    }
-
-    /** Returns the package name of the home activity. */
-    public String getHomeActivityPackageName() {
-        if (mPm == null) return null;
-        if (RecentsDebugFlags.Static.EnableMockTasks) return null;
-
-        ArrayList<ResolveInfo> homeActivities = new ArrayList<>();
-        ComponentName defaultHomeActivity = mPm.getHomeActivities(homeActivities);
-        if (defaultHomeActivity != null) {
-            return defaultHomeActivity.getPackageName();
-        } else if (homeActivities.size() == 1) {
-            ResolveInfo info = homeActivities.get(0);
-            if (info.activityInfo != null) {
-                return info.activityInfo.packageName;
-            }
-        }
-        return null;
-    }
-
-    /**
      * Returns whether the provided {@param userId} represents the system user.
      */
     public boolean isSystemUser(int userId) {
@@ -1091,7 +547,7 @@
             ActivityManager.StackInfo stackInfo =
                     mIam.getStackInfo(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_RECENTS);
             if (stackInfo == null) {
-                stackInfo = mIam.getStackInfo(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD);
+                stackInfo = mIam.getStackInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
             }
             if (stackInfo != null) {
                 windowRect.set(stackInfo.bounds);
@@ -1174,19 +630,11 @@
      * Registers a task stack listener with the system.
      * This should be called on the main thread.
      */
-    public void registerTaskStackListener(TaskStackListener listener) {
+    public void registerTaskStackListener(TaskStackChangeListener listener) {
         if (mIam == null) return;
 
-        synchronized (mTaskStackListeners) {
-            mTaskStackListeners.add(listener);
-            if (mTaskStackListeners.size() == 1) {
-                // Register mTaskStackListener to IActivityManager only once if needed.
-                try {
-                    mIam.registerTaskStackListener(mTaskStackListener);
-                } catch (Exception e) {
-                    Log.w(TAG, "Failed to call registerTaskStackListener", e);
-                }
-            }
+        synchronized (mTaskStackChangeListeners) {
+            mTaskStackChangeListeners.addListener(mIam, listener);
         }
     }
 
@@ -1195,7 +643,7 @@
             return;
         }
         try {
-            WindowManagerGlobal.getWindowManagerService().endProlongedAnimations();
+            mIwm.endProlongedAnimations();
         } catch (Exception e) {
             e.printStackTrace();
         }
@@ -1205,7 +653,7 @@
         if (mWm == null) return;
 
         try {
-            WindowManagerGlobal.getWindowManagerService().registerDockedStackListener(listener);
+            mIwm.registerDockedStackListener(listener);
         } catch (Exception e) {
             e.printStackTrace();
         }
@@ -1232,8 +680,7 @@
         if (mWm == null) return;
 
         try {
-            WindowManagerGlobal.getWindowManagerService().getStableInsets(Display.DEFAULT_DISPLAY,
-                    outStableInsets);
+            mIwm.getStableInsets(Display.DEFAULT_DISPLAY, outStableInsets);
         } catch (Exception e) {
             e.printStackTrace();
         }
@@ -1243,9 +690,7 @@
             IAppTransitionAnimationSpecsFuture future, IRemoteCallback animStartedListener,
             boolean scaleUp) {
         try {
-            WindowManagerGlobal.getWindowManagerService()
-                    .overridePendingAppTransitionMultiThumbFuture(future, animStartedListener,
-                            scaleUp);
+            mIwm.overridePendingAppTransitionMultiThumbFuture(future, animStartedListener, scaleUp);
         } catch (RemoteException e) {
             Log.w(TAG, "Failed to override transition: " + e);
         }
@@ -1254,23 +699,27 @@
     /**
      * Updates the visibility of recents.
      */
-    public void setRecentsVisibility(boolean visible) {
-        try {
-            mIwm.setRecentsVisibility(visible);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Unable to reach window manager", e);
-        }
+    public void setRecentsVisibility(final boolean visible) {
+        mUiOffloadThread.submit(() -> {
+            try {
+                mIwm.setRecentsVisibility(visible);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Unable to reach window manager", e);
+            }
+        });
     }
 
     /**
      * Updates the visibility of the picture-in-picture.
      */
-    public void setPipVisibility(boolean visible) {
-        try {
-            mIwm.setPipVisibility(visible);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Unable to reach window manager", e);
-        }
+    public void setPipVisibility(final boolean visible) {
+        mUiOffloadThread.submit(() -> {
+            try {
+                mIwm.setPipVisibility(visible);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Unable to reach window manager", e);
+            }
+        });
     }
 
     public boolean isDreaming() {
@@ -1292,126 +741,7 @@
         });
     }
 
-    public void updateOverviewLastStackActiveTimeAsync(long newLastStackActiveTime,
-            int currentUserId) {
-        mUiOffloadThread.submit(() -> {
-            Settings.Secure.putLongForUser(mContext.getContentResolver(),
-                    Secure.OVERVIEW_LAST_STACK_ACTIVE_TIME, newLastStackActiveTime, currentUserId);
-        });
-    }
-
     public interface StartActivityFromRecentsResultListener {
         void onStartActivityResult(boolean succeeded);
     }
-
-    private class PinnedActivityInfo {
-        final String mPackageName;
-        final int mUserId;
-        final int mTaskId;
-        final int mStackId;
-
-        PinnedActivityInfo(String packageName, int userId, int taskId, int stackId) {
-            mPackageName = packageName;
-            mUserId = userId;
-            mTaskId = taskId;
-            mStackId = stackId;
-        }
-    }
-
-    private final class H extends Handler {
-        private static final int ON_TASK_STACK_CHANGED = 1;
-        private static final int ON_TASK_SNAPSHOT_CHANGED = 2;
-        private static final int ON_ACTIVITY_PINNED = 3;
-        private static final int ON_PINNED_ACTIVITY_RESTART_ATTEMPT = 4;
-        private static final int ON_PINNED_STACK_ANIMATION_ENDED = 5;
-        private static final int ON_ACTIVITY_FORCED_RESIZABLE = 6;
-        private static final int ON_ACTIVITY_DISMISSING_DOCKED_STACK = 7;
-        private static final int ON_TASK_PROFILE_LOCKED = 8;
-        private static final int ON_PINNED_STACK_ANIMATION_STARTED = 9;
-        private static final int ON_ACTIVITY_UNPINNED = 10;
-        private static final int ON_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_FAILED = 11;
-
-        @Override
-        public void handleMessage(Message msg) {
-            synchronized (mTaskStackListeners) {
-                switch (msg.what) {
-                    case ON_TASK_STACK_CHANGED: {
-                    Trace.beginSection("onTaskStackChanged");
-                        for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
-                            mTaskStackListeners.get(i).onTaskStackChanged();
-                        }
-                    Trace.endSection();
-                        break;
-                    }
-                    case ON_TASK_SNAPSHOT_CHANGED: {
-                    Trace.beginSection("onTaskSnapshotChanged");
-                        for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
-                            mTaskStackListeners.get(i).onTaskSnapshotChanged(msg.arg1,
-                                    (TaskSnapshot) msg.obj);
-                        }
-                    Trace.endSection();
-                        break;
-                    }
-                    case ON_ACTIVITY_PINNED: {
-                        final PinnedActivityInfo info = (PinnedActivityInfo) msg.obj;
-                        for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
-                            mTaskStackListeners.get(i).onActivityPinned(
-                                    info.mPackageName, info.mUserId, info.mTaskId, info.mStackId);
-                        }
-                        break;
-                    }
-                    case ON_ACTIVITY_UNPINNED: {
-                        for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
-                            mTaskStackListeners.get(i).onActivityUnpinned();
-                        }
-                        break;
-                    }
-                    case ON_PINNED_ACTIVITY_RESTART_ATTEMPT: {
-                        for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
-                            mTaskStackListeners.get(i).onPinnedActivityRestartAttempt(
-                                    msg.arg1 != 0);
-                        }
-                        break;
-                    }
-                    case ON_PINNED_STACK_ANIMATION_STARTED: {
-                        for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
-                            mTaskStackListeners.get(i).onPinnedStackAnimationStarted();
-                        }
-                        break;
-                    }
-                    case ON_PINNED_STACK_ANIMATION_ENDED: {
-                        for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
-                            mTaskStackListeners.get(i).onPinnedStackAnimationEnded();
-                        }
-                        break;
-                    }
-                    case ON_ACTIVITY_FORCED_RESIZABLE: {
-                        for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
-                            mTaskStackListeners.get(i).onActivityForcedResizable(
-                                    (String) msg.obj, msg.arg1, msg.arg2);
-                        }
-                        break;
-                    }
-                    case ON_ACTIVITY_DISMISSING_DOCKED_STACK: {
-                        for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
-                            mTaskStackListeners.get(i).onActivityDismissingDockedStack();
-                        }
-                        break;
-                    }
-                    case ON_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_FAILED: {
-                        for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
-                            mTaskStackListeners.get(i).onActivityLaunchOnSecondaryDisplayFailed();
-                        }
-                        break;
-                    }
-                    case ON_TASK_PROFILE_LOCKED: {
-                        for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
-                            mTaskStackListeners.get(i).onTaskProfileLocked(msg.arg1, msg.arg2);
-                        }
-                        break;
-                    }
-                }
-            }
-        }
-    }
 }
diff --git a/com/android/systemui/recents/misc/TaskStackChangeListener.java b/com/android/systemui/recents/misc/TaskStackChangeListener.java
new file mode 100644
index 0000000..6d0952a
--- /dev/null
+++ b/com/android/systemui/recents/misc/TaskStackChangeListener.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2017 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.misc;
+
+import android.app.ActivityManager.TaskSnapshot;
+import android.content.Context;
+import android.os.UserHandle;
+import android.util.Log;
+
+/**
+ * An abstract class to track task stack changes.
+ * Classes should implement this instead of {@link android.app.ITaskStackListener}
+ * to reduce IPC calls from system services. These callbacks will be called on the main thread.
+ */
+public abstract class TaskStackChangeListener {
+
+    /**
+     * NOTE: This call is made of the thread that the binder call comes in on.
+     */
+    public void onTaskStackChangedBackground() { }
+    public void onTaskStackChanged() { }
+    public void onTaskSnapshotChanged(int taskId, TaskSnapshot snapshot) { }
+    public void onActivityPinned(String packageName, int userId, int taskId, int stackId) { }
+    public void onActivityUnpinned() { }
+    public void onPinnedActivityRestartAttempt(boolean clearedTask) { }
+    public void onPinnedStackAnimationStarted() { }
+    public void onPinnedStackAnimationEnded() { }
+    public void onActivityForcedResizable(String packageName, int taskId, int reason) { }
+    public void onActivityDismissingDockedStack() { }
+    public void onActivityLaunchOnSecondaryDisplayFailed() { }
+    public void onTaskProfileLocked(int taskId, int userId) { }
+
+    /**
+     * Checks that the current user matches the user's SystemUI process. Since
+     * {@link android.app.ITaskStackListener} is not multi-user aware, handlers of
+     * TaskStackChangeListener should make this call to verify that we don't act on events from other
+     * user's processes.
+     */
+    protected final boolean checkCurrentUserId(Context context, boolean debug) {
+        int processUserId = UserHandle.myUserId();
+        int currentUserId = SystemServicesProxy.getInstance(context).getCurrentUser();
+        if (processUserId != currentUserId) {
+            if (debug) {
+                Log.d(SystemServicesProxy.TAG, "UID mismatch. SystemUI is running uid=" + processUserId
+                        + " and the current user is uid=" + currentUserId);
+            }
+            return false;
+        }
+        return true;
+    }
+}
diff --git a/com/android/systemui/recents/misc/TaskStackChangeListeners.java b/com/android/systemui/recents/misc/TaskStackChangeListeners.java
new file mode 100644
index 0000000..8eb70f0
--- /dev/null
+++ b/com/android/systemui/recents/misc/TaskStackChangeListeners.java
@@ -0,0 +1,254 @@
+/*
+ * Copyright (C) 2017 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.misc;
+
+import android.app.ActivityManager.TaskSnapshot;
+import android.app.IActivityManager;
+import android.app.TaskStackListener;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.Trace;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Tracks all the task stack listeners
+ */
+public class TaskStackChangeListeners extends TaskStackListener {
+
+    private static final String TAG = TaskStackChangeListeners.class.getSimpleName();
+
+    /**
+     * List of {@link TaskStackChangeListener} registered from {@link #addListener}.
+     */
+    private final List<TaskStackChangeListener> mTaskStackListeners = new ArrayList<>();
+    private final List<TaskStackChangeListener> mTmpListeners = new ArrayList<>();
+
+    private final Handler mHandler;
+
+    public TaskStackChangeListeners(Looper looper) {
+        mHandler = new H(looper);
+    }
+
+    public void addListener(IActivityManager am, TaskStackChangeListener listener) {
+        mTaskStackListeners.add(listener);
+        if (mTaskStackListeners.size() == 1) {
+            // Register mTaskStackListener to IActivityManager only once if needed.
+            try {
+                am.registerTaskStackListener(this);
+            } catch (Exception e) {
+                Log.w(TAG, "Failed to call registerTaskStackListener", e);
+            }
+        }
+    }
+
+    @Override
+    public void onTaskStackChanged() throws RemoteException {
+        // Call the task changed callback for the non-ui thread listeners first
+        synchronized (mTaskStackListeners) {
+            mTmpListeners.clear();
+            mTmpListeners.addAll(mTaskStackListeners);
+        }
+        for (int i = mTmpListeners.size() - 1; i >= 0; i--) {
+            mTmpListeners.get(i).onTaskStackChangedBackground();
+        }
+
+        mHandler.removeMessages(H.ON_TASK_STACK_CHANGED);
+        mHandler.sendEmptyMessage(H.ON_TASK_STACK_CHANGED);
+    }
+
+    @Override
+    public void onActivityPinned(String packageName, int userId, int taskId, int stackId)
+            throws RemoteException {
+        mHandler.removeMessages(H.ON_ACTIVITY_PINNED);
+        mHandler.obtainMessage(H.ON_ACTIVITY_PINNED,
+                new PinnedActivityInfo(packageName, userId, taskId, stackId)).sendToTarget();
+    }
+
+    @Override
+    public void onActivityUnpinned() throws RemoteException {
+        mHandler.removeMessages(H.ON_ACTIVITY_UNPINNED);
+        mHandler.sendEmptyMessage(H.ON_ACTIVITY_UNPINNED);
+    }
+
+    @Override
+    public void onPinnedActivityRestartAttempt(boolean clearedTask)
+            throws RemoteException{
+        mHandler.removeMessages(H.ON_PINNED_ACTIVITY_RESTART_ATTEMPT);
+        mHandler.obtainMessage(H.ON_PINNED_ACTIVITY_RESTART_ATTEMPT, clearedTask ? 1 : 0, 0)
+                .sendToTarget();
+    }
+
+    @Override
+    public void onPinnedStackAnimationStarted() throws RemoteException {
+        mHandler.removeMessages(H.ON_PINNED_STACK_ANIMATION_STARTED);
+        mHandler.sendEmptyMessage(H.ON_PINNED_STACK_ANIMATION_STARTED);
+    }
+
+    @Override
+    public void onPinnedStackAnimationEnded() throws RemoteException {
+        mHandler.removeMessages(H.ON_PINNED_STACK_ANIMATION_ENDED);
+        mHandler.sendEmptyMessage(H.ON_PINNED_STACK_ANIMATION_ENDED);
+    }
+
+    @Override
+    public void onActivityForcedResizable(String packageName, int taskId, int reason)
+            throws RemoteException {
+        mHandler.obtainMessage(H.ON_ACTIVITY_FORCED_RESIZABLE, taskId, reason, packageName)
+                .sendToTarget();
+    }
+
+    @Override
+    public void onActivityDismissingDockedStack() throws RemoteException {
+        mHandler.sendEmptyMessage(H.ON_ACTIVITY_DISMISSING_DOCKED_STACK);
+    }
+
+    @Override
+    public void onActivityLaunchOnSecondaryDisplayFailed() throws RemoteException {
+        mHandler.sendEmptyMessage(H.ON_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_FAILED);
+    }
+
+    @Override
+    public void onTaskProfileLocked(int taskId, int userId) throws RemoteException {
+        mHandler.obtainMessage(H.ON_TASK_PROFILE_LOCKED, taskId, userId).sendToTarget();
+    }
+
+    @Override
+    public void onTaskSnapshotChanged(int taskId, TaskSnapshot snapshot)
+            throws RemoteException {
+        mHandler.obtainMessage(H.ON_TASK_SNAPSHOT_CHANGED, taskId, 0, snapshot).sendToTarget();
+    }
+
+    private final class H extends Handler {
+        private static final int ON_TASK_STACK_CHANGED = 1;
+        private static final int ON_TASK_SNAPSHOT_CHANGED = 2;
+        private static final int ON_ACTIVITY_PINNED = 3;
+        private static final int ON_PINNED_ACTIVITY_RESTART_ATTEMPT = 4;
+        private static final int ON_PINNED_STACK_ANIMATION_ENDED = 5;
+        private static final int ON_ACTIVITY_FORCED_RESIZABLE = 6;
+        private static final int ON_ACTIVITY_DISMISSING_DOCKED_STACK = 7;
+        private static final int ON_TASK_PROFILE_LOCKED = 8;
+        private static final int ON_PINNED_STACK_ANIMATION_STARTED = 9;
+        private static final int ON_ACTIVITY_UNPINNED = 10;
+        private static final int ON_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_FAILED = 11;
+
+        public H(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            synchronized (mTaskStackListeners) {
+                switch (msg.what) {
+                    case ON_TASK_STACK_CHANGED: {
+                        Trace.beginSection("onTaskStackChanged");
+                        for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
+                            mTaskStackListeners.get(i).onTaskStackChanged();
+                        }
+                        Trace.endSection();
+                        break;
+                    }
+                    case ON_TASK_SNAPSHOT_CHANGED: {
+                        Trace.beginSection("onTaskSnapshotChanged");
+                        for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
+                            mTaskStackListeners.get(i).onTaskSnapshotChanged(msg.arg1,
+                                    (TaskSnapshot) msg.obj);
+                        }
+                        Trace.endSection();
+                        break;
+                    }
+                    case ON_ACTIVITY_PINNED: {
+                        final PinnedActivityInfo info = (PinnedActivityInfo) msg.obj;
+                        for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
+                            mTaskStackListeners.get(i).onActivityPinned(
+                                    info.mPackageName, info.mUserId, info.mTaskId, info.mStackId);
+                        }
+                        break;
+                    }
+                    case ON_ACTIVITY_UNPINNED: {
+                        for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
+                            mTaskStackListeners.get(i).onActivityUnpinned();
+                        }
+                        break;
+                    }
+                    case ON_PINNED_ACTIVITY_RESTART_ATTEMPT: {
+                        for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
+                            mTaskStackListeners.get(i).onPinnedActivityRestartAttempt(
+                                    msg.arg1 != 0);
+                        }
+                        break;
+                    }
+                    case ON_PINNED_STACK_ANIMATION_STARTED: {
+                        for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
+                            mTaskStackListeners.get(i).onPinnedStackAnimationStarted();
+                        }
+                        break;
+                    }
+                    case ON_PINNED_STACK_ANIMATION_ENDED: {
+                        for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
+                            mTaskStackListeners.get(i).onPinnedStackAnimationEnded();
+                        }
+                        break;
+                    }
+                    case ON_ACTIVITY_FORCED_RESIZABLE: {
+                        for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
+                            mTaskStackListeners.get(i).onActivityForcedResizable(
+                                    (String) msg.obj, msg.arg1, msg.arg2);
+                        }
+                        break;
+                    }
+                    case ON_ACTIVITY_DISMISSING_DOCKED_STACK: {
+                        for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
+                            mTaskStackListeners.get(i).onActivityDismissingDockedStack();
+                        }
+                        break;
+                    }
+                    case ON_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_FAILED: {
+                        for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
+                            mTaskStackListeners.get(i).onActivityLaunchOnSecondaryDisplayFailed();
+                        }
+                        break;
+                    }
+                    case ON_TASK_PROFILE_LOCKED: {
+                        for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
+                            mTaskStackListeners.get(i).onTaskProfileLocked(msg.arg1, msg.arg2);
+                        }
+                        break;
+                    }
+                }
+            }
+        }
+    }
+
+    private static class PinnedActivityInfo {
+        final String mPackageName;
+        final int mUserId;
+        final int mTaskId;
+        final int mStackId;
+
+        PinnedActivityInfo(String packageName, int userId, int taskId, int stackId) {
+            mPackageName = packageName;
+            mUserId = userId;
+            mTaskId = taskId;
+            mStackId = stackId;
+        }
+    }
+}
diff --git a/com/android/systemui/recents/model/RecentsPackageMonitor.java b/com/android/systemui/recents/model/RecentsPackageMonitor.java
deleted file mode 100644
index 308cece..0000000
--- a/com/android/systemui/recents/model/RecentsPackageMonitor.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2014 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.model;
-
-import android.content.Context;
-import android.os.UserHandle;
-
-import com.android.internal.content.PackageMonitor;
-import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.events.activity.PackagesChangedEvent;
-import com.android.systemui.recents.misc.ForegroundThread;
-
-/**
- * The package monitor listens for changes from PackageManager to update the contents of the
- * Recents list.
- */
-public class RecentsPackageMonitor extends PackageMonitor {
-
-    /** Registers the broadcast receivers with the specified callbacks. */
-    public void register(Context context) {
-        try {
-            // We register for events from all users, but will cross-reference them with
-            // packages for the current user and any profiles they have.  Ensure that events are
-            // handled in a background thread.
-            register(context, ForegroundThread.get().getLooper(), UserHandle.ALL, true);
-        } catch (IllegalStateException e) {
-            e.printStackTrace();
-        }
-    }
-
-    /** Unregisters the broadcast receivers. */
-    @Override
-    public void unregister() {
-        try {
-            super.unregister();
-        } catch (IllegalStateException e) {
-            e.printStackTrace();
-        }
-    }
-
-    @Override
-    public void onPackageRemoved(String packageName, int uid) {
-        // Notify callbacks on the main thread that a package has changed
-        final int eventUserId = getChangingUserId();
-        EventBus.getDefault().post(new PackagesChangedEvent(this, packageName, eventUserId));
-    }
-
-    @Override
-    public boolean onPackageChanged(String packageName, int uid, String[] components) {
-        onPackageModified(packageName);
-        return true;
-    }
-
-    @Override
-    public void onPackageModified(String packageName) {
-        // Notify callbacks on the main thread that a package has changed
-        final int eventUserId = getChangingUserId();
-        EventBus.getDefault().post(new PackagesChangedEvent(this, packageName, eventUserId));
-    }
-}
diff --git a/com/android/systemui/recents/model/RecentsTaskLoadPlan.java b/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
deleted file mode 100644
index d5e0313..0000000
--- a/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
+++ /dev/null
@@ -1,330 +0,0 @@
-/*
- * Copyright (C) 2014 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.model;
-
-import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
-
-import android.app.ActivityManager;
-import android.content.Context;
-import android.content.pm.ActivityInfo;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.UserInfo;
-import android.content.res.Resources;
-import android.graphics.drawable.Drawable;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.provider.Settings;
-import android.provider.Settings.Secure;
-import android.util.ArraySet;
-import android.util.SparseArray;
-import android.util.SparseBooleanArray;
-import android.util.SparseIntArray;
-
-import com.android.systemui.Prefs;
-import com.android.systemui.R;
-import com.android.systemui.recents.Recents;
-import com.android.systemui.recents.RecentsDebugFlags;
-import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.views.lowram.TaskStackLowRamLayoutAlgorithm;
-import com.android.systemui.recents.views.grid.TaskGridLayoutAlgorithm;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-
-/**
- * This class stores the loading state as it goes through multiple stages of loading:
- *   1) preloadRawTasks() will load the raw set of recents tasks from the system
- *   2) preloadPlan() will construct a new task stack with all metadata and only icons and
- *      thumbnails that are currently in the cache
- *   3) executePlan() will actually load and fill in the icons and thumbnails according to the load
- *      options specified, such that we can transition into the Recents activity seamlessly
- */
-public class RecentsTaskLoadPlan {
-
-    private static int MIN_NUM_TASKS = 5;
-    private static int SESSION_BEGIN_TIME = 1000 /* ms/s */ * 60 /* s/min */ * 60 /* min/hr */ *
-            6 /* hrs */;
-
-    /** The set of conditions to load tasks. */
-    public static class Options {
-        public int runningTaskId = -1;
-        public boolean loadIcons = true;
-        public boolean loadThumbnails = false;
-        public boolean onlyLoadForCache = false;
-        public boolean onlyLoadPausedActivities = false;
-        public int numVisibleTasks = 0;
-        public int numVisibleTaskThumbnails = 0;
-    }
-
-    Context mContext;
-
-    int mPreloadedUserId;
-    List<ActivityManager.RecentTaskInfo> mRawTasks;
-    TaskStack mStack;
-    ArraySet<Integer> mCurrentQuietProfiles = new ArraySet<Integer>();
-
-    /** Package level ctor */
-    RecentsTaskLoadPlan(Context context) {
-        mContext = context;
-    }
-
-    private void updateCurrentQuietProfilesCache(int currentUserId) {
-        mCurrentQuietProfiles.clear();
-
-        UserManager userManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
-        List<UserInfo> profiles = userManager.getProfiles(currentUserId);
-        if (profiles != null) {
-            for (int i = 0; i < profiles.size(); i++) {
-                UserInfo user  = profiles.get(i);
-                if (user.isManagedProfile() && user.isQuietModeEnabled()) {
-                    mCurrentQuietProfiles.add(user.id);
-                }
-            }
-        }
-    }
-
-    /**
-     * An optimization to preload the raw list of tasks. The raw tasks are saved in least-recent
-     * to most-recent order.
-     *
-     * Note: Do not lock, callers should synchronize on the loader before making this call.
-     */
-    void preloadRawTasks(boolean includeFrontMostExcludedTask) {
-        SystemServicesProxy ssp = Recents.getSystemServices();
-        int currentUserId = ssp.getCurrentUser();
-        updateCurrentQuietProfilesCache(currentUserId);
-        mPreloadedUserId = currentUserId;
-        mRawTasks = ssp.getRecentTasks(ActivityManager.getMaxRecentTasksStatic(),
-                currentUserId, includeFrontMostExcludedTask, mCurrentQuietProfiles);
-
-        // Since the raw tasks are given in most-recent to least-recent order, we need to reverse it
-        Collections.reverse(mRawTasks);
-    }
-
-    /**
-     * Preloads the list of recent tasks from the system. After this call, the TaskStack will
-     * have a list of all the recent tasks with their metadata, not including icons or
-     * thumbnails which were not cached and have to be loaded.
-     *
-     * The tasks will be ordered by:
-     * - least-recent to most-recent stack tasks
-     * - least-recent to most-recent freeform tasks
-     *
-     * Note: Do not lock, since this can be calling back to the loader, which separately also drives
-     * this call (callers should synchronize on the loader before making this call).
-     */
-    void preloadPlan(RecentsTaskLoader loader, int runningTaskId,
-            boolean includeFrontMostExcludedTask) {
-        Resources res = mContext.getResources();
-        ArrayList<Task> allTasks = new ArrayList<>();
-        if (mRawTasks == null) {
-            preloadRawTasks(includeFrontMostExcludedTask);
-        }
-
-        SparseArray<Task.TaskKey> affiliatedTasks = new SparseArray<>();
-        SparseIntArray affiliatedTaskCounts = new SparseIntArray();
-        SparseBooleanArray lockedUsers = new SparseBooleanArray();
-        String dismissDescFormat = mContext.getString(
-                R.string.accessibility_recents_item_will_be_dismissed);
-        String appInfoDescFormat = mContext.getString(
-                R.string.accessibility_recents_item_open_app_info);
-        int currentUserId = mPreloadedUserId;
-        long legacyLastStackActiveTime = migrateLegacyLastStackActiveTime(currentUserId);
-        long lastStackActiveTime = Settings.Secure.getLongForUser(mContext.getContentResolver(),
-                Secure.OVERVIEW_LAST_STACK_ACTIVE_TIME, legacyLastStackActiveTime, currentUserId);
-        if (RecentsDebugFlags.Static.EnableMockTasks) {
-            lastStackActiveTime = 0;
-        }
-        long newLastStackActiveTime = -1;
-        int taskCount = mRawTasks.size();
-        for (int i = 0; i < taskCount; i++) {
-            ActivityManager.RecentTaskInfo t = mRawTasks.get(i);
-
-            // Compose the task key
-            final int windowingMode = t.configuration.windowConfiguration.getWindowingMode();
-            Task.TaskKey taskKey = new Task.TaskKey(t.persistentId, windowingMode, t.baseIntent,
-                    t.userId, t.firstActiveTime, t.lastActiveTime);
-
-            // This task is only shown in the stack if it satisfies the historical time or min
-            // number of tasks constraints. Freeform tasks are also always shown.
-            boolean isFreeformTask = windowingMode == WINDOWING_MODE_FREEFORM;
-            boolean isStackTask;
-            if (Recents.getConfiguration().isGridEnabled) {
-                // When grid layout is enabled, we only show the first
-                // TaskGridLayoutAlgorithm.MAX_LAYOUT_FROM_HOME_TASK_COUNT} tasks.
-                isStackTask = t.lastActiveTime >= lastStackActiveTime &&
-                    i >= taskCount - TaskGridLayoutAlgorithm.MAX_LAYOUT_TASK_COUNT;
-            } else if (Recents.getConfiguration().isLowRamDevice) {
-                // Show a max of 3 items
-                isStackTask = t.lastActiveTime >= lastStackActiveTime &&
-                        i >= taskCount - TaskStackLowRamLayoutAlgorithm.MAX_LAYOUT_TASK_COUNT;
-            } else {
-                isStackTask = isFreeformTask || !isHistoricalTask(t) ||
-                    (t.lastActiveTime >= lastStackActiveTime && i >= (taskCount - MIN_NUM_TASKS));
-            }
-            boolean isLaunchTarget = taskKey.id == runningTaskId;
-
-            // The last stack active time is the baseline for which we show visible tasks.  Since
-            // the system will store all the tasks, we don't want to show the tasks prior to the
-            // last visible ones, otherwise, as you dismiss them, the previous tasks may satisfy
-            // the other stack-task constraints.
-            if (isStackTask && newLastStackActiveTime < 0) {
-                newLastStackActiveTime = t.lastActiveTime;
-            }
-
-            // Load the title, icon, and color
-            ActivityInfo info = loader.getAndUpdateActivityInfo(taskKey);
-            String title = loader.getAndUpdateActivityTitle(taskKey, t.taskDescription);
-            String titleDescription = loader.getAndUpdateContentDescription(taskKey,
-                    t.taskDescription, res);
-            String dismissDescription = String.format(dismissDescFormat, titleDescription);
-            String appInfoDescription = String.format(appInfoDescFormat, titleDescription);
-            Drawable icon = isStackTask
-                    ? loader.getAndUpdateActivityIcon(taskKey, t.taskDescription, res, false)
-                    : null;
-            ThumbnailData thumbnail = loader.getAndUpdateThumbnail(taskKey,
-                    false /* loadIfNotCached */, false /* storeInCache */);
-            int activityColor = loader.getActivityPrimaryColor(t.taskDescription);
-            int backgroundColor = loader.getActivityBackgroundColor(t.taskDescription);
-            boolean isSystemApp = (info != null) &&
-                    ((info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0);
-            if (lockedUsers.indexOfKey(t.userId) < 0) {
-                lockedUsers.put(t.userId, Recents.getSystemServices().isDeviceLocked(t.userId));
-            }
-            boolean isLocked = lockedUsers.get(t.userId);
-
-            // Add the task to the stack
-            Task task = new Task(taskKey, t.affiliatedTaskId, t.affiliatedTaskColor, icon,
-                    thumbnail, title, titleDescription, dismissDescription, appInfoDescription,
-                    activityColor, backgroundColor, isLaunchTarget, isStackTask, isSystemApp,
-                    t.supportsSplitScreenMultiWindow, t.bounds, t.taskDescription, t.resizeMode, t.topActivity,
-                    isLocked);
-
-            allTasks.add(task);
-            affiliatedTaskCounts.put(taskKey.id, affiliatedTaskCounts.get(taskKey.id, 0) + 1);
-            affiliatedTasks.put(taskKey.id, taskKey);
-        }
-        if (newLastStackActiveTime != -1) {
-            Recents.getSystemServices().updateOverviewLastStackActiveTimeAsync(
-                    newLastStackActiveTime, currentUserId);
-        }
-
-        // Initialize the stacks
-        mStack = new TaskStack();
-        mStack.setTasks(mContext, allTasks, false /* notifyStackChanges */);
-    }
-
-    /**
-     * Called to apply the actual loading based on the specified conditions.
-     *
-     * Note: Do not lock, since this can be calling back to the loader, which separately also drives
-     * this call (callers should synchronize on the loader before making this call).
-     */
-    void executePlan(Options opts, RecentsTaskLoader loader) {
-        Resources res = mContext.getResources();
-
-        // Iterate through each of the tasks and load them according to the load conditions.
-        ArrayList<Task> tasks = mStack.getStackTasks();
-        int taskCount = tasks.size();
-        for (int i = 0; i < taskCount; i++) {
-            Task task = tasks.get(i);
-            Task.TaskKey taskKey = task.key;
-
-            boolean isRunningTask = (task.key.id == opts.runningTaskId);
-            boolean isVisibleTask = i >= (taskCount - opts.numVisibleTasks);
-            boolean isVisibleThumbnail = i >= (taskCount - opts.numVisibleTaskThumbnails);
-
-            // If requested, skip the running task
-            if (opts.onlyLoadPausedActivities && isRunningTask) {
-                continue;
-            }
-
-            if (opts.loadIcons && (isRunningTask || isVisibleTask)) {
-                if (task.icon == null) {
-                    task.icon = loader.getAndUpdateActivityIcon(taskKey, task.taskDescription, res,
-                            true);
-                }
-            }
-            if (opts.loadThumbnails && isVisibleThumbnail) {
-                task.thumbnail = loader.getAndUpdateThumbnail(taskKey,
-                        true /* loadIfNotCached */, true /* storeInCache */);
-            }
-        }
-    }
-
-    /**
-     * Returns the TaskStack from the preloaded list of recent tasks.
-     */
-    public TaskStack getTaskStack() {
-        return mStack;
-    }
-
-    /**
-     * Returns the raw list of recent tasks.
-     */
-    public List<ActivityManager.RecentTaskInfo> getRawTasks() {
-        return mRawTasks;
-    }
-
-    /** Returns whether there are any tasks in any stacks. */
-    public boolean hasTasks() {
-        if (mStack != null) {
-            return mStack.getTaskCount() > 0;
-        }
-        return false;
-    }
-
-    /**
-     * Returns whether this task is too old to be shown.
-     */
-    private boolean isHistoricalTask(ActivityManager.RecentTaskInfo t) {
-        return t.lastActiveTime < (System.currentTimeMillis() - SESSION_BEGIN_TIME);
-    }
-
-
-    /**
-     * Migrate the last active time from the prefs to the secure settings.
-     *
-     * The first time this runs, it will:
-     * 1) fetch the last stack active time from the prefs
-     * 2) set the prefs to the last stack active time for all users
-     * 3) clear the pref
-     * 4) return the last stack active time
-     *
-     * Subsequent calls to this will return zero.
-     */
-    private long migrateLegacyLastStackActiveTime(int currentUserId) {
-        long legacyLastStackActiveTime = Prefs.getLong(mContext,
-                Prefs.Key.OVERVIEW_LAST_STACK_TASK_ACTIVE_TIME, -1);
-        if (legacyLastStackActiveTime != -1) {
-            Prefs.remove(mContext, Prefs.Key.OVERVIEW_LAST_STACK_TASK_ACTIVE_TIME);
-            UserManager userMgr = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
-            List<UserInfo> users = userMgr.getUsers();
-            for (int i = 0; i < users.size(); i++) {
-                int userId = users.get(i).id;
-                if (userId != currentUserId) {
-                    Recents.getSystemServices().updateOverviewLastStackActiveTimeAsync(
-                            legacyLastStackActiveTime, userId);
-                }
-            }
-            return legacyLastStackActiveTime;
-        }
-        return 0;
-    }
-}
diff --git a/com/android/systemui/recents/model/RecentsTaskLoader.java b/com/android/systemui/recents/model/RecentsTaskLoader.java
deleted file mode 100644
index 1b89386..0000000
--- a/com/android/systemui/recents/model/RecentsTaskLoader.java
+++ /dev/null
@@ -1,663 +0,0 @@
-/*
- * Copyright (C) 2014 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.model;
-
-import android.app.ActivityManager;
-import android.content.ComponentCallbacks2;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.pm.ActivityInfo;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Looper;
-import android.os.Trace;
-import android.util.Log;
-import android.util.LruCache;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.systemui.R;
-import com.android.systemui.recents.Recents;
-import com.android.systemui.recents.RecentsConfiguration;
-import com.android.systemui.recents.RecentsDebugFlags;
-import com.android.systemui.recents.events.activity.PackagesChangedEvent;
-import com.android.systemui.recents.misc.SystemServicesProxy;
-
-import java.io.PrintWriter;
-import java.util.Map;
-import java.util.concurrent.ConcurrentLinkedQueue;
-
-
-/**
- * A Task load queue
- */
-class TaskResourceLoadQueue {
-
-    ConcurrentLinkedQueue<Task> mQueue = new ConcurrentLinkedQueue<Task>();
-
-    /** Adds a new task to the load queue */
-    void addTask(Task t) {
-        if (!mQueue.contains(t)) {
-            mQueue.add(t);
-        }
-        synchronized(this) {
-            notifyAll();
-        }
-    }
-
-    /**
-     * Retrieves the next task from the load queue, as well as whether we want that task to be
-     * force reloaded.
-     */
-    Task nextTask() {
-        return mQueue.poll();
-    }
-
-    /** Removes a task from the load queue */
-    void removeTask(Task t) {
-        mQueue.remove(t);
-    }
-
-    /** Clears all the tasks from the load queue */
-    void clearTasks() {
-        mQueue.clear();
-    }
-
-    /** Returns whether the load queue is empty */
-    boolean isEmpty() {
-        return mQueue.isEmpty();
-    }
-}
-
-/**
- * Task resource loader
- */
-class BackgroundTaskLoader implements Runnable {
-    static String TAG = "TaskResourceLoader";
-    static boolean DEBUG = false;
-
-    Context mContext;
-    HandlerThread mLoadThread;
-    Handler mLoadThreadHandler;
-    Handler mMainThreadHandler;
-
-    TaskResourceLoadQueue mLoadQueue;
-    TaskKeyLruCache<Drawable> mIconCache;
-    BitmapDrawable mDefaultIcon;
-
-    boolean mStarted;
-    boolean mCancelled;
-    boolean mWaitingOnLoadQueue;
-
-    private final OnIdleChangedListener mOnIdleChangedListener;
-
-    /** Constructor, creates a new loading thread that loads task resources in the background */
-    public BackgroundTaskLoader(TaskResourceLoadQueue loadQueue,
-            TaskKeyLruCache<Drawable> iconCache, BitmapDrawable defaultIcon,
-            OnIdleChangedListener onIdleChangedListener) {
-        mLoadQueue = loadQueue;
-        mIconCache = iconCache;
-        mDefaultIcon = defaultIcon;
-        mMainThreadHandler = new Handler();
-        mOnIdleChangedListener = onIdleChangedListener;
-        mLoadThread = new HandlerThread("Recents-TaskResourceLoader",
-                android.os.Process.THREAD_PRIORITY_BACKGROUND);
-        mLoadThread.start();
-        mLoadThreadHandler = new Handler(mLoadThread.getLooper());
-    }
-
-    /** Restarts the loader thread */
-    void start(Context context) {
-        mContext = context;
-        mCancelled = false;
-        if (!mStarted) {
-            // Start loading on the load thread
-            mStarted = true;
-            mLoadThreadHandler.post(this);
-        } else {
-            // Notify the load thread to start loading again
-            synchronized (mLoadThread) {
-                mLoadThread.notifyAll();
-            }
-        }
-    }
-
-    /** Requests the loader thread to stop after the current iteration */
-    void stop() {
-        // Mark as cancelled for the thread to pick up
-        mCancelled = true;
-        // If we are waiting for the load queue for more tasks, then we can just reset the
-        // Context now, since nothing is using it
-        if (mWaitingOnLoadQueue) {
-            mContext = null;
-        }
-    }
-
-    @Override
-    public void run() {
-        while (true) {
-            if (mCancelled) {
-                // We have to unset the context here, since the background thread may be using it
-                // when we call stop()
-                mContext = null;
-                // If we are cancelled, then wait until we are started again
-                synchronized(mLoadThread) {
-                    try {
-                        mLoadThread.wait();
-                    } catch (InterruptedException ie) {
-                        ie.printStackTrace();
-                    }
-                }
-            } else {
-                SystemServicesProxy ssp = Recents.getSystemServices();
-                // If we've stopped the loader, then fall through to the above logic to wait on
-                // the load thread
-                if (ssp != null) {
-                    processLoadQueueItem(ssp);
-                }
-
-                // If there are no other items in the list, then just wait until something is added
-                if (!mCancelled && mLoadQueue.isEmpty()) {
-                    synchronized(mLoadQueue) {
-                        try {
-                            mWaitingOnLoadQueue = true;
-                            mMainThreadHandler.post(
-                                    () -> mOnIdleChangedListener.onIdleChanged(true));
-                            mLoadQueue.wait();
-                            mMainThreadHandler.post(
-                                    () -> mOnIdleChangedListener.onIdleChanged(false));
-                            mWaitingOnLoadQueue = false;
-                        } catch (InterruptedException ie) {
-                            ie.printStackTrace();
-                        }
-                    }
-                }
-            }
-        }
-    }
-
-    /**
-     * This needs to be in a separate method to work around an surprising interpreter behavior:
-     * The register will keep the local reference to cachedThumbnailData even if it falls out of
-     * scope. Putting it into a method fixes this issue.
-     */
-    private void processLoadQueueItem(SystemServicesProxy ssp) {
-        // Load the next item from the queue
-        final Task t = mLoadQueue.nextTask();
-        if (t != null) {
-            Drawable cachedIcon = mIconCache.get(t.key);
-
-            // Load the icon if it is stale or we haven't cached one yet
-            if (cachedIcon == null) {
-                cachedIcon = ssp.getBadgedTaskDescriptionIcon(t.taskDescription,
-                        t.key.userId, mContext.getResources());
-
-                if (cachedIcon == null) {
-                    ActivityInfo info = ssp.getActivityInfo(
-                            t.key.getComponent(), t.key.userId);
-                    if (info != null) {
-                        if (DEBUG) Log.d(TAG, "Loading icon: " + t.key);
-                        cachedIcon = ssp.getBadgedActivityIcon(info, t.key.userId);
-                    }
-                }
-
-                if (cachedIcon == null) {
-                    cachedIcon = mDefaultIcon;
-                }
-
-                // At this point, even if we can't load the icon, we will set the
-                // default icon.
-                mIconCache.put(t.key, cachedIcon);
-            }
-
-            if (DEBUG) Log.d(TAG, "Loading thumbnail: " + t.key);
-            final ThumbnailData thumbnailData = ssp.getTaskThumbnail(t.key.id,
-                    true /* reducedResolution */);
-
-            if (!mCancelled) {
-                // Notify that the task data has changed
-                final Drawable finalIcon = cachedIcon;
-                mMainThreadHandler.post(
-                        () -> t.notifyTaskDataLoaded(thumbnailData, finalIcon));
-            }
-        }
-    }
-
-    interface OnIdleChangedListener {
-        void onIdleChanged(boolean idle);
-    }
-}
-
-/**
- * Recents task loader
- */
-public class RecentsTaskLoader {
-
-    private static final String TAG = "RecentsTaskLoader";
-    private static final boolean DEBUG = false;
-
-    // This activity info LruCache is useful because it can be expensive to retrieve ActivityInfos
-    // for many tasks, which we use to get the activity labels and icons.  Unlike the other caches
-    // below, this is per-package so we can't invalidate the items in the cache based on the last
-    // active time.  Instead, we rely on the RecentsPackageMonitor to keep us informed whenever a
-    // package in the cache has been updated, so that we may remove it.
-    private final LruCache<ComponentName, ActivityInfo> mActivityInfoCache;
-    private final TaskKeyLruCache<Drawable> mIconCache;
-    private final TaskKeyLruCache<String> mActivityLabelCache;
-    private final TaskKeyLruCache<String> mContentDescriptionCache;
-    private final TaskResourceLoadQueue mLoadQueue;
-    private final BackgroundTaskLoader mLoader;
-    private final HighResThumbnailLoader mHighResThumbnailLoader;
-    @GuardedBy("this")
-    private final TaskKeyStrongCache<ThumbnailData> mThumbnailCache = new TaskKeyStrongCache<>();
-    @GuardedBy("this")
-    private final TaskKeyStrongCache<ThumbnailData> mTempCache = new TaskKeyStrongCache<>();
-    private final int mMaxThumbnailCacheSize;
-    private final int mMaxIconCacheSize;
-    private int mNumVisibleTasksLoaded;
-
-    int mDefaultTaskBarBackgroundColor;
-    int mDefaultTaskViewBackgroundColor;
-    BitmapDrawable mDefaultIcon;
-
-    private TaskKeyLruCache.EvictionCallback mClearActivityInfoOnEviction =
-            new TaskKeyLruCache.EvictionCallback() {
-        @Override
-        public void onEntryEvicted(Task.TaskKey key) {
-            if (key != null) {
-                mActivityInfoCache.remove(key.getComponent());
-            }
-        }
-    };
-
-    public RecentsTaskLoader(Context context) {
-        Resources res = context.getResources();
-        mDefaultTaskBarBackgroundColor =
-                context.getColor(R.color.recents_task_bar_default_background_color);
-        mDefaultTaskViewBackgroundColor =
-                context.getColor(R.color.recents_task_view_default_background_color);
-        mMaxThumbnailCacheSize = res.getInteger(R.integer.config_recents_max_thumbnail_count);
-        mMaxIconCacheSize = res.getInteger(R.integer.config_recents_max_icon_count);
-        int iconCacheSize = RecentsDebugFlags.Static.DisableBackgroundCache ? 1 :
-                mMaxIconCacheSize;
-
-        // Create the default assets
-        Bitmap icon = Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8);
-        icon.eraseColor(0);
-        mDefaultIcon = new BitmapDrawable(context.getResources(), icon);
-
-        // Initialize the proxy, cache and loaders
-        int numRecentTasks = ActivityManager.getMaxRecentTasksStatic();
-        mHighResThumbnailLoader = new HighResThumbnailLoader(Recents.getSystemServices(),
-                Looper.getMainLooper(), Recents.getConfiguration().isLowRamDevice);
-        mLoadQueue = new TaskResourceLoadQueue();
-        mIconCache = new TaskKeyLruCache<>(iconCacheSize, mClearActivityInfoOnEviction);
-        mActivityLabelCache = new TaskKeyLruCache<>(numRecentTasks, mClearActivityInfoOnEviction);
-        mContentDescriptionCache = new TaskKeyLruCache<>(numRecentTasks,
-                mClearActivityInfoOnEviction);
-        mActivityInfoCache = new LruCache(numRecentTasks);
-        mLoader = new BackgroundTaskLoader(mLoadQueue, mIconCache, mDefaultIcon,
-                mHighResThumbnailLoader::setTaskLoadQueueIdle);
-    }
-
-    /** Returns the size of the app icon cache. */
-    public int getIconCacheSize() {
-        return mMaxIconCacheSize;
-    }
-
-    /** Returns the size of the thumbnail cache. */
-    public int getThumbnailCacheSize() {
-        return mMaxThumbnailCacheSize;
-    }
-
-    public HighResThumbnailLoader getHighResThumbnailLoader() {
-        return mHighResThumbnailLoader;
-    }
-
-    /** Creates a new plan for loading the recent tasks. */
-    public RecentsTaskLoadPlan createLoadPlan(Context context) {
-        RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(context);
-        return plan;
-    }
-
-    /** Preloads raw recents tasks using the specified plan to store the output. */
-    public synchronized void preloadRawTasks(RecentsTaskLoadPlan plan,
-            boolean includeFrontMostExcludedTask) {
-        plan.preloadRawTasks(includeFrontMostExcludedTask);
-    }
-
-    /** Preloads recents tasks using the specified plan to store the output. */
-    public synchronized void preloadTasks(RecentsTaskLoadPlan plan, int runningTaskId,
-            boolean includeFrontMostExcludedTask) {
-        try {
-            Trace.beginSection("preloadPlan");
-            plan.preloadPlan(this, runningTaskId, includeFrontMostExcludedTask);
-        } finally {
-            Trace.endSection();
-        }
-    }
-
-    /** Begins loading the heavy task data according to the specified options. */
-    public synchronized void loadTasks(Context context, RecentsTaskLoadPlan plan,
-            RecentsTaskLoadPlan.Options opts) {
-        if (opts == null) {
-            throw new RuntimeException("Requires load options");
-        }
-        if (opts.onlyLoadForCache && opts.loadThumbnails) {
-
-            // If we are loading for the cache, we'd like to have the real cache only include the
-            // visible thumbnails. However, we also don't want to reload already cached thumbnails.
-            // Thus, we copy over the current entries into a second cache, and clear the real cache,
-            // such that the real cache only contains visible thumbnails.
-            mTempCache.copyEntries(mThumbnailCache);
-            mThumbnailCache.evictAll();
-        }
-        plan.executePlan(opts, this);
-        mTempCache.evictAll();
-        if (!opts.onlyLoadForCache) {
-            mNumVisibleTasksLoaded = opts.numVisibleTasks;
-        }
-    }
-
-    /**
-     * Acquires the task resource data directly from the cache, loading if necessary.
-     */
-    public void loadTaskData(Task t) {
-        Drawable icon = mIconCache.getAndInvalidateIfModified(t.key);
-        icon = icon != null ? icon : mDefaultIcon;
-        mLoadQueue.addTask(t);
-        t.notifyTaskDataLoaded(t.thumbnail, icon);
-    }
-
-    /** Releases the task resource data back into the pool. */
-    public void unloadTaskData(Task t) {
-        mLoadQueue.removeTask(t);
-        t.notifyTaskDataUnloaded(mDefaultIcon);
-    }
-
-    /** Completely removes the resource data from the pool. */
-    public void deleteTaskData(Task t, boolean notifyTaskDataUnloaded) {
-        mLoadQueue.removeTask(t);
-        mIconCache.remove(t.key);
-        mActivityLabelCache.remove(t.key);
-        mContentDescriptionCache.remove(t.key);
-        if (notifyTaskDataUnloaded) {
-            t.notifyTaskDataUnloaded(mDefaultIcon);
-        }
-    }
-
-    /**
-     * Handles signals from the system, trimming memory when requested to prevent us from running
-     * out of memory.
-     */
-    public synchronized void onTrimMemory(int level) {
-        switch (level) {
-            case ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN:
-                // Stop the loader immediately when the UI is no longer visible
-                stopLoader();
-                mIconCache.trimToSize(Math.max(mNumVisibleTasksLoaded,
-                        mMaxIconCacheSize / 2));
-                break;
-            case ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE:
-            case ComponentCallbacks2.TRIM_MEMORY_BACKGROUND:
-                // We are leaving recents, so trim the data a bit
-                mIconCache.trimToSize(Math.max(1, mMaxIconCacheSize / 2));
-                mActivityInfoCache.trimToSize(Math.max(1,
-                        ActivityManager.getMaxRecentTasksStatic() / 2));
-                break;
-            case ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW:
-            case ComponentCallbacks2.TRIM_MEMORY_MODERATE:
-                // We are going to be low on memory
-                mIconCache.trimToSize(Math.max(1, mMaxIconCacheSize / 4));
-                mActivityInfoCache.trimToSize(Math.max(1,
-                        ActivityManager.getMaxRecentTasksStatic() / 4));
-                break;
-            case ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL:
-            case ComponentCallbacks2.TRIM_MEMORY_COMPLETE:
-                // We are low on memory, so release everything
-                mIconCache.evictAll();
-                mActivityInfoCache.evictAll();
-                // The cache is small, only clear the label cache when we are critical
-                mActivityLabelCache.evictAll();
-                mContentDescriptionCache.evictAll();
-                mThumbnailCache.evictAll();
-                break;
-            default:
-                break;
-        }
-    }
-
-    /**
-     * Returns the cached task label if the task key is not expired, updating the cache if it is.
-     */
-    String getAndUpdateActivityTitle(Task.TaskKey taskKey, ActivityManager.TaskDescription td) {
-        SystemServicesProxy ssp = Recents.getSystemServices();
-
-        // Return the task description label if it exists
-        if (td != null && td.getLabel() != null) {
-            return td.getLabel();
-        }
-        // Return the cached activity label if it exists
-        String label = mActivityLabelCache.getAndInvalidateIfModified(taskKey);
-        if (label != null) {
-            return label;
-        }
-        // All short paths failed, load the label from the activity info and cache it
-        ActivityInfo activityInfo = getAndUpdateActivityInfo(taskKey);
-        if (activityInfo != null) {
-            label = ssp.getBadgedActivityLabel(activityInfo, taskKey.userId);
-            mActivityLabelCache.put(taskKey, label);
-            return label;
-        }
-        // If the activity info does not exist or fails to load, return an empty label for now,
-        // but do not cache it
-        return "";
-    }
-
-    /**
-     * Returns the cached task content description if the task key is not expired, updating the
-     * cache if it is.
-     */
-    String getAndUpdateContentDescription(Task.TaskKey taskKey, ActivityManager.TaskDescription td,
-            Resources res) {
-        SystemServicesProxy ssp = Recents.getSystemServices();
-
-        // Return the cached content description if it exists
-        String label = mContentDescriptionCache.getAndInvalidateIfModified(taskKey);
-        if (label != null) {
-            return label;
-        }
-
-        // All short paths failed, load the label from the activity info and cache it
-        ActivityInfo activityInfo = getAndUpdateActivityInfo(taskKey);
-        if (activityInfo != null) {
-            label = ssp.getBadgedContentDescription(activityInfo, taskKey.userId, td, res);
-            if (td == null) {
-                // Only add to the cache if the task description is null, otherwise, it is possible
-                // for the task description to change between calls without the last active time
-                // changing (ie. between preloading and Overview starting) which would lead to stale
-                // content descriptions
-                // TODO: Investigate improving this
-                mContentDescriptionCache.put(taskKey, label);
-            }
-            return label;
-        }
-        // If the content description does not exist, return an empty label for now, but do not
-        // cache it
-        return "";
-    }
-
-    /**
-     * Returns the cached task icon if the task key is not expired, updating the cache if it is.
-     */
-    Drawable getAndUpdateActivityIcon(Task.TaskKey taskKey, ActivityManager.TaskDescription td,
-            Resources res, boolean loadIfNotCached) {
-        SystemServicesProxy ssp = Recents.getSystemServices();
-
-        // Return the cached activity icon if it exists
-        Drawable icon = mIconCache.getAndInvalidateIfModified(taskKey);
-        if (icon != null) {
-            return icon;
-        }
-
-        if (loadIfNotCached) {
-            // Return and cache the task description icon if it exists
-            icon = ssp.getBadgedTaskDescriptionIcon(td, taskKey.userId, res);
-            if (icon != null) {
-                mIconCache.put(taskKey, icon);
-                return icon;
-            }
-
-            // Load the icon from the activity info and cache it
-            ActivityInfo activityInfo = getAndUpdateActivityInfo(taskKey);
-            if (activityInfo != null) {
-                icon = ssp.getBadgedActivityIcon(activityInfo, taskKey.userId);
-                if (icon != null) {
-                    mIconCache.put(taskKey, icon);
-                    return icon;
-                }
-            }
-        }
-        // We couldn't load any icon
-        return null;
-    }
-
-    /**
-     * Returns the cached thumbnail if the task key is not expired, updating the cache if it is.
-     */
-    synchronized ThumbnailData getAndUpdateThumbnail(Task.TaskKey taskKey, boolean loadIfNotCached,
-            boolean storeInCache) {
-        SystemServicesProxy ssp = Recents.getSystemServices();
-
-        ThumbnailData cached = mThumbnailCache.getAndInvalidateIfModified(taskKey);
-        if (cached != null) {
-            return cached;
-        }
-
-        cached = mTempCache.getAndInvalidateIfModified(taskKey);
-        if (cached != null) {
-            mThumbnailCache.put(taskKey, cached);
-            return cached;
-        }
-
-        if (loadIfNotCached) {
-            RecentsConfiguration config = Recents.getConfiguration();
-            if (config.svelteLevel < RecentsConfiguration.SVELTE_DISABLE_LOADING) {
-                // Load the thumbnail from the system
-                ThumbnailData thumbnailData = ssp.getTaskThumbnail(taskKey.id,
-                        true /* reducedResolution */);
-                if (thumbnailData.thumbnail != null) {
-                    if (storeInCache) {
-                        mThumbnailCache.put(taskKey, thumbnailData);
-                    }
-                    return thumbnailData;
-                }
-            }
-        }
-
-        // We couldn't load any thumbnail
-        return null;
-    }
-
-    /**
-     * Returns the task's primary color if possible, defaulting to the default color if there is
-     * no specified primary color.
-     */
-    int getActivityPrimaryColor(ActivityManager.TaskDescription td) {
-        if (td != null && td.getPrimaryColor() != 0) {
-            return td.getPrimaryColor();
-        }
-        return mDefaultTaskBarBackgroundColor;
-    }
-
-    /**
-     * Returns the task's background color if possible.
-     */
-    int getActivityBackgroundColor(ActivityManager.TaskDescription td) {
-        if (td != null && td.getBackgroundColor() != 0) {
-            return td.getBackgroundColor();
-        }
-        return mDefaultTaskViewBackgroundColor;
-    }
-
-    /**
-     * Returns the activity info for the given task key, retrieving one from the system if the
-     * task key is expired.
-     */
-    ActivityInfo getAndUpdateActivityInfo(Task.TaskKey taskKey) {
-        SystemServicesProxy ssp = Recents.getSystemServices();
-        ComponentName cn = taskKey.getComponent();
-        ActivityInfo activityInfo = mActivityInfoCache.get(cn);
-        if (activityInfo == null) {
-            activityInfo = ssp.getActivityInfo(cn, taskKey.userId);
-            if (cn == null || activityInfo == null) {
-                Log.e(TAG, "Unexpected null component name or activity info: " + cn + ", " +
-                        activityInfo);
-                return null;
-            }
-            mActivityInfoCache.put(cn, activityInfo);
-        }
-        return activityInfo;
-    }
-
-    /**
-     * Starts loading tasks.
-     */
-    public void startLoader(Context ctx) {
-        mLoader.start(ctx);
-    }
-
-    /**
-     * Stops the task loader and clears all queued, pending task loads.
-     */
-    private void stopLoader() {
-        mLoader.stop();
-        mLoadQueue.clearTasks();
-    }
-
-    /**** Event Bus Events ****/
-
-    public final void onBusEvent(PackagesChangedEvent event) {
-        // Remove all the cached activity infos for this package.  The other caches do not need to
-        // be pruned at this time, as the TaskKey expiration checks will flush them next time their
-        // cached contents are requested
-        Map<ComponentName, ActivityInfo> activityInfoCache = mActivityInfoCache.snapshot();
-        for (ComponentName cn : activityInfoCache.keySet()) {
-            if (cn.getPackageName().equals(event.packageName)) {
-                if (DEBUG) {
-                    Log.d(TAG, "Removing activity info from cache: " + cn);
-                }
-                mActivityInfoCache.remove(cn);
-            }
-        }
-    }
-
-    public synchronized void dump(String prefix, PrintWriter writer) {
-        String innerPrefix = prefix + "  ";
-
-        writer.print(prefix); writer.println(TAG);
-        writer.print(prefix); writer.println("Icon Cache");
-        mIconCache.dump(innerPrefix, writer);
-        writer.print(prefix); writer.println("Thumbnail Cache");
-        mThumbnailCache.dump(innerPrefix, writer);
-        writer.print(prefix); writer.println("Temp Thumbnail Cache");
-        mTempCache.dump(innerPrefix, writer);
-    }
-}
diff --git a/com/android/systemui/recents/model/TaskGrouping.java b/com/android/systemui/recents/model/TaskGrouping.java
deleted file mode 100644
index 2109376..0000000
--- a/com/android/systemui/recents/model/TaskGrouping.java
+++ /dev/null
@@ -1,106 +0,0 @@
-package com.android.systemui.recents.model;
-
-import android.util.ArrayMap;
-
-import java.util.ArrayList;
-
-/** Represents a grouping of tasks witihin a stack. */
-public class TaskGrouping {
-
-    int affiliation;
-    long latestActiveTimeInGroup;
-
-    Task.TaskKey mFrontMostTaskKey;
-    ArrayList<Task.TaskKey> mTaskKeys = new ArrayList<Task.TaskKey>();
-    ArrayMap<Task.TaskKey, Integer> mTaskKeyIndices = new ArrayMap<>();
-
-    /** Creates a group with a specified affiliation. */
-    public TaskGrouping(int affiliation) {
-        this.affiliation = affiliation;
-    }
-
-    /** Adds a new task to this group. */
-    void addTask(Task t) {
-        mTaskKeys.add(t.key);
-        if (t.key.lastActiveTime > latestActiveTimeInGroup) {
-            latestActiveTimeInGroup = t.key.lastActiveTime;
-        }
-        t.setGroup(this);
-        updateTaskIndices();
-    }
-
-    /** Removes a task from this group. */
-    void removeTask(Task t) {
-        mTaskKeys.remove(t.key);
-        latestActiveTimeInGroup = 0;
-        int taskCount = mTaskKeys.size();
-        for (int i = 0; i < taskCount; i++) {
-            long lastActiveTime = mTaskKeys.get(i).lastActiveTime;
-            if (lastActiveTime > latestActiveTimeInGroup) {
-                latestActiveTimeInGroup = lastActiveTime;
-            }
-        }
-        t.setGroup(null);
-        updateTaskIndices();
-    }
-
-    /** Returns the key of the next task in the group. */
-    public Task.TaskKey getNextTaskInGroup(Task t) {
-        int i = indexOf(t);
-        if ((i + 1) < getTaskCount()) {
-            return mTaskKeys.get(i + 1);
-        }
-        return null;
-    }
-
-    /** Returns the key of the previous task in the group. */
-    public Task.TaskKey getPrevTaskInGroup(Task t) {
-        int i = indexOf(t);
-        if ((i - 1) >= 0) {
-            return mTaskKeys.get(i - 1);
-        }
-        return null;
-    }
-
-    /** Gets the front task */
-    public boolean isFrontMostTask(Task t) {
-        return (t.key == mFrontMostTaskKey);
-    }
-
-    /** Finds the index of a given task in a group. */
-    public int indexOf(Task t) {
-        return mTaskKeyIndices.get(t.key);
-    }
-
-    /** Returns whether a task is in this grouping. */
-    public boolean containsTask(Task t) {
-        return mTaskKeyIndices.containsKey(t.key);
-    }
-
-    /** Returns whether one task is above another in the group.  If they are not in the same group,
-     * this returns false. */
-    public boolean isTaskAboveTask(Task t, Task below) {
-        return mTaskKeyIndices.containsKey(t.key) && mTaskKeyIndices.containsKey(below.key) &&
-                mTaskKeyIndices.get(t.key) > mTaskKeyIndices.get(below.key);
-    }
-
-    /** Returns the number of tasks in this group. */
-    public int getTaskCount() { return mTaskKeys.size(); }
-
-    /** Updates the mapping of tasks to indices. */
-    private void updateTaskIndices() {
-        if (mTaskKeys.isEmpty()) {
-            mFrontMostTaskKey = null;
-            mTaskKeyIndices.clear();
-            return;
-        }
-
-        int taskCount = mTaskKeys.size();
-        mFrontMostTaskKey = mTaskKeys.get(mTaskKeys.size() - 1);
-        mTaskKeyIndices.clear();
-        for (int i = 0; i < taskCount; i++) {
-            Task.TaskKey k = mTaskKeys.get(i);
-            mTaskKeyIndices.put(k, i);
-        }
-    }
-}
diff --git a/com/android/systemui/recents/model/TaskStack.java b/com/android/systemui/recents/model/TaskStack.java
deleted file mode 100644
index fdae917..0000000
--- a/com/android/systemui/recents/model/TaskStack.java
+++ /dev/null
@@ -1,1140 +0,0 @@
-/*
- * Copyright (C) 2014 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.model;
-
-import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT;
-import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.view.WindowManager.DOCKED_BOTTOM;
-import static android.view.WindowManager.DOCKED_INVALID;
-import static android.view.WindowManager.DOCKED_LEFT;
-import static android.view.WindowManager.DOCKED_RIGHT;
-import static android.view.WindowManager.DOCKED_TOP;
-
-import android.animation.Animator;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
-import android.animation.PropertyValuesHolder;
-import android.annotation.IntDef;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.drawable.ColorDrawable;
-import android.util.ArrayMap;
-import android.util.ArraySet;
-import android.util.IntProperty;
-import android.util.SparseArray;
-import android.view.animation.Interpolator;
-
-import com.android.internal.policy.DockedDividerUtils;
-import com.android.systemui.Interpolators;
-import com.android.systemui.R;
-import com.android.systemui.recents.Recents;
-import com.android.systemui.recents.RecentsDebugFlags;
-import com.android.systemui.recents.misc.NamedCounter;
-import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.Utilities;
-import com.android.systemui.recents.views.AnimationProps;
-import com.android.systemui.recents.views.DropTarget;
-import com.android.systemui.recents.views.TaskStackLayoutAlgorithm;
-
-import java.io.PrintWriter;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-import java.util.Random;
-
-
-/**
- * An interface for a task filter to query whether a particular task should show in a stack.
- */
-interface TaskFilter {
-    /** Returns whether the filter accepts the specified task */
-    public boolean acceptTask(SparseArray<Task> taskIdMap, Task t, int index);
-}
-
-/**
- * A list of filtered tasks.
- */
-class FilteredTaskList {
-
-    ArrayList<Task> mTasks = new ArrayList<>();
-    ArrayList<Task> mFilteredTasks = new ArrayList<>();
-    ArrayMap<Task.TaskKey, Integer> mTaskIndices = new ArrayMap<>();
-    TaskFilter mFilter;
-
-    /** Sets the task filter, saving the current touch state */
-    boolean setFilter(TaskFilter filter) {
-        ArrayList<Task> prevFilteredTasks = new ArrayList<>(mFilteredTasks);
-        mFilter = filter;
-        updateFilteredTasks();
-        if (!prevFilteredTasks.equals(mFilteredTasks)) {
-            return true;
-        } else {
-            return false;
-        }
-    }
-
-    /** Removes the task filter and returns the previous touch state */
-    void removeFilter() {
-        mFilter = null;
-        updateFilteredTasks();
-    }
-
-    /** Adds a new task to the task list */
-    void add(Task t) {
-        mTasks.add(t);
-        updateFilteredTasks();
-    }
-
-    /**
-     * Moves the given task.
-     */
-    public void setTaskWindowingMode(Task task, int insertIndex, int windowingMode) {
-        int taskIndex = indexOf(task);
-        if (taskIndex != insertIndex) {
-            mTasks.remove(taskIndex);
-            if (taskIndex < insertIndex) {
-                insertIndex--;
-            }
-            mTasks.add(insertIndex, task);
-        }
-
-        // Update the stack id now, after we've moved the task, and before we update the
-        // filtered tasks
-        task.setWindowingMode(windowingMode);
-        updateFilteredTasks();
-    }
-
-    /** Sets the list of tasks */
-    void set(List<Task> tasks) {
-        mTasks.clear();
-        mTasks.addAll(tasks);
-        updateFilteredTasks();
-    }
-
-    /** Removes a task from the base list only if it is in the filtered list */
-    boolean remove(Task t) {
-        if (mFilteredTasks.contains(t)) {
-            boolean removed = mTasks.remove(t);
-            updateFilteredTasks();
-            return removed;
-        }
-        return false;
-    }
-
-    /** Returns the index of this task in the list of filtered tasks */
-    int indexOf(Task t) {
-        if (t != null && mTaskIndices.containsKey(t.key)) {
-            return mTaskIndices.get(t.key);
-        }
-        return -1;
-    }
-
-    /** Returns the size of the list of filtered tasks */
-    int size() {
-        return mFilteredTasks.size();
-    }
-
-    /** Returns whether the filtered list contains this task */
-    boolean contains(Task t) {
-        return mTaskIndices.containsKey(t.key);
-    }
-
-    /** Updates the list of filtered tasks whenever the base task list changes */
-    private void updateFilteredTasks() {
-        mFilteredTasks.clear();
-        if (mFilter != null) {
-            // Create a sparse array from task id to Task
-            SparseArray<Task> taskIdMap = new SparseArray<>();
-            int taskCount = mTasks.size();
-            for (int i = 0; i < taskCount; i++) {
-                Task t = mTasks.get(i);
-                taskIdMap.put(t.key.id, t);
-            }
-
-            for (int i = 0; i < taskCount; i++) {
-                Task t = mTasks.get(i);
-                if (mFilter.acceptTask(taskIdMap, t, i)) {
-                    mFilteredTasks.add(t);
-                }
-            }
-        } else {
-            mFilteredTasks.addAll(mTasks);
-        }
-        updateFilteredTaskIndices();
-    }
-
-    /** Updates the mapping of tasks to indices. */
-    private void updateFilteredTaskIndices() {
-        int taskCount = mFilteredTasks.size();
-        mTaskIndices.clear();
-        for (int i = 0; i < taskCount; i++) {
-            Task t = mFilteredTasks.get(i);
-            mTaskIndices.put(t.key, i);
-        }
-    }
-
-    /** Returns whether this task list is filtered */
-    boolean hasFilter() {
-        return (mFilter != null);
-    }
-
-    /** Returns the list of filtered tasks */
-    ArrayList<Task> getTasks() {
-        return mFilteredTasks;
-    }
-}
-
-/**
- * The task stack contains a list of multiple tasks.
- */
-public class TaskStack {
-
-    private static final String TAG = "TaskStack";
-
-    /** Task stack callbacks */
-    public interface TaskStackCallbacks {
-        /**
-         * Notifies when a new task has been added to the stack.
-         */
-        void onStackTaskAdded(TaskStack stack, Task newTask);
-
-        /**
-         * Notifies when a task has been removed from the stack.
-         */
-        void onStackTaskRemoved(TaskStack stack, Task removedTask, Task newFrontMostTask,
-                AnimationProps animation, boolean fromDockGesture,
-                boolean dismissRecentsIfAllRemoved);
-
-        /**
-         * Notifies when all tasks have been removed from the stack.
-         */
-        void onStackTasksRemoved(TaskStack stack);
-
-        /**
-         * Notifies when tasks in the stack have been updated.
-         */
-        void onStackTasksUpdated(TaskStack stack);
-    }
-
-    /**
-     * The various possible dock states when dragging and dropping a task.
-     */
-    public static class DockState implements DropTarget {
-
-        public static final int DOCK_AREA_BG_COLOR = 0xFFffffff;
-        public static final int DOCK_AREA_GRID_BG_COLOR = 0xFF000000;
-
-        // The rotation to apply to the hint text
-        @Retention(RetentionPolicy.SOURCE)
-        @IntDef({HORIZONTAL, VERTICAL})
-        public @interface TextOrientation {}
-        private static final int HORIZONTAL = 0;
-        private static final int VERTICAL = 1;
-
-        private static final int DOCK_AREA_ALPHA = 80;
-        public static final DockState NONE = new DockState(DOCKED_INVALID, -1, 80, 255, HORIZONTAL,
-                null, null, null);
-        public static final DockState LEFT = new DockState(DOCKED_LEFT,
-                DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT, DOCK_AREA_ALPHA, 0, VERTICAL,
-                new RectF(0, 0, 0.125f, 1), new RectF(0, 0, 0.125f, 1),
-                new RectF(0, 0, 0.5f, 1));
-        public static final DockState TOP = new DockState(DOCKED_TOP,
-                DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT, DOCK_AREA_ALPHA, 0, HORIZONTAL,
-                new RectF(0, 0, 1, 0.125f), new RectF(0, 0, 1, 0.125f),
-                new RectF(0, 0, 1, 0.5f));
-        public static final DockState RIGHT = new DockState(DOCKED_RIGHT,
-                DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT, DOCK_AREA_ALPHA, 0, VERTICAL,
-                new RectF(0.875f, 0, 1, 1), new RectF(0.875f, 0, 1, 1),
-                new RectF(0.5f, 0, 1, 1));
-        public static final DockState BOTTOM = new DockState(DOCKED_BOTTOM,
-                DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT, DOCK_AREA_ALPHA, 0, HORIZONTAL,
-                new RectF(0, 0.875f, 1, 1), new RectF(0, 0.875f, 1, 1),
-                new RectF(0, 0.5f, 1, 1));
-
-        @Override
-        public boolean acceptsDrop(int x, int y, int width, int height, Rect insets,
-                boolean isCurrentTarget) {
-            if (isCurrentTarget) {
-                getMappedRect(expandedTouchDockArea, width, height, mTmpRect);
-                return mTmpRect.contains(x, y);
-            } else {
-                getMappedRect(touchArea, width, height, mTmpRect);
-                updateBoundsWithSystemInsets(mTmpRect, insets);
-                return mTmpRect.contains(x, y);
-            }
-        }
-
-        // Represents the view state of this dock state
-        public static class ViewState {
-            private static final IntProperty<ViewState> HINT_ALPHA =
-                    new IntProperty<ViewState>("drawableAlpha") {
-                        @Override
-                        public void setValue(ViewState object, int alpha) {
-                            object.mHintTextAlpha = alpha;
-                            object.dockAreaOverlay.invalidateSelf();
-                        }
-
-                        @Override
-                        public Integer get(ViewState object) {
-                            return object.mHintTextAlpha;
-                        }
-                    };
-
-            public final int dockAreaAlpha;
-            public final ColorDrawable dockAreaOverlay;
-            public final int hintTextAlpha;
-            public final int hintTextOrientation;
-
-            private final int mHintTextResId;
-            private String mHintText;
-            private Paint mHintTextPaint;
-            private Point mHintTextBounds = new Point();
-            private int mHintTextAlpha = 255;
-            private AnimatorSet mDockAreaOverlayAnimator;
-            private Rect mTmpRect = new Rect();
-
-            private ViewState(int areaAlpha, int hintAlpha, @TextOrientation int hintOrientation,
-                    int hintTextResId) {
-                dockAreaAlpha = areaAlpha;
-                dockAreaOverlay = new ColorDrawable(Recents.getConfiguration().isGridEnabled
-                        ? DOCK_AREA_GRID_BG_COLOR : DOCK_AREA_BG_COLOR);
-                dockAreaOverlay.setAlpha(0);
-                hintTextAlpha = hintAlpha;
-                hintTextOrientation = hintOrientation;
-                mHintTextResId = hintTextResId;
-                mHintTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
-                mHintTextPaint.setColor(Color.WHITE);
-            }
-
-            /**
-             * Updates the view state with the given context.
-             */
-            public void update(Context context) {
-                Resources res = context.getResources();
-                mHintText = context.getString(mHintTextResId);
-                mHintTextPaint.setTextSize(res.getDimensionPixelSize(
-                        R.dimen.recents_drag_hint_text_size));
-                mHintTextPaint.getTextBounds(mHintText, 0, mHintText.length(), mTmpRect);
-                mHintTextBounds.set((int) mHintTextPaint.measureText(mHintText), mTmpRect.height());
-            }
-
-            /**
-             * Draws the current view state.
-             */
-            public void draw(Canvas canvas) {
-                // Draw the overlay background
-                if (dockAreaOverlay.getAlpha() > 0) {
-                    dockAreaOverlay.draw(canvas);
-                }
-
-                // Draw the hint text
-                if (mHintTextAlpha > 0) {
-                    Rect bounds = dockAreaOverlay.getBounds();
-                    int x = bounds.left + (bounds.width() - mHintTextBounds.x) / 2;
-                    int y = bounds.top + (bounds.height() + mHintTextBounds.y) / 2;
-                    mHintTextPaint.setAlpha(mHintTextAlpha);
-                    if (hintTextOrientation == VERTICAL) {
-                        canvas.save();
-                        canvas.rotate(-90f, bounds.centerX(), bounds.centerY());
-                    }
-                    canvas.drawText(mHintText, x, y, mHintTextPaint);
-                    if (hintTextOrientation == VERTICAL) {
-                        canvas.restore();
-                    }
-                }
-            }
-
-            /**
-             * Creates a new bounds and alpha animation.
-             */
-            public void startAnimation(Rect bounds, int areaAlpha, int hintAlpha, int duration,
-                    Interpolator interpolator, boolean animateAlpha, boolean animateBounds) {
-                if (mDockAreaOverlayAnimator != null) {
-                    mDockAreaOverlayAnimator.cancel();
-                }
-
-                ObjectAnimator anim;
-                ArrayList<Animator> animators = new ArrayList<>();
-                if (dockAreaOverlay.getAlpha() != areaAlpha) {
-                    if (animateAlpha) {
-                        anim = ObjectAnimator.ofInt(dockAreaOverlay,
-                                Utilities.DRAWABLE_ALPHA, dockAreaOverlay.getAlpha(), areaAlpha);
-                        anim.setDuration(duration);
-                        anim.setInterpolator(interpolator);
-                        animators.add(anim);
-                    } else {
-                        dockAreaOverlay.setAlpha(areaAlpha);
-                    }
-                }
-                if (mHintTextAlpha != hintAlpha) {
-                    if (animateAlpha) {
-                        anim = ObjectAnimator.ofInt(this, HINT_ALPHA, mHintTextAlpha,
-                                hintAlpha);
-                        anim.setDuration(150);
-                        anim.setInterpolator(hintAlpha > mHintTextAlpha
-                                ? Interpolators.ALPHA_IN
-                                : Interpolators.ALPHA_OUT);
-                        animators.add(anim);
-                    } else {
-                        mHintTextAlpha = hintAlpha;
-                        dockAreaOverlay.invalidateSelf();
-                    }
-                }
-                if (bounds != null && !dockAreaOverlay.getBounds().equals(bounds)) {
-                    if (animateBounds) {
-                        PropertyValuesHolder prop = PropertyValuesHolder.ofObject(
-                                Utilities.DRAWABLE_RECT, Utilities.RECT_EVALUATOR,
-                                new Rect(dockAreaOverlay.getBounds()), bounds);
-                        anim = ObjectAnimator.ofPropertyValuesHolder(dockAreaOverlay, prop);
-                        anim.setDuration(duration);
-                        anim.setInterpolator(interpolator);
-                        animators.add(anim);
-                    } else {
-                        dockAreaOverlay.setBounds(bounds);
-                    }
-                }
-                if (!animators.isEmpty()) {
-                    mDockAreaOverlayAnimator = new AnimatorSet();
-                    mDockAreaOverlayAnimator.playTogether(animators);
-                    mDockAreaOverlayAnimator.start();
-                }
-            }
-        }
-
-        public final int dockSide;
-        public final int createMode;
-        public final ViewState viewState;
-        private final RectF touchArea;
-        private final RectF dockArea;
-        private final RectF expandedTouchDockArea;
-        private static final Rect mTmpRect = new Rect();
-
-        /**
-         * @param createMode used to pass to ActivityManager to dock the task
-         * @param touchArea the area in which touch will initiate this dock state
-         * @param dockArea the visible dock area
-         * @param expandedTouchDockArea the area in which touch will continue to dock after entering
-         *                              the initial touch area.  This is also the new dock area to
-         *                              draw.
-         */
-        DockState(int dockSide, int createMode, int dockAreaAlpha, int hintTextAlpha,
-                  @TextOrientation int hintTextOrientation, RectF touchArea, RectF dockArea,
-                  RectF expandedTouchDockArea) {
-            this.dockSide = dockSide;
-            this.createMode = createMode;
-            this.viewState = new ViewState(dockAreaAlpha, hintTextAlpha, hintTextOrientation,
-                    R.string.recents_drag_hint_message);
-            this.dockArea = dockArea;
-            this.touchArea = touchArea;
-            this.expandedTouchDockArea = expandedTouchDockArea;
-        }
-
-        /**
-         * Updates the dock state with the given context.
-         */
-        public void update(Context context) {
-            viewState.update(context);
-        }
-
-        /**
-         * Returns the docked task bounds with the given {@param width} and {@param height}.
-         */
-        public Rect getPreDockedBounds(int width, int height, Rect insets) {
-            getMappedRect(dockArea, width, height, mTmpRect);
-            return updateBoundsWithSystemInsets(mTmpRect, insets);
-        }
-
-        /**
-         * Returns the expanded docked task bounds with the given {@param width} and
-         * {@param height}.
-         */
-        public Rect getDockedBounds(int width, int height, int dividerSize, Rect insets,
-                Resources res) {
-            // Calculate the docked task bounds
-            boolean isHorizontalDivision =
-                    res.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
-            int position = DockedDividerUtils.calculateMiddlePosition(isHorizontalDivision,
-                    insets, width, height, dividerSize);
-            Rect newWindowBounds = new Rect();
-            DockedDividerUtils.calculateBoundsForPosition(position, dockSide, newWindowBounds,
-                    width, height, dividerSize);
-            return newWindowBounds;
-        }
-
-        /**
-         * Returns the task stack bounds with the given {@param width} and
-         * {@param height}.
-         */
-        public Rect getDockedTaskStackBounds(Rect displayRect, int width, int height,
-                int dividerSize, Rect insets, TaskStackLayoutAlgorithm layoutAlgorithm,
-                Resources res, Rect windowRectOut) {
-            // Calculate the inverse docked task bounds
-            boolean isHorizontalDivision =
-                    res.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
-            int position = DockedDividerUtils.calculateMiddlePosition(isHorizontalDivision,
-                    insets, width, height, dividerSize);
-            DockedDividerUtils.calculateBoundsForPosition(position,
-                    DockedDividerUtils.invertDockSide(dockSide), windowRectOut, width, height,
-                    dividerSize);
-
-            // Calculate the task stack bounds from the new window bounds
-            Rect taskStackBounds = new Rect();
-            // If the task stack bounds is specifically under the dock area, then ignore the top
-            // inset
-            int top = dockArea.bottom < 1f
-                    ? 0
-                    : insets.top;
-            // For now, ignore the left insets since we always dock on the left and show Recents
-            // on the right
-            layoutAlgorithm.getTaskStackBounds(displayRect, windowRectOut, top, 0, insets.right,
-                    taskStackBounds);
-            return taskStackBounds;
-        }
-
-        /**
-         * Returns the expanded bounds in certain dock sides such that the bounds account for the
-         * system insets (namely the vertical nav bar).  This call modifies and returns the given
-         * {@param bounds}.
-         */
-        private Rect updateBoundsWithSystemInsets(Rect bounds, Rect insets) {
-            if (dockSide == DOCKED_LEFT) {
-                bounds.right += insets.left;
-            } else if (dockSide == DOCKED_RIGHT) {
-                bounds.left -= insets.right;
-            }
-            return bounds;
-        }
-
-        /**
-         * Returns the mapped rect to the given dimensions.
-         */
-        private void getMappedRect(RectF bounds, int width, int height, Rect out) {
-            out.set((int) (bounds.left * width), (int) (bounds.top * height),
-                    (int) (bounds.right * width), (int) (bounds.bottom * height));
-        }
-    }
-
-    // A comparator that sorts tasks by their freeform state
-    private Comparator<Task> FREEFORM_COMPARATOR = new Comparator<Task>() {
-        @Override
-        public int compare(Task o1, Task o2) {
-            if (o1.isFreeformTask() && !o2.isFreeformTask()) {
-                return 1;
-            } else if (o2.isFreeformTask() && !o1.isFreeformTask()) {
-                return -1;
-            }
-            return Long.compare(o1.temporarySortIndexInStack, o2.temporarySortIndexInStack);
-        }
-    };
-
-
-    // The task offset to apply to a task id as a group affiliation
-    static final int IndividualTaskIdOffset = 1 << 16;
-
-    ArrayList<Task> mRawTaskList = new ArrayList<>();
-    FilteredTaskList mStackTaskList = new FilteredTaskList();
-    TaskStackCallbacks mCb;
-
-    ArrayList<TaskGrouping> mGroups = new ArrayList<>();
-    ArrayMap<Integer, TaskGrouping> mAffinitiesGroups = new ArrayMap<>();
-
-    public TaskStack() {
-        // Ensure that we only show non-docked tasks
-        mStackTaskList.setFilter(new TaskFilter() {
-            @Override
-            public boolean acceptTask(SparseArray<Task> taskIdMap, Task t, int index) {
-                if (RecentsDebugFlags.Static.EnableAffiliatedTaskGroups) {
-                    if (t.isAffiliatedTask()) {
-                        // If this task is affiliated with another parent in the stack, then the
-                        // historical state of this task depends on the state of the parent task
-                        Task parentTask = taskIdMap.get(t.affiliationTaskId);
-                        if (parentTask != null) {
-                            t = parentTask;
-                        }
-                    }
-                }
-                return t.isStackTask;
-            }
-        });
-    }
-
-    /** Sets the callbacks for this task stack. */
-    public void setCallbacks(TaskStackCallbacks cb) {
-        mCb = cb;
-    }
-
-    /** Sets the windowing mode for a given task. */
-    public void setTaskWindowingMode(Task task, int windowingMode) {
-        // Find the index to insert into
-        ArrayList<Task> taskList = mStackTaskList.getTasks();
-        int taskCount = taskList.size();
-        if (!task.isFreeformTask() && (windowingMode == WINDOWING_MODE_FREEFORM)) {
-            // Insert freeform tasks at the front
-            mStackTaskList.setTaskWindowingMode(task, taskCount, windowingMode);
-        } else if (task.isFreeformTask() && (windowingMode == WINDOWING_MODE_FULLSCREEN)) {
-            // Insert after the first stacked task
-            int insertIndex = 0;
-            for (int i = taskCount - 1; i >= 0; i--) {
-                if (!taskList.get(i).isFreeformTask()) {
-                    insertIndex = i + 1;
-                    break;
-                }
-            }
-            mStackTaskList.setTaskWindowingMode(task, insertIndex, windowingMode);
-        }
-    }
-
-    /** Does the actual work associated with removing the task. */
-    void removeTaskImpl(FilteredTaskList taskList, Task t) {
-        // Remove the task from the list
-        taskList.remove(t);
-        // Remove it from the group as well, and if it is empty, remove the group
-        TaskGrouping group = t.group;
-        if (group != null) {
-            group.removeTask(t);
-            if (group.getTaskCount() == 0) {
-                removeGroup(group);
-            }
-        }
-    }
-
-    /**
-     * Removes a task from the stack, with an additional {@param animation} hint to the callbacks on
-     * how they should update themselves.
-     */
-    public void removeTask(Task t, AnimationProps animation, boolean fromDockGesture) {
-        removeTask(t, animation, fromDockGesture, true /* dismissRecentsIfAllRemoved */);
-    }
-
-    /**
-     * Removes a task from the stack, with an additional {@param animation} hint to the callbacks on
-     * how they should update themselves.
-     */
-    public void removeTask(Task t, AnimationProps animation, boolean fromDockGesture,
-            boolean dismissRecentsIfAllRemoved) {
-        if (mStackTaskList.contains(t)) {
-            removeTaskImpl(mStackTaskList, t);
-            Task newFrontMostTask = getStackFrontMostTask(false  /* includeFreeform */);
-            if (mCb != null) {
-                // Notify that a task has been removed
-                mCb.onStackTaskRemoved(this, t, newFrontMostTask, animation,
-                        fromDockGesture, dismissRecentsIfAllRemoved);
-            }
-        }
-        mRawTaskList.remove(t);
-    }
-
-    /**
-     * Removes all tasks from the stack.
-     */
-    public void removeAllTasks(boolean notifyStackChanges) {
-        ArrayList<Task> tasks = mStackTaskList.getTasks();
-        for (int i = tasks.size() - 1; i >= 0; i--) {
-            Task t = tasks.get(i);
-            removeTaskImpl(mStackTaskList, t);
-            mRawTaskList.remove(t);
-        }
-        if (mCb != null && notifyStackChanges) {
-            // Notify that all tasks have been removed
-            mCb.onStackTasksRemoved(this);
-        }
-    }
-
-
-    /**
-     * @see #setTasks(Context, List, boolean, boolean)
-     */
-    public void setTasks(Context context, TaskStack stack, boolean notifyStackChanges) {
-        setTasks(context, stack.mRawTaskList, notifyStackChanges);
-    }
-
-    /**
-     * Sets a few tasks in one go, without calling any callbacks.
-     *
-     * @param tasks the new set of tasks to replace the current set.
-     * @param notifyStackChanges whether or not to callback on specific changes to the list of tasks.
-     */
-    public void setTasks(Context context, List<Task> tasks, boolean notifyStackChanges) {
-        // Compute a has set for each of the tasks
-        ArrayMap<Task.TaskKey, Task> currentTasksMap = createTaskKeyMapFromList(mRawTaskList);
-        ArrayMap<Task.TaskKey, Task> newTasksMap = createTaskKeyMapFromList(tasks);
-        ArrayList<Task> addedTasks = new ArrayList<>();
-        ArrayList<Task> removedTasks = new ArrayList<>();
-        ArrayList<Task> allTasks = new ArrayList<>();
-
-        // Disable notifications if there are no callbacks
-        if (mCb == null) {
-            notifyStackChanges = false;
-        }
-
-        // Remove any tasks that no longer exist
-        int taskCount = mRawTaskList.size();
-        for (int i = taskCount - 1; i >= 0; i--) {
-            Task task = mRawTaskList.get(i);
-            if (!newTasksMap.containsKey(task.key)) {
-                if (notifyStackChanges) {
-                    removedTasks.add(task);
-                }
-            }
-            task.setGroup(null);
-        }
-
-        // Add any new tasks
-        taskCount = tasks.size();
-        for (int i = 0; i < taskCount; i++) {
-            Task newTask = tasks.get(i);
-            Task currentTask = currentTasksMap.get(newTask.key);
-            if (currentTask == null && notifyStackChanges) {
-                addedTasks.add(newTask);
-            } else if (currentTask != null) {
-                // The current task has bound callbacks, so just copy the data from the new task
-                // state and add it back into the list
-                currentTask.copyFrom(newTask);
-                newTask = currentTask;
-            }
-            allTasks.add(newTask);
-        }
-
-        // Sort all the tasks to ensure they are ordered correctly
-        for (int i = allTasks.size() - 1; i >= 0; i--) {
-            allTasks.get(i).temporarySortIndexInStack = i;
-        }
-        Collections.sort(allTasks, FREEFORM_COMPARATOR);
-
-        mStackTaskList.set(allTasks);
-        mRawTaskList = allTasks;
-
-        // Update the affiliated groupings
-        createAffiliatedGroupings(context);
-
-        // Only callback for the removed tasks after the stack has updated
-        int removedTaskCount = removedTasks.size();
-        Task newFrontMostTask = getStackFrontMostTask(false);
-        for (int i = 0; i < removedTaskCount; i++) {
-            mCb.onStackTaskRemoved(this, removedTasks.get(i), newFrontMostTask,
-                    AnimationProps.IMMEDIATE, false /* fromDockGesture */,
-                    true /* dismissRecentsIfAllRemoved */);
-        }
-
-        // Only callback for the newly added tasks after this stack has been updated
-        int addedTaskCount = addedTasks.size();
-        for (int i = 0; i < addedTaskCount; i++) {
-            mCb.onStackTaskAdded(this, addedTasks.get(i));
-        }
-
-        // Notify that the task stack has been updated
-        if (notifyStackChanges) {
-            mCb.onStackTasksUpdated(this);
-        }
-    }
-
-    /**
-     * Gets the front-most task in the stack.
-     */
-    public Task getStackFrontMostTask(boolean includeFreeformTasks) {
-        ArrayList<Task> stackTasks = mStackTaskList.getTasks();
-        if (stackTasks.isEmpty()) {
-            return null;
-        }
-        for (int i = stackTasks.size() - 1; i >= 0; i--) {
-            Task task = stackTasks.get(i);
-            if (!task.isFreeformTask() || includeFreeformTasks) {
-                return task;
-            }
-        }
-        return null;
-    }
-
-    /** Gets the task keys */
-    public ArrayList<Task.TaskKey> getTaskKeys() {
-        ArrayList<Task.TaskKey> taskKeys = new ArrayList<>();
-        ArrayList<Task> tasks = computeAllTasksList();
-        int taskCount = tasks.size();
-        for (int i = 0; i < taskCount; i++) {
-            Task task = tasks.get(i);
-            taskKeys.add(task.key);
-        }
-        return taskKeys;
-    }
-
-    /**
-     * Returns the set of "active" (non-historical) tasks in the stack that have been used recently.
-     */
-    public ArrayList<Task> getStackTasks() {
-        return mStackTaskList.getTasks();
-    }
-
-    /**
-     * Returns the set of "freeform" tasks in the stack.
-     */
-    public ArrayList<Task> getFreeformTasks() {
-        ArrayList<Task> freeformTasks = new ArrayList<>();
-        ArrayList<Task> tasks = mStackTaskList.getTasks();
-        int taskCount = tasks.size();
-        for (int i = 0; i < taskCount; i++) {
-            Task task = tasks.get(i);
-            if (task.isFreeformTask()) {
-                freeformTasks.add(task);
-            }
-        }
-        return freeformTasks;
-    }
-
-    /**
-     * Computes a set of all the active and historical tasks.
-     */
-    public ArrayList<Task> computeAllTasksList() {
-        ArrayList<Task> tasks = new ArrayList<>();
-        tasks.addAll(mStackTaskList.getTasks());
-        return tasks;
-    }
-
-    /**
-     * Returns the number of stack and freeform tasks.
-     */
-    public int getTaskCount() {
-        return mStackTaskList.size();
-    }
-
-    /**
-     * Returns the number of stack tasks.
-     */
-    public int getStackTaskCount() {
-        ArrayList<Task> tasks = mStackTaskList.getTasks();
-        int stackCount = 0;
-        int taskCount = tasks.size();
-        for (int i = 0; i < taskCount; i++) {
-            Task task = tasks.get(i);
-            if (!task.isFreeformTask()) {
-                stackCount++;
-            }
-        }
-        return stackCount;
-    }
-
-    /**
-     * Returns the number of freeform tasks.
-     */
-    public int getFreeformTaskCount() {
-        ArrayList<Task> tasks = mStackTaskList.getTasks();
-        int freeformCount = 0;
-        int taskCount = tasks.size();
-        for (int i = 0; i < taskCount; i++) {
-            Task task = tasks.get(i);
-            if (task.isFreeformTask()) {
-                freeformCount++;
-            }
-        }
-        return freeformCount;
-    }
-
-    /**
-     * Returns the task in stack tasks which is the launch target.
-     */
-    public Task getLaunchTarget() {
-        ArrayList<Task> tasks = mStackTaskList.getTasks();
-        int taskCount = tasks.size();
-        for (int i = 0; i < taskCount; i++) {
-            Task task = tasks.get(i);
-            if (task.isLaunchTarget) {
-                return task;
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Returns whether the next launch target should actually be the PiP task.
-     */
-    public boolean isNextLaunchTargetPip(long lastPipTime) {
-        Task launchTarget = getLaunchTarget();
-        Task nextLaunchTarget = getNextLaunchTargetRaw();
-        if (nextLaunchTarget != null && lastPipTime > 0) {
-            // If the PiP time is more recent than the next launch target, then launch the PiP task
-            return lastPipTime > nextLaunchTarget.key.lastActiveTime;
-        } else if (launchTarget != null && lastPipTime > 0 && getTaskCount() == 1) {
-            // Otherwise, if there is no next launch target, but there is a PiP, then launch
-            // the PiP task
-            return true;
-        }
-        return false;
-    }
-
-    /**
-     * Returns the task in stack tasks which should be launched next if Recents are toggled
-     * again, or null if there is no task to be launched. Callers should check
-     * {@link #isNextLaunchTargetPip(long)} before fetching the next raw launch target from the
-     * stack.
-     */
-    public Task getNextLaunchTarget() {
-        Task nextLaunchTarget = getNextLaunchTargetRaw();
-        if (nextLaunchTarget != null) {
-            return nextLaunchTarget;
-        }
-        return getStackTasks().get(getTaskCount() - 1);
-    }
-
-    private Task getNextLaunchTargetRaw() {
-        int taskCount = getTaskCount();
-        if (taskCount == 0) {
-            return null;
-        }
-        int launchTaskIndex = indexOfStackTask(getLaunchTarget());
-        if (launchTaskIndex != -1 && launchTaskIndex > 0) {
-            return getStackTasks().get(launchTaskIndex - 1);
-        }
-        return null;
-    }
-
-    /** Returns the index of this task in this current task stack */
-    public int indexOfStackTask(Task t) {
-        return mStackTaskList.indexOf(t);
-    }
-
-    /** Finds the task with the specified task id. */
-    public Task findTaskWithId(int taskId) {
-        ArrayList<Task> tasks = computeAllTasksList();
-        int taskCount = tasks.size();
-        for (int i = 0; i < taskCount; i++) {
-            Task task = tasks.get(i);
-            if (task.key.id == taskId) {
-                return task;
-            }
-        }
-        return null;
-    }
-
-    /******** Grouping ********/
-
-    /** Adds a group to the set */
-    public void addGroup(TaskGrouping group) {
-        mGroups.add(group);
-        mAffinitiesGroups.put(group.affiliation, group);
-    }
-
-    public void removeGroup(TaskGrouping group) {
-        mGroups.remove(group);
-        mAffinitiesGroups.remove(group.affiliation);
-    }
-
-    /** Returns the group with the specified affiliation. */
-    public TaskGrouping getGroupWithAffiliation(int affiliation) {
-        return mAffinitiesGroups.get(affiliation);
-    }
-
-    /**
-     * Temporary: This method will simulate affiliation groups
-     */
-    void createAffiliatedGroupings(Context context) {
-        mGroups.clear();
-        mAffinitiesGroups.clear();
-
-        if (RecentsDebugFlags.Static.EnableMockTaskGroups) {
-            ArrayMap<Task.TaskKey, Task> taskMap = new ArrayMap<>();
-            // Sort all tasks by increasing firstActiveTime of the task
-            ArrayList<Task> tasks = mStackTaskList.getTasks();
-            Collections.sort(tasks, new Comparator<Task>() {
-                @Override
-                public int compare(Task task, Task task2) {
-                    return Long.compare(task.key.firstActiveTime, task2.key.firstActiveTime);
-                }
-            });
-            // Create groups when sequential packages are the same
-            NamedCounter counter = new NamedCounter("task-group", "");
-            int taskCount = tasks.size();
-            String prevPackage = "";
-            int prevAffiliation = -1;
-            Random r = new Random();
-            int groupCountDown = RecentsDebugFlags.Static.MockTaskGroupsTaskCount;
-            for (int i = 0; i < taskCount; i++) {
-                Task t = tasks.get(i);
-                String packageName = t.key.getComponent().getPackageName();
-                packageName = "pkg";
-                TaskGrouping group;
-                if (packageName.equals(prevPackage) && groupCountDown > 0) {
-                    group = getGroupWithAffiliation(prevAffiliation);
-                    groupCountDown--;
-                } else {
-                    int affiliation = IndividualTaskIdOffset + t.key.id;
-                    group = new TaskGrouping(affiliation);
-                    addGroup(group);
-                    prevAffiliation = affiliation;
-                    prevPackage = packageName;
-                    groupCountDown = RecentsDebugFlags.Static.MockTaskGroupsTaskCount;
-                }
-                group.addTask(t);
-                taskMap.put(t.key, t);
-            }
-            // Sort groups by increasing latestActiveTime of the group
-            Collections.sort(mGroups, new Comparator<TaskGrouping>() {
-                @Override
-                public int compare(TaskGrouping taskGrouping, TaskGrouping taskGrouping2) {
-                    return Long.compare(taskGrouping.latestActiveTimeInGroup,
-                            taskGrouping2.latestActiveTimeInGroup);
-                }
-            });
-            // Sort group tasks by increasing firstActiveTime of the task, and also build a new list
-            // of tasks
-            int taskIndex = 0;
-            int groupCount = mGroups.size();
-            for (int i = 0; i < groupCount; i++) {
-                TaskGrouping group = mGroups.get(i);
-                Collections.sort(group.mTaskKeys, new Comparator<Task.TaskKey>() {
-                    @Override
-                    public int compare(Task.TaskKey taskKey, Task.TaskKey taskKey2) {
-                        return Long.compare(taskKey.firstActiveTime, taskKey2.firstActiveTime);
-                    }
-                });
-                ArrayList<Task.TaskKey> groupTasks = group.mTaskKeys;
-                int groupTaskCount = groupTasks.size();
-                for (int j = 0; j < groupTaskCount; j++) {
-                    tasks.set(taskIndex, taskMap.get(groupTasks.get(j)));
-                    taskIndex++;
-                }
-            }
-            mStackTaskList.set(tasks);
-        } else {
-            // Create the task groups
-            ArrayMap<Task.TaskKey, Task> tasksMap = new ArrayMap<>();
-            ArrayList<Task> tasks = mStackTaskList.getTasks();
-            int taskCount = tasks.size();
-            for (int i = 0; i < taskCount; i++) {
-                Task t = tasks.get(i);
-                TaskGrouping group;
-                if (RecentsDebugFlags.Static.EnableAffiliatedTaskGroups) {
-                    int affiliation = t.affiliationTaskId > 0 ? t.affiliationTaskId :
-                            IndividualTaskIdOffset + t.key.id;
-                    if (mAffinitiesGroups.containsKey(affiliation)) {
-                        group = getGroupWithAffiliation(affiliation);
-                    } else {
-                        group = new TaskGrouping(affiliation);
-                        addGroup(group);
-                    }
-                } else {
-                    group = new TaskGrouping(t.key.id);
-                    addGroup(group);
-                }
-                group.addTask(t);
-                tasksMap.put(t.key, t);
-            }
-            // Update the task colors for each of the groups
-            float minAlpha = context.getResources().getFloat(
-                    R.dimen.recents_task_affiliation_color_min_alpha_percentage);
-            int taskGroupCount = mGroups.size();
-            for (int i = 0; i < taskGroupCount; i++) {
-                TaskGrouping group = mGroups.get(i);
-                taskCount = group.getTaskCount();
-                // Ignore the groups that only have one task
-                if (taskCount <= 1) continue;
-                // Calculate the group color distribution
-                int affiliationColor = tasksMap.get(group.mTaskKeys.get(0)).affiliationColor;
-                float alphaStep = (1f - minAlpha) / taskCount;
-                float alpha = 1f;
-                for (int j = 0; j < taskCount; j++) {
-                    Task t = tasksMap.get(group.mTaskKeys.get(j));
-                    t.colorPrimary = Utilities.getColorWithOverlay(affiliationColor, Color.WHITE,
-                            alpha);
-                    alpha -= alphaStep;
-                }
-            }
-        }
-    }
-
-    /**
-     * Computes the components of tasks in this stack that have been removed as a result of a change
-     * in the specified package.
-     */
-    public ArraySet<ComponentName> computeComponentsRemoved(String packageName, int userId) {
-        // Identify all the tasks that should be removed as a result of the package being removed.
-        // Using a set to ensure that we callback once per unique component.
-        SystemServicesProxy ssp = Recents.getSystemServices();
-        ArraySet<ComponentName> existingComponents = new ArraySet<>();
-        ArraySet<ComponentName> removedComponents = new ArraySet<>();
-        ArrayList<Task.TaskKey> taskKeys = getTaskKeys();
-        int taskKeyCount = taskKeys.size();
-        for (int i = 0; i < taskKeyCount; i++) {
-            Task.TaskKey t = taskKeys.get(i);
-
-            // Skip if this doesn't apply to the current user
-            if (t.userId != userId) continue;
-
-            ComponentName cn = t.getComponent();
-            if (cn.getPackageName().equals(packageName)) {
-                if (existingComponents.contains(cn)) {
-                    // If we know that the component still exists in the package, then skip
-                    continue;
-                }
-                if (ssp.getActivityInfo(cn, userId) != null) {
-                    existingComponents.add(cn);
-                } else {
-                    removedComponents.add(cn);
-                }
-            }
-        }
-        return removedComponents;
-    }
-
-    @Override
-    public String toString() {
-        String str = "Stack Tasks (" + mStackTaskList.size() + "):\n";
-        ArrayList<Task> tasks = mStackTaskList.getTasks();
-        int taskCount = tasks.size();
-        for (int i = 0; i < taskCount; i++) {
-            str += "    " + tasks.get(i).toString() + "\n";
-        }
-        return str;
-    }
-
-    /**
-     * Given a list of tasks, returns a map of each task's key to the task.
-     */
-    private ArrayMap<Task.TaskKey, Task> createTaskKeyMapFromList(List<Task> tasks) {
-        ArrayMap<Task.TaskKey, Task> map = new ArrayMap<>(tasks.size());
-        int taskCount = tasks.size();
-        for (int i = 0; i < taskCount; i++) {
-            Task task = tasks.get(i);
-            map.put(task.key, task);
-        }
-        return map;
-    }
-
-    public void dump(String prefix, PrintWriter writer) {
-        String innerPrefix = prefix + "  ";
-
-        writer.print(prefix); writer.print(TAG);
-        writer.print(" numStackTasks="); writer.print(mStackTaskList.size());
-        writer.println();
-        ArrayList<Task> tasks = mStackTaskList.getTasks();
-        int taskCount = tasks.size();
-        for (int i = 0; i < taskCount; i++) {
-            tasks.get(i).dump(innerPrefix, writer);
-        }
-    }
-}
diff --git a/com/android/systemui/recents/model/ThumbnailData.java b/com/android/systemui/recents/model/ThumbnailData.java
deleted file mode 100644
index 33ff1b6..0000000
--- a/com/android/systemui/recents/model/ThumbnailData.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.model;
-
-import android.app.ActivityManager.TaskSnapshot;
-import android.graphics.Bitmap;
-import android.graphics.Rect;
-
-/**
- * Data for a single thumbnail.
- */
-public class ThumbnailData {
-
-    // TODO: Make these final once the non-snapshot path is removed.
-    public Bitmap thumbnail;
-    public int orientation;
-    public final Rect insets = new Rect();
-    public boolean reducedResolution;
-    public float scale;
-
-    public static ThumbnailData createFromTaskSnapshot(TaskSnapshot snapshot) {
-        ThumbnailData out = new ThumbnailData();
-        out.thumbnail = Bitmap.createHardwareBitmap(snapshot.getSnapshot());
-        out.insets.set(snapshot.getContentInsets());
-        out.orientation = snapshot.getOrientation();
-        out.reducedResolution = snapshot.isReducedResolution();
-        out.scale = snapshot.getScale();
-        return out;
-    }
-}
diff --git a/com/android/systemui/recents/views/AnimateableViewBounds.java b/com/android/systemui/recents/views/AnimateableViewBounds.java
index 7998ecb..b598ec6 100644
--- a/com/android/systemui/recents/views/AnimateableViewBounds.java
+++ b/com/android/systemui/recents/views/AnimateableViewBounds.java
@@ -22,7 +22,7 @@
 import android.view.ViewDebug;
 import android.view.ViewOutlineProvider;
 
-import com.android.systemui.recents.misc.Utilities;
+import com.android.systemui.shared.recents.utilities.Utilities;
 
 /* An outline provider that has a clip and outline that can be animated. */
 public class AnimateableViewBounds extends ViewOutlineProvider {
diff --git a/com/android/systemui/recents/views/DockState.java b/com/android/systemui/recents/views/DockState.java
new file mode 100644
index 0000000..59f2868
--- /dev/null
+++ b/com/android/systemui/recents/views/DockState.java
@@ -0,0 +1,351 @@
+/*
+ * Copyright (C) 2017 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.views;
+
+import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT;
+import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
+import static android.view.WindowManager.DOCKED_BOTTOM;
+import static android.view.WindowManager.DOCKED_INVALID;
+import static android.view.WindowManager.DOCKED_LEFT;
+import static android.view.WindowManager.DOCKED_RIGHT;
+import static android.view.WindowManager.DOCKED_TOP;
+
+import android.animation.Animator;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.annotation.IntDef;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.drawable.ColorDrawable;
+import android.util.IntProperty;
+import android.view.animation.Interpolator;
+
+import com.android.internal.policy.DockedDividerUtils;
+import com.android.systemui.Interpolators;
+import com.android.systemui.R;
+import com.android.systemui.recents.Recents;
+import com.android.systemui.shared.recents.utilities.Utilities;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+
+/**
+ * The various possible dock states when dragging and dropping a task.
+ */
+public class DockState implements DropTarget {
+
+    public static final int DOCK_AREA_BG_COLOR = 0xFFffffff;
+    public static final int DOCK_AREA_GRID_BG_COLOR = 0xFF000000;
+
+    // The rotation to apply to the hint text
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({HORIZONTAL, VERTICAL})
+    public @interface TextOrientation {}
+    private static final int HORIZONTAL = 0;
+    private static final int VERTICAL = 1;
+
+    private static final int DOCK_AREA_ALPHA = 80;
+    public static final DockState NONE = new DockState(DOCKED_INVALID, -1, 80, 255, HORIZONTAL,
+            null, null, null);
+    public static final DockState LEFT = new DockState(DOCKED_LEFT,
+            DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT, DOCK_AREA_ALPHA, 0, VERTICAL,
+            new RectF(0, 0, 0.125f, 1), new RectF(0, 0, 0.125f, 1),
+            new RectF(0, 0, 0.5f, 1));
+    public static final DockState TOP = new DockState(DOCKED_TOP,
+            DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT, DOCK_AREA_ALPHA, 0, HORIZONTAL,
+            new RectF(0, 0, 1, 0.125f), new RectF(0, 0, 1, 0.125f),
+            new RectF(0, 0, 1, 0.5f));
+    public static final DockState RIGHT = new DockState(DOCKED_RIGHT,
+            DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT, DOCK_AREA_ALPHA, 0, VERTICAL,
+            new RectF(0.875f, 0, 1, 1), new RectF(0.875f, 0, 1, 1),
+            new RectF(0.5f, 0, 1, 1));
+    public static final DockState BOTTOM = new DockState(DOCKED_BOTTOM,
+            DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT, DOCK_AREA_ALPHA, 0, HORIZONTAL,
+            new RectF(0, 0.875f, 1, 1), new RectF(0, 0.875f, 1, 1),
+            new RectF(0, 0.5f, 1, 1));
+
+    @Override
+    public boolean acceptsDrop(int x, int y, int width, int height, Rect insets,
+            boolean isCurrentTarget) {
+        if (isCurrentTarget) {
+            getMappedRect(expandedTouchDockArea, width, height, mTmpRect);
+            return mTmpRect.contains(x, y);
+        } else {
+            getMappedRect(touchArea, width, height, mTmpRect);
+            updateBoundsWithSystemInsets(mTmpRect, insets);
+            return mTmpRect.contains(x, y);
+        }
+    }
+
+    // Represents the view state of this dock state
+    public static class ViewState {
+        private static final IntProperty<ViewState> HINT_ALPHA =
+                new IntProperty<ViewState>("drawableAlpha") {
+                    @Override
+                    public void setValue(ViewState object, int alpha) {
+                        object.mHintTextAlpha = alpha;
+                        object.dockAreaOverlay.invalidateSelf();
+                    }
+
+                    @Override
+                    public Integer get(ViewState object) {
+                        return object.mHintTextAlpha;
+                    }
+                };
+
+        public final int dockAreaAlpha;
+        public final ColorDrawable dockAreaOverlay;
+        public final int hintTextAlpha;
+        public final int hintTextOrientation;
+
+        private final int mHintTextResId;
+        private String mHintText;
+        private Paint mHintTextPaint;
+        private Point mHintTextBounds = new Point();
+        private int mHintTextAlpha = 255;
+        private AnimatorSet mDockAreaOverlayAnimator;
+        private Rect mTmpRect = new Rect();
+
+        private ViewState(int areaAlpha, int hintAlpha, @TextOrientation int hintOrientation,
+                int hintTextResId) {
+            dockAreaAlpha = areaAlpha;
+            dockAreaOverlay = new ColorDrawable(Recents.getConfiguration().isGridEnabled
+                    ? DOCK_AREA_GRID_BG_COLOR : DOCK_AREA_BG_COLOR);
+            dockAreaOverlay.setAlpha(0);
+            hintTextAlpha = hintAlpha;
+            hintTextOrientation = hintOrientation;
+            mHintTextResId = hintTextResId;
+            mHintTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+            mHintTextPaint.setColor(Color.WHITE);
+        }
+
+        /**
+         * Updates the view state with the given context.
+         */
+        public void update(Context context) {
+            Resources res = context.getResources();
+            mHintText = context.getString(mHintTextResId);
+            mHintTextPaint.setTextSize(res.getDimensionPixelSize(
+                    R.dimen.recents_drag_hint_text_size));
+            mHintTextPaint.getTextBounds(mHintText, 0, mHintText.length(), mTmpRect);
+            mHintTextBounds.set((int) mHintTextPaint.measureText(mHintText), mTmpRect.height());
+        }
+
+        /**
+         * Draws the current view state.
+         */
+        public void draw(Canvas canvas) {
+            // Draw the overlay background
+            if (dockAreaOverlay.getAlpha() > 0) {
+                dockAreaOverlay.draw(canvas);
+            }
+
+            // Draw the hint text
+            if (mHintTextAlpha > 0) {
+                Rect bounds = dockAreaOverlay.getBounds();
+                int x = bounds.left + (bounds.width() - mHintTextBounds.x) / 2;
+                int y = bounds.top + (bounds.height() + mHintTextBounds.y) / 2;
+                mHintTextPaint.setAlpha(mHintTextAlpha);
+                if (hintTextOrientation == VERTICAL) {
+                    canvas.save();
+                    canvas.rotate(-90f, bounds.centerX(), bounds.centerY());
+                }
+                canvas.drawText(mHintText, x, y, mHintTextPaint);
+                if (hintTextOrientation == VERTICAL) {
+                    canvas.restore();
+                }
+            }
+        }
+
+        /**
+         * Creates a new bounds and alpha animation.
+         */
+        public void startAnimation(Rect bounds, int areaAlpha, int hintAlpha, int duration,
+                Interpolator interpolator, boolean animateAlpha, boolean animateBounds) {
+            if (mDockAreaOverlayAnimator != null) {
+                mDockAreaOverlayAnimator.cancel();
+            }
+
+            ObjectAnimator anim;
+            ArrayList<Animator> animators = new ArrayList<>();
+            if (dockAreaOverlay.getAlpha() != areaAlpha) {
+                if (animateAlpha) {
+                    anim = ObjectAnimator.ofInt(dockAreaOverlay,
+                            Utilities.DRAWABLE_ALPHA, dockAreaOverlay.getAlpha(), areaAlpha);
+                    anim.setDuration(duration);
+                    anim.setInterpolator(interpolator);
+                    animators.add(anim);
+                } else {
+                    dockAreaOverlay.setAlpha(areaAlpha);
+                }
+            }
+            if (mHintTextAlpha != hintAlpha) {
+                if (animateAlpha) {
+                    anim = ObjectAnimator.ofInt(this, HINT_ALPHA, mHintTextAlpha,
+                            hintAlpha);
+                    anim.setDuration(150);
+                    anim.setInterpolator(hintAlpha > mHintTextAlpha
+                            ? Interpolators.ALPHA_IN
+                            : Interpolators.ALPHA_OUT);
+                    animators.add(anim);
+                } else {
+                    mHintTextAlpha = hintAlpha;
+                    dockAreaOverlay.invalidateSelf();
+                }
+            }
+            if (bounds != null && !dockAreaOverlay.getBounds().equals(bounds)) {
+                if (animateBounds) {
+                    PropertyValuesHolder prop = PropertyValuesHolder.ofObject(
+                            Utilities.DRAWABLE_RECT, Utilities.RECT_EVALUATOR,
+                            new Rect(dockAreaOverlay.getBounds()), bounds);
+                    anim = ObjectAnimator.ofPropertyValuesHolder(dockAreaOverlay, prop);
+                    anim.setDuration(duration);
+                    anim.setInterpolator(interpolator);
+                    animators.add(anim);
+                } else {
+                    dockAreaOverlay.setBounds(bounds);
+                }
+            }
+            if (!animators.isEmpty()) {
+                mDockAreaOverlayAnimator = new AnimatorSet();
+                mDockAreaOverlayAnimator.playTogether(animators);
+                mDockAreaOverlayAnimator.start();
+            }
+        }
+    }
+
+    public final int dockSide;
+    public final int createMode;
+    public final ViewState viewState;
+    private final RectF touchArea;
+    private final RectF dockArea;
+    private final RectF expandedTouchDockArea;
+    private static final Rect mTmpRect = new Rect();
+
+    /**
+     * @param createMode used to pass to ActivityManager to dock the task
+     * @param touchArea the area in which touch will initiate this dock state
+     * @param dockArea the visible dock area
+     * @param expandedTouchDockArea the area in which touch will continue to dock after entering
+     *                              the initial touch area.  This is also the new dock area to
+     *                              draw.
+     */
+    DockState(int dockSide, int createMode, int dockAreaAlpha, int hintTextAlpha,
+            @TextOrientation int hintTextOrientation, RectF touchArea, RectF dockArea,
+            RectF expandedTouchDockArea) {
+        this.dockSide = dockSide;
+        this.createMode = createMode;
+        this.viewState = new ViewState(dockAreaAlpha, hintTextAlpha, hintTextOrientation,
+                R.string.recents_drag_hint_message);
+        this.dockArea = dockArea;
+        this.touchArea = touchArea;
+        this.expandedTouchDockArea = expandedTouchDockArea;
+    }
+
+    /**
+     * Updates the dock state with the given context.
+     */
+    public void update(Context context) {
+        viewState.update(context);
+    }
+
+    /**
+     * Returns the docked task bounds with the given {@param width} and {@param height}.
+     */
+    public Rect getPreDockedBounds(int width, int height, Rect insets) {
+        getMappedRect(dockArea, width, height, mTmpRect);
+        return updateBoundsWithSystemInsets(mTmpRect, insets);
+    }
+
+    /**
+     * Returns the expanded docked task bounds with the given {@param width} and
+     * {@param height}.
+     */
+    public Rect getDockedBounds(int width, int height, int dividerSize, Rect insets,
+            Resources res) {
+        // Calculate the docked task bounds
+        boolean isHorizontalDivision =
+                res.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
+        int position = DockedDividerUtils.calculateMiddlePosition(isHorizontalDivision,
+                insets, width, height, dividerSize);
+        Rect newWindowBounds = new Rect();
+        DockedDividerUtils.calculateBoundsForPosition(position, dockSide, newWindowBounds,
+                width, height, dividerSize);
+        return newWindowBounds;
+    }
+
+    /**
+     * Returns the task stack bounds with the given {@param width} and
+     * {@param height}.
+     */
+    public Rect getDockedTaskStackBounds(Rect displayRect, int width, int height,
+            int dividerSize, Rect insets, TaskStackLayoutAlgorithm layoutAlgorithm,
+            Resources res, Rect windowRectOut) {
+        // Calculate the inverse docked task bounds
+        boolean isHorizontalDivision =
+                res.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
+        int position = DockedDividerUtils.calculateMiddlePosition(isHorizontalDivision,
+                insets, width, height, dividerSize);
+        DockedDividerUtils.calculateBoundsForPosition(position,
+                DockedDividerUtils.invertDockSide(dockSide), windowRectOut, width, height,
+                dividerSize);
+
+        // Calculate the task stack bounds from the new window bounds
+        Rect taskStackBounds = new Rect();
+        // If the task stack bounds is specifically under the dock area, then ignore the top
+        // inset
+        int top = dockArea.bottom < 1f
+                ? 0
+                : insets.top;
+        // For now, ignore the left insets since we always dock on the left and show Recents
+        // on the right
+        layoutAlgorithm.getTaskStackBounds(displayRect, windowRectOut, top, 0, insets.right,
+                taskStackBounds);
+        return taskStackBounds;
+    }
+
+    /**
+     * Returns the expanded bounds in certain dock sides such that the bounds account for the
+     * system insets (namely the vertical nav bar).  This call modifies and returns the given
+     * {@param bounds}.
+     */
+    private Rect updateBoundsWithSystemInsets(Rect bounds, Rect insets) {
+        if (dockSide == DOCKED_LEFT) {
+            bounds.right += insets.left;
+        } else if (dockSide == DOCKED_RIGHT) {
+            bounds.left -= insets.right;
+        }
+        return bounds;
+    }
+
+    /**
+     * Returns the mapped rect to the given dimensions.
+     */
+    private void getMappedRect(RectF bounds, int width, int height, Rect out) {
+        out.set((int) (bounds.left * width), (int) (bounds.top * height),
+                (int) (bounds.right * width), (int) (bounds.bottom * height));
+    }
+}
\ No newline at end of file
diff --git a/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java b/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java
deleted file mode 100644
index 035c058..0000000
--- a/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java
+++ /dev/null
@@ -1,170 +0,0 @@
-/*
- * Copyright (C) 2014 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.views;
-
-import android.content.Context;
-import android.graphics.RectF;
-import android.util.ArrayMap;
-
-import com.android.systemui.R;
-import com.android.systemui.recents.model.Task;
-
-import java.util.Collections;
-import java.util.List;
-
-/**
- * The layout logic for the contents of the freeform workspace.
- */
-public class FreeformWorkspaceLayoutAlgorithm {
-
-    // Optimization, allows for quick lookup of task -> rect
-    private ArrayMap<Task.TaskKey, RectF> mTaskRectMap = new ArrayMap<>();
-
-    private int mTaskPadding;
-
-    public FreeformWorkspaceLayoutAlgorithm(Context context) {
-        reloadOnConfigurationChange(context);
-    }
-
-    /**
-     * Reloads the layout for the current configuration.
-     */
-    public void reloadOnConfigurationChange(Context context) {
-        // This is applied to the edges of each task
-        mTaskPadding = context.getResources().getDimensionPixelSize(
-                R.dimen.recents_freeform_layout_task_padding) / 2;
-    }
-
-    /**
-     * Updates the layout for each of the freeform workspace tasks.  This is called after the stack
-     * layout is updated.
-     */
-    public void update(List<Task> freeformTasks, TaskStackLayoutAlgorithm stackLayout) {
-        Collections.reverse(freeformTasks);
-        mTaskRectMap.clear();
-
-        int numFreeformTasks = stackLayout.mNumFreeformTasks;
-        if (!freeformTasks.isEmpty()) {
-
-            // Normalize the widths so that we can calculate the best layout below
-            int workspaceWidth = stackLayout.mFreeformRect.width();
-            int workspaceHeight = stackLayout.mFreeformRect.height();
-            float normalizedWorkspaceWidth = (float) workspaceWidth / workspaceHeight;
-            float normalizedWorkspaceHeight = 1f;
-            float[] normalizedTaskWidths = new float[numFreeformTasks];
-            for (int i = 0; i < numFreeformTasks; i++) {
-                Task task = freeformTasks.get(i);
-                float rowTaskWidth;
-                if (task.bounds != null) {
-                    rowTaskWidth = (float) task.bounds.width() / task.bounds.height();
-                } else {
-                    // If this is a stack task that was dragged into the freeform workspace, then
-                    // the task will not yet have an associated bounds, so assume the full workspace
-                    // width for the time being
-                    rowTaskWidth = normalizedWorkspaceWidth;
-                }
-                // Bound the task width to the workspace width so that at the worst case, it will
-                // fit its own row
-                normalizedTaskWidths[i] = Math.min(rowTaskWidth, normalizedWorkspaceWidth);
-            }
-
-            // Determine the scale to best fit each of the tasks in the workspace
-            float rowScale = 0.85f;
-            float rowWidth = 0f;
-            float maxRowWidth = 0f;
-            int rowCount = 1;
-            for (int i = 0; i < numFreeformTasks;) {
-                float width = normalizedTaskWidths[i] * rowScale;
-                if (rowWidth + width > normalizedWorkspaceWidth) {
-                    // That is too long for this row, create new row
-                    if ((rowCount + 1) * rowScale > normalizedWorkspaceHeight) {
-                        // The new row is too high, so we need to try fitting again.  Update the
-                        // scale to be the smaller of the scale needed to fit the task in the
-                        // previous row, or the scale needed to fit the new row
-                        rowScale = Math.min(normalizedWorkspaceWidth / (rowWidth + width),
-                                normalizedWorkspaceHeight / (rowCount + 1));
-                        rowCount = 1;
-                        rowWidth = 0;
-                        i = 0;
-                    } else {
-                        // The new row fits, so continue
-                        rowWidth = width;
-                        rowCount++;
-                        i++;
-                    }
-                } else {
-                    // Task is OK in this row
-                    rowWidth += width;
-                    i++;
-                }
-                maxRowWidth = Math.max(rowWidth, maxRowWidth);
-            }
-
-            // Normalize each of the actual rects to that scale
-            float defaultRowLeft = ((1f - (maxRowWidth / normalizedWorkspaceWidth)) *
-                    workspaceWidth) / 2f;
-            float rowLeft = defaultRowLeft;
-            float rowTop = ((1f - (rowScale * rowCount)) * workspaceHeight) / 2f;
-            float rowHeight = rowScale * workspaceHeight;
-            for (int i = 0; i < numFreeformTasks; i++) {
-                Task task = freeformTasks.get(i);
-                float width = rowHeight * normalizedTaskWidths[i];
-                if (rowLeft + width > workspaceWidth) {
-                    // This goes on the next line
-                    rowTop += rowHeight;
-                    rowLeft = defaultRowLeft;
-                }
-                RectF rect = new RectF(rowLeft, rowTop, rowLeft + width, rowTop + rowHeight);
-                rect.inset(mTaskPadding, mTaskPadding);
-                rowLeft += width;
-                mTaskRectMap.put(task.key, rect);
-            }
-        }
-    }
-
-    /**
-     * Returns whether the transform is available for the given task.
-     */
-    public boolean isTransformAvailable(Task task, TaskStackLayoutAlgorithm stackLayout) {
-        if (stackLayout.mNumFreeformTasks == 0 || task == null) {
-            return false;
-        }
-        return mTaskRectMap.containsKey(task.key);
-    }
-
-    /**
-     * Returns the transform for the given task.  Any rect returned will be offset by the actual
-     * transform for the freeform workspace.
-     */
-    public TaskViewTransform getTransform(Task task, TaskViewTransform transformOut,
-            TaskStackLayoutAlgorithm stackLayout) {
-        if (mTaskRectMap.containsKey(task.key)) {
-            final RectF ffRect = mTaskRectMap.get(task.key);
-
-            transformOut.scale = 1f;
-            transformOut.alpha = 1f;
-            transformOut.translationZ = stackLayout.mMaxTranslationZ;
-            transformOut.dimAlpha = 0f;
-            transformOut.viewOutlineAlpha = TaskStackLayoutAlgorithm.OUTLINE_ALPHA_MAX_VALUE;
-            transformOut.rect.set(ffRect);
-            transformOut.rect.offset(stackLayout.mFreeformRect.left, stackLayout.mFreeformRect.top);
-            transformOut.visible = true;
-            return transformOut;
-        }
-        return null;
-    }
-}
diff --git a/com/android/systemui/recents/views/RecentsTransitionHelper.java b/com/android/systemui/recents/views/RecentsTransitionHelper.java
index ee05d81..25c2fc9 100644
--- a/com/android/systemui/recents/views/RecentsTransitionHelper.java
+++ b/com/android/systemui/recents/views/RecentsTransitionHelper.java
@@ -19,7 +19,6 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
@@ -31,8 +30,6 @@
 import android.app.ActivityOptions.OnAnimationStartedListener;
 import android.content.Context;
 import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Color;
 import android.graphics.GraphicBuffer;
 import android.graphics.Rect;
 import android.os.Bundle;
@@ -59,8 +56,8 @@
 import com.android.systemui.recents.events.component.ScreenPinningRequestEvent;
 import com.android.systemui.recents.events.component.SetWaitingForTransitionStartEvent;
 import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.model.Task;
-import com.android.systemui.recents.model.TaskStack;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.model.TaskStack;
 import com.android.systemui.statusbar.phone.StatusBar;
 
 import java.util.ArrayList;
@@ -188,20 +185,9 @@
         } else {
             LaunchTaskStartedEvent launchStartedEvent = new LaunchTaskStartedEvent(taskView,
                     screenPinningRequested);
-            if (task.group != null && !task.group.isFrontMostTask(task)) {
-                launchStartedEvent.addPostAnimationCallback(new Runnable() {
-                    @Override
-                    public void run() {
-                        startTaskActivity(stack, task, taskView, opts, transitionFuture,
-                                windowingMode, activityType);
-                    }
-                });
-                EventBus.getDefault().send(launchStartedEvent);
-            } else {
-                EventBus.getDefault().send(launchStartedEvent);
-                startTaskActivity(stack, task, taskView, opts, transitionFuture,
-                        windowingMode, activityType);
-            }
+            EventBus.getDefault().send(launchStartedEvent);
+            startTaskActivity(stack, task, taskView, opts, transitionFuture, windowingMode,
+                    activityType);
         }
         Recents.getSystemServices().sendCloseSystemWindows(
                 StatusBar.SYSTEM_DIALOG_REASON_HOME_KEY);
@@ -329,7 +315,6 @@
 
         // If this is a full screen stack, the transition will be towards the single, full screen
         // task. We only need the transition spec for this task.
-        List<AppTransitionAnimationSpec> specs = new ArrayList<>();
 
         // TODO: Sometimes targetStackId is not initialized after reboot, so we also have to
         // check for INVALID_STACK_ID (now WINDOWING_MODE_UNDEFINED)
@@ -338,6 +323,7 @@
                 || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
                 || activityType == ACTIVITY_TYPE_ASSISTANT
                 || windowingMode == WINDOWING_MODE_UNDEFINED) {
+            List<AppTransitionAnimationSpec> specs = new ArrayList<>();
             if (taskView == null) {
                 specs.add(composeOffscreenAnimationSpec(task, offscreenTaskRect));
             } else {
@@ -351,34 +337,7 @@
             }
             return specs;
         }
-
-        // Otherwise, for freeform tasks, create a new animation spec for each task we have to
-        // launch
-        TaskStack stack = stackView.getStack();
-        ArrayList<Task> tasks = stack.getStackTasks();
-        int taskCount = tasks.size();
-        for (int i = taskCount - 1; i >= 0; i--) {
-            Task t = tasks.get(i);
-            if (t.isFreeformTask() || windowingMode == WINDOWING_MODE_FREEFORM) {
-                TaskView tv = stackView.getChildViewForTask(t);
-                if (tv == null) {
-                    // TODO: Create a different animation task rect for this case (though it should
-                    //       never happen)
-                    specs.add(composeOffscreenAnimationSpec(t, offscreenTaskRect));
-                } else {
-                    mTmpTransform.fillIn(taskView);
-                    stackLayout.transformToScreenCoordinates(mTmpTransform,
-                            null /* windowOverrideRect */);
-                    AppTransitionAnimationSpec spec = composeAnimationSpec(stackView, tv,
-                            mTmpTransform, true /* addHeaderBitmap */);
-                    if (spec != null) {
-                        specs.add(spec);
-                    }
-                }
-            }
-        }
-
-        return specs;
+        return Collections.emptyList();
     }
 
     /**
@@ -462,7 +421,7 @@
         // force the task thumbnail to full stackView height immediately causing the transition
         // jarring.
         if (!Recents.getConfiguration().isLowRamDevice && taskView.getTask() !=
-                stackView.getStack().getStackFrontMostTask(false /* includeFreeformTasks */)) {
+                stackView.getStack().getStackFrontMostTask()) {
             taskRect.bottom = taskRect.top + stackView.getMeasuredHeight();
         }
         return new AppTransitionAnimationSpec(taskView.getTask().key.id, b, taskRect);
diff --git a/com/android/systemui/recents/views/RecentsView.java b/com/android/systemui/recents/views/RecentsView.java
index c7edb9a..5f12a04 100644
--- a/com/android/systemui/recents/views/RecentsView.java
+++ b/com/android/systemui/recents/views/RecentsView.java
@@ -16,10 +16,6 @@
 
 package com.android.systemui.recents.views;
 
-import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
-
-import android.animation.Animator;
-import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
 import android.animation.ValueAnimator.AnimatorUpdateListener;
 import android.app.ActivityOptions.OnAnimationStartedListener;
@@ -57,7 +53,6 @@
 import com.android.systemui.recents.RecentsActivity;
 import com.android.systemui.recents.RecentsActivityLaunchState;
 import com.android.systemui.recents.RecentsConfiguration;
-import com.android.systemui.recents.RecentsDebugFlags;
 import com.android.systemui.recents.events.EventBus;
 import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimationStarted;
 import com.android.systemui.recents.events.activity.DockedFirstAnimationFrameEvent;
@@ -78,9 +73,9 @@
 import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent;
 import com.android.systemui.recents.misc.ReferenceCountedTrigger;
 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.recents.model.TaskStack;
+import com.android.systemui.shared.recents.utilities.Utilities;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.model.TaskStack;
 import com.android.systemui.recents.views.RecentsTransitionHelper.AnimationSpecComposer;
 import com.android.systemui.recents.views.RecentsTransitionHelper.AppTransitionAnimationSpecsFuture;
 import com.android.systemui.stackdivider.WindowManagerProxy;
@@ -114,7 +109,6 @@
     private final int mStackButtonShadowColor;
 
     private boolean mAwaitingFirstLayout = true;
-    private boolean mLastTaskLaunchedWasFreeform;
 
     @ViewDebug.ExportedProperty(category="recents")
     Rect mSystemInsets = new Rect();
@@ -165,22 +159,20 @@
         mEmptyView = (TextView) inflater.inflate(R.layout.recents_empty, this, false);
         addView(mEmptyView);
 
-        if (RecentsDebugFlags.Static.EnableStackActionButton) {
-            if (mStackActionButton != null) {
-                removeView(mStackActionButton);
-            }
-            mStackActionButton = (TextView) inflater.inflate(Recents.getConfiguration()
-                            .isLowRamDevice
-                        ? R.layout.recents_low_ram_stack_action_button
-                        : R.layout.recents_stack_action_button,
-                    this, false);
-
-            mStackButtonShadowRadius = mStackActionButton.getShadowRadius();
-            mStackButtonShadowDistance = new PointF(mStackActionButton.getShadowDx(),
-                    mStackActionButton.getShadowDy());
-            mStackButtonShadowColor = mStackActionButton.getShadowColor();
-            addView(mStackActionButton);
+        if (mStackActionButton != null) {
+            removeView(mStackActionButton);
         }
+        mStackActionButton = (TextView) inflater.inflate(Recents.getConfiguration()
+                        .isLowRamDevice
+                    ? R.layout.recents_low_ram_stack_action_button
+                    : R.layout.recents_stack_action_button,
+                this, false);
+
+        mStackButtonShadowRadius = mStackActionButton.getShadowRadius();
+        mStackButtonShadowDistance = new PointF(mStackActionButton.getShadowDx(),
+                mStackActionButton.getShadowDy());
+        mStackButtonShadowColor = mStackActionButton.getShadowColor();
+        addView(mStackActionButton);
 
         reevaluateStyles();
     }
@@ -232,7 +224,6 @@
 
         // Reset the state
         mAwaitingFirstLayout = !isResumingFromVisible;
-        mLastTaskLaunchedWasFreeform = false;
 
         // Update the stack
         mTaskStackView.onReload(isResumingFromVisible);
@@ -319,13 +310,6 @@
         }
     }
 
-    /**
-     * Returns whether the last task launched was in the freeform stack or not.
-     */
-    public boolean isLastTaskLaunchedFreeform() {
-        return mLastTaskLaunchedWasFreeform;
-    }
-
     /** Launches the focused task from the first stack if possible */
     public boolean launchFocusedTask(int logEvent) {
         if (mTaskStackView != null) {
@@ -371,9 +355,7 @@
         mEmptyView.setText(msgResId);
         mEmptyView.setVisibility(View.VISIBLE);
         mEmptyView.bringToFront();
-        if (RecentsDebugFlags.Static.EnableStackActionButton) {
-            mStackActionButton.bringToFront();
-        }
+        mStackActionButton.bringToFront();
     }
 
     /**
@@ -383,9 +365,7 @@
         mEmptyView.setVisibility(View.INVISIBLE);
         mTaskStackView.setVisibility(View.VISIBLE);
         mTaskStackView.bringToFront();
-        if (RecentsDebugFlags.Static.EnableStackActionButton) {
-            mStackActionButton.bringToFront();
-        }
+        mStackActionButton.bringToFront();
     }
 
     /**
@@ -433,13 +413,11 @@
                     MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST));
         }
 
-        if (RecentsDebugFlags.Static.EnableStackActionButton) {
-            // Measure the stack action button within the constraints of the space above the stack
-            Rect buttonBounds = mTaskStackView.mLayoutAlgorithm.getStackActionButtonRect();
-            measureChild(mStackActionButton,
-                    MeasureSpec.makeMeasureSpec(buttonBounds.width(), MeasureSpec.AT_MOST),
-                    MeasureSpec.makeMeasureSpec(buttonBounds.height(), MeasureSpec.AT_MOST));
-        }
+        // Measure the stack action button within the constraints of the space above the stack
+        Rect buttonBounds = mTaskStackView.mLayoutAlgorithm.getStackActionButtonRect();
+        measureChild(mStackActionButton,
+                MeasureSpec.makeMeasureSpec(buttonBounds.width(), MeasureSpec.AT_MOST),
+                MeasureSpec.makeMeasureSpec(buttonBounds.height(), MeasureSpec.AT_MOST));
 
         setMeasuredDimension(width, height);
     }
@@ -473,13 +451,11 @@
         mBackgroundScrim.setBounds(left, top, right, bottom);
         mMultiWindowBackgroundScrim.setBounds(0, 0, mTmpDisplaySize.x, mTmpDisplaySize.y);
 
-        if (RecentsDebugFlags.Static.EnableStackActionButton) {
-            // Layout the stack action button such that its drawable is start-aligned with the
-            // stack, vertically centered in the available space above the stack
-            Rect buttonBounds = getStackActionButtonBoundsFromStackLayout();
-            mStackActionButton.layout(buttonBounds.left, buttonBounds.top, buttonBounds.right,
-                    buttonBounds.bottom);
-        }
+        // Layout the stack action button such that its drawable is start-aligned with the
+        // stack, vertically centered in the available space above the stack
+        Rect buttonBounds = getStackActionButtonBoundsFromStackLayout();
+        mStackActionButton.layout(buttonBounds.left, buttonBounds.top, buttonBounds.right,
+                buttonBounds.bottom);
 
         if (mAwaitingFirstLayout) {
             mAwaitingFirstLayout = false;
@@ -521,7 +497,7 @@
     public void onDrawForeground(Canvas canvas) {
         super.onDrawForeground(canvas);
 
-        ArrayList<TaskStack.DockState> visDockStates = mTouchHandler.getVisibleDockStates();
+        ArrayList<DockState> visDockStates = mTouchHandler.getVisibleDockStates();
         for (int i = visDockStates.size() - 1; i >= 0; i--) {
             visDockStates.get(i).viewState.draw(canvas);
         }
@@ -529,7 +505,7 @@
 
     @Override
     protected boolean verifyDrawable(Drawable who) {
-        ArrayList<TaskStack.DockState> visDockStates = mTouchHandler.getVisibleDockStates();
+        ArrayList<DockState> visDockStates = mTouchHandler.getVisibleDockStates();
         for (int i = visDockStates.size() - 1; i >= 0; i--) {
             Drawable d = visDockStates.get(i).viewState.dockAreaOverlay;
             if (d == who) {
@@ -542,7 +518,6 @@
     /**** EventBus Events ****/
 
     public final void onBusEvent(LaunchTaskEvent event) {
-        mLastTaskLaunchedWasFreeform = event.task.isFreeformTask();
         mTransitionHelper.launchTaskFromRecents(getStack(), event.task, mTaskStackView,
                 event.taskView, event.screenPinningRequested, event.targetWindowingMode,
                 event.targetActivityType);
@@ -553,10 +528,8 @@
 
     public final void onBusEvent(DismissRecentsToHomeAnimationStarted event) {
         int taskViewExitToHomeDuration = TaskStackAnimationHelper.EXIT_TO_HOME_TRANSLATION_DURATION;
-        if (RecentsDebugFlags.Static.EnableStackActionButton) {
-            // Hide the stack action button
-            EventBus.getDefault().send(new HideStackActionButtonEvent());
-        }
+        // Hide the stack action button
+        EventBus.getDefault().send(new HideStackActionButtonEvent());
         animateBackgroundScrim(0f, taskViewExitToHomeDuration);
 
         if (Recents.getConfiguration().isLowRamDevice) {
@@ -566,8 +539,8 @@
 
     public final void onBusEvent(DragStartEvent event) {
         updateVisibleDockRegions(Recents.getConfiguration().getDockStatesForCurrentOrientation(),
-                true /* isDefaultDockState */, TaskStack.DockState.NONE.viewState.dockAreaAlpha,
-                TaskStack.DockState.NONE.viewState.hintTextAlpha,
+                true /* isDefaultDockState */, DockState.NONE.viewState.dockAreaAlpha,
+                DockState.NONE.viewState.hintTextAlpha,
                 true /* animateAlpha */, false /* animateBounds */);
 
         // Temporarily hide the stack action button without changing visibility
@@ -581,15 +554,15 @@
     }
 
     public final void onBusEvent(DragDropTargetChangedEvent event) {
-        if (event.dropTarget == null || !(event.dropTarget instanceof TaskStack.DockState)) {
+        if (event.dropTarget == null || !(event.dropTarget instanceof DockState)) {
             updateVisibleDockRegions(
                     Recents.getConfiguration().getDockStatesForCurrentOrientation(),
-                    true /* isDefaultDockState */, TaskStack.DockState.NONE.viewState.dockAreaAlpha,
-                    TaskStack.DockState.NONE.viewState.hintTextAlpha,
+                    true /* isDefaultDockState */, DockState.NONE.viewState.dockAreaAlpha,
+                    DockState.NONE.viewState.hintTextAlpha,
                     true /* animateAlpha */, true /* animateBounds */);
         } else {
-            final TaskStack.DockState dockState = (TaskStack.DockState) event.dropTarget;
-            updateVisibleDockRegions(new TaskStack.DockState[] {dockState},
+            final DockState dockState = (DockState) event.dropTarget;
+            updateVisibleDockRegions(new DockState[] {dockState},
                     false /* isDefaultDockState */, -1, -1, true /* animateAlpha */,
                     true /* animateBounds */);
         }
@@ -608,8 +581,8 @@
 
     public final void onBusEvent(final DragEndEvent event) {
         // Handle the case where we drop onto a dock region
-        if (event.dropTarget instanceof TaskStack.DockState) {
-            final TaskStack.DockState dockState = (TaskStack.DockState) event.dropTarget;
+        if (event.dropTarget instanceof DockState) {
+            final DockState dockState = (DockState) event.dropTarget;
 
             // Hide the dock region
             updateVisibleDockRegions(null, false /* isDefaultDockState */, -1, -1,
@@ -731,18 +704,10 @@
     }
 
     public final void onBusEvent(ShowStackActionButtonEvent event) {
-        if (!RecentsDebugFlags.Static.EnableStackActionButton) {
-            return;
-        }
-
         showStackActionButton(SHOW_STACK_ACTION_BUTTON_DURATION, event.translate);
     }
 
     public final void onBusEvent(HideStackActionButtonEvent event) {
-        if (!RecentsDebugFlags.Static.EnableStackActionButton) {
-            return;
-        }
-
         hideStackActionButton(HIDE_STACK_ACTION_BUTTON_DURATION, true /* translate */);
     }
 
@@ -758,10 +723,6 @@
      * Shows the stack action button.
      */
     private void showStackActionButton(final int duration, final boolean translate) {
-        if (!RecentsDebugFlags.Static.EnableStackActionButton) {
-            return;
-        }
-
         final ReferenceCountedTrigger postAnimationTrigger = new ReferenceCountedTrigger();
         if (mStackActionButton.getVisibility() == View.INVISIBLE) {
             mStackActionButton.setVisibility(View.VISIBLE);
@@ -794,10 +755,6 @@
      * Hides the stack action button.
      */
     private void hideStackActionButton(int duration, boolean translate) {
-        if (!RecentsDebugFlags.Static.EnableStackActionButton) {
-            return;
-        }
-
         final ReferenceCountedTrigger postAnimationTrigger = new ReferenceCountedTrigger();
         hideStackActionButton(duration, translate, postAnimationTrigger);
         postAnimationTrigger.flushLastDecrementRunnables();
@@ -808,10 +765,6 @@
      */
     private void hideStackActionButton(int duration, boolean translate,
                                        final ReferenceCountedTrigger postAnimationTrigger) {
-        if (!RecentsDebugFlags.Static.EnableStackActionButton) {
-            return;
-        }
-
         if (mStackActionButton.getVisibility() == View.VISIBLE) {
             if (translate) {
                 mStackActionButton.animate().translationY(mStackActionButton.getMeasuredHeight()
@@ -858,15 +811,15 @@
     /**
      * Updates the dock region to match the specified dock state.
      */
-    private void updateVisibleDockRegions(TaskStack.DockState[] newDockStates,
+    private void updateVisibleDockRegions(DockState[] newDockStates,
             boolean isDefaultDockState, int overrideAreaAlpha, int overrideHintAlpha,
             boolean animateAlpha, boolean animateBounds) {
-        ArraySet<TaskStack.DockState> newDockStatesSet = Utilities.arrayToSet(newDockStates,
-                new ArraySet<TaskStack.DockState>());
-        ArrayList<TaskStack.DockState> visDockStates = mTouchHandler.getVisibleDockStates();
+        ArraySet<DockState> newDockStatesSet = Utilities.arrayToSet(newDockStates,
+                new ArraySet<DockState>());
+        ArrayList<DockState> visDockStates = mTouchHandler.getVisibleDockStates();
         for (int i = visDockStates.size() - 1; i >= 0; i--) {
-            TaskStack.DockState dockState = visDockStates.get(i);
-            TaskStack.DockState.ViewState viewState = dockState.viewState;
+            DockState dockState = visDockStates.get(i);
+            DockState.ViewState viewState = dockState.viewState;
             if (newDockStates == null || !newDockStatesSet.contains(dockState)) {
                 // This is no longer visible, so hide it
                 viewState.startAnimation(null, 0, 0, TaskStackView.SLOW_SYNC_STACK_DURATION,
diff --git a/com/android/systemui/recents/views/RecentsViewTouchHandler.java b/com/android/systemui/recents/views/RecentsViewTouchHandler.java
index b6b24bc..0cfdbde 100644
--- a/com/android/systemui/recents/views/RecentsViewTouchHandler.java
+++ b/com/android/systemui/recents/views/RecentsViewTouchHandler.java
@@ -17,7 +17,6 @@
 package com.android.systemui.recents.views;
 
 import android.app.ActivityManager;
-import android.content.res.Configuration;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.view.InputDevice;
@@ -32,7 +31,6 @@
 import com.android.systemui.recents.events.EventBus;
 import com.android.systemui.recents.events.activity.ConfigurationChangedEvent;
 import com.android.systemui.recents.events.activity.HideRecentsEvent;
-import com.android.systemui.recents.events.activity.HideStackActionButtonEvent;
 import com.android.systemui.recents.events.ui.DismissAllTaskViewsEvent;
 import com.android.systemui.recents.events.ui.HideIncompatibleAppOverlayEvent;
 import com.android.systemui.recents.events.ui.ShowIncompatibleAppOverlayEvent;
@@ -41,8 +39,8 @@
 import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent;
 import com.android.systemui.recents.events.ui.dragndrop.DragStartInitializeDropTargetsEvent;
 import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.model.Task;
-import com.android.systemui.recents.model.TaskStack;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.model.TaskStack;
 
 import java.util.ArrayList;
 
@@ -72,7 +70,7 @@
     private DropTarget mLastDropTarget;
     private DividerSnapAlgorithm mDividerSnapAlgorithm;
     private ArrayList<DropTarget> mDropTargets = new ArrayList<>();
-    private ArrayList<TaskStack.DockState> mVisibleDockStates = new ArrayList<>();
+    private ArrayList<DockState> mVisibleDockStates = new ArrayList<>();
 
     public RecentsViewTouchHandler(RecentsView rv) {
         mRv = rv;
@@ -96,7 +94,7 @@
     /**
      * Returns the set of visible dock states for this current drag.
      */
-    public ArrayList<TaskStack.DockState> getVisibleDockStates() {
+    public ArrayList<DockState> getVisibleDockStates() {
         return mVisibleDockStates;
     }
 
@@ -148,9 +146,9 @@
                 EventBus.getDefault().send(new ShowIncompatibleAppOverlayEvent());
             } else {
                 // Add the dock state drop targets (these take priority)
-                TaskStack.DockState[] dockStates = Recents.getConfiguration()
+                DockState[] dockStates = Recents.getConfiguration()
                         .getDockStatesForCurrentOrientation();
-                for (TaskStack.DockState dockState : dockStates) {
+                for (DockState dockState : dockStates) {
                     registerDropTargetForCurrentDrag(dockState);
                     dockState.update(mRv.getContext());
                     mVisibleDockStates.add(dockState);
diff --git a/com/android/systemui/recents/views/SystemBarScrimViews.java b/com/android/systemui/recents/views/SystemBarScrimViews.java
index 8f784b8..7827c59 100644
--- a/com/android/systemui/recents/views/SystemBarScrimViews.java
+++ b/com/android/systemui/recents/views/SystemBarScrimViews.java
@@ -30,7 +30,7 @@
 import com.android.systemui.recents.events.activity.MultiWindowStateChangedEvent;
 import com.android.systemui.recents.events.ui.dragndrop.DragEndCancelledEvent;
 import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
-import com.android.systemui.recents.model.TaskStack;
+import com.android.systemui.shared.recents.utilities.AnimationProps;
 
 /** Manages the scrims for the various system bars. */
 public class SystemBarScrimViews {
@@ -159,7 +159,7 @@
 
     public final void onBusEvent(final DragEndEvent event) {
         // Hide the nav bar scrims once we drop to a dock region
-        if (event.dropTarget instanceof TaskStack.DockState) {
+        if (event.dropTarget instanceof DockState) {
             animateScrimToCurrentNavBarState(false /* hasStackTasks */);
         }
     }
diff --git a/com/android/systemui/recents/views/TaskStackAnimationHelper.java b/com/android/systemui/recents/views/TaskStackAnimationHelper.java
index 81bf6af..26db26f 100644
--- a/com/android/systemui/recents/views/TaskStackAnimationHelper.java
+++ b/com/android/systemui/recents/views/TaskStackAnimationHelper.java
@@ -18,13 +18,11 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
-import android.animation.TimeInterpolator;
 import android.animation.ValueAnimator;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.util.Log;
-import android.view.View;
 import android.view.animation.Interpolator;
 import android.view.animation.PathInterpolator;
 
@@ -37,9 +35,10 @@
 import com.android.systemui.recents.events.EventBus;
 import com.android.systemui.recents.events.component.SetWaitingForTransitionStartEvent;
 import com.android.systemui.recents.misc.ReferenceCountedTrigger;
-import com.android.systemui.recents.model.Task;
-import com.android.systemui.recents.model.TaskStack;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.model.TaskStack;
 import com.android.systemui.recents.views.lowram.TaskStackLowRamLayoutAlgorithm;
+import com.android.systemui.shared.recents.utilities.AnimationProps;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -161,20 +160,12 @@
         for (int i = taskViews.size() - 1; i >= 0; i--) {
             TaskView tv = taskViews.get(i);
             Task task = tv.getTask();
-            boolean currentTaskOccludesLaunchTarget = launchTargetTask != null &&
-                    launchTargetTask.group != null &&
-                    launchTargetTask.group.isTaskAboveTask(task, launchTargetTask);
-            boolean hideTask = launchTargetTask != null &&
-                    launchTargetTask.isFreeformTask() &&
-                    task.isFreeformTask();
 
             // Get the current transform for the task, which will be used to position it offscreen
             stackLayout.getStackTransform(task, stackScroller.getStackScroll(), mTmpTransform,
                     null);
 
-            if (hideTask) {
-                tv.setVisibility(View.INVISIBLE);
-            } else if (launchState.launchedFromApp && !launchState.launchedViaDockGesture) {
+            if (launchState.launchedFromApp && !launchState.launchedViaDockGesture) {
                 if (task.isLaunchTarget) {
                     tv.onPrepareLaunchTargetForEnterAnimation();
                 } else if (isLowRamDevice && i >= taskViews.size() -
@@ -195,13 +186,6 @@
                     // com.android.server.wm.AppTransition#DEFAULT_APP_TRANSITION_DURATION}
                     mStackView.updateTaskViewToTransform(tv, mTmpTransform,
                             new AnimationProps(336, Interpolators.FAST_OUT_SLOW_IN));
-                } else if (currentTaskOccludesLaunchTarget) {
-                    // Move the task view slightly lower so we can animate it in
-                    mTmpTransform.rect.offset(0, taskViewAffiliateGroupEnterOffset);
-                    mTmpTransform.alpha = 0f;
-                    mStackView.updateTaskViewToTransform(tv, mTmpTransform,
-                            AnimationProps.IMMEDIATE);
-                    tv.setClipViewInStack(false);
                 }
             } else if (launchState.launchedFromHome) {
                 if (isLowRamDevice) {
@@ -266,9 +250,6 @@
             int taskIndexFromBack = i;
             final TaskView tv = taskViews.get(i);
             Task task = tv.getTask();
-            boolean currentTaskOccludesLaunchTarget = launchTargetTask != null &&
-                    launchTargetTask.group != null &&
-                    launchTargetTask.group.isTaskAboveTask(task, launchTargetTask);
 
             // Get the current transform for the task, which will be updated to the final transform
             // to animate to depending on how recents was invoked
@@ -280,21 +261,6 @@
                     tv.onStartLaunchTargetEnterAnimation(mTmpTransform,
                             taskViewEnterFromAppDuration, mStackView.mScreenPinningEnabled,
                             postAnimationTrigger);
-                } else {
-                    // Animate the task up if it was occluding the launch target
-                    if (currentTaskOccludesLaunchTarget) {
-                        AnimationProps taskAnimation = new AnimationProps(
-                                taskViewEnterFromAffiliatedAppDuration, Interpolators.ALPHA_IN,
-                                new AnimatorListenerAdapter() {
-                                    @Override
-                                    public void onAnimationEnd(Animator animation) {
-                                        postAnimationTrigger.decrement();
-                                        tv.setClipViewInStack(true);
-                                    }
-                                });
-                        postAnimationTrigger.increment();
-                        mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation);
-                    }
                 }
 
             } else if (launchState.launchedFromHome) {
@@ -423,9 +389,6 @@
         for (int i = 0; i < taskViewCount; i++) {
             TaskView tv = taskViews.get(i);
             Task task = tv.getTask();
-            boolean currentTaskOccludesLaunchTarget = launchingTask != null &&
-                    launchingTask.group != null &&
-                    launchingTask.group.isTaskAboveTask(task, launchingTask);
 
             if (tv == launchingTaskView) {
                 tv.setClipViewInStack(false);
@@ -437,17 +400,6 @@
                 });
                 tv.onStartLaunchTargetLaunchAnimation(taskViewExitToAppDuration,
                         screenPinningRequested, postAnimationTrigger);
-            } else if (currentTaskOccludesLaunchTarget) {
-                // Animate this task out of view
-                AnimationProps taskAnimation = new AnimationProps(
-                        taskViewExitToAppDuration, Interpolators.ALPHA_OUT,
-                        postAnimationTrigger.decrementOnAnimationEnd());
-                postAnimationTrigger.increment();
-
-                mTmpTransform.fillIn(tv);
-                mTmpTransform.alpha = 0f;
-                mTmpTransform.rect.offset(0, taskViewAffiliateGroupEnterOffset);
-                mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation);
             }
         }
     }
@@ -611,7 +563,7 @@
                 false /* ignoreTaskOverrides */, mTmpFinalTaskTransforms);
 
         // Hide the front most task view until the scroll is complete
-        Task frontMostTask = newStack.getStackFrontMostTask(false /* includeFreeform */);
+        Task frontMostTask = newStack.getStackFrontMostTask();
         final TaskView frontMostTaskView = mStackView.getChildViewForTask(frontMostTask);
         final TaskViewTransform frontMostTransform = mTmpFinalTaskTransforms.get(
                 stackTasks.indexOf(frontMostTask));
diff --git a/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java b/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
index eaa32ee..acb058c 100644
--- a/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
+++ b/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
@@ -24,7 +24,6 @@
 import android.graphics.Rect;
 import android.util.ArraySet;
 import android.util.Log;
-import android.util.MutableFloat;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
 import android.view.ViewDebug;
@@ -36,9 +35,9 @@
 import com.android.systemui.recents.RecentsDebugFlags;
 import com.android.systemui.recents.misc.FreePathInterpolator;
 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.recents.model.TaskStack;
+import com.android.systemui.shared.recents.utilities.Utilities;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.model.TaskStack;
 import com.android.systemui.recents.views.lowram.TaskStackLowRamLayoutAlgorithm;
 import com.android.systemui.recents.views.grid.TaskGridLayoutAlgorithm;
 
@@ -147,75 +146,6 @@
     }
 
     /**
-     * The various stack/freeform states.
-     */
-    public static class StackState {
-
-        public static final StackState FREEFORM_ONLY = new StackState(1f, 255);
-        public static final StackState STACK_ONLY = new StackState(0f, 0);
-        public static final StackState SPLIT = new StackState(0.5f, 255);
-
-        public final float freeformHeightPct;
-        public final int freeformBackgroundAlpha;
-
-        /**
-         * @param freeformHeightPct the percentage of the stack height (not including paddings) to
-         *                          allocate to the freeform workspace
-         * @param freeformBackgroundAlpha the background alpha for the freeform workspace
-         */
-        private StackState(float freeformHeightPct, int freeformBackgroundAlpha) {
-            this.freeformHeightPct = freeformHeightPct;
-            this.freeformBackgroundAlpha = freeformBackgroundAlpha;
-        }
-
-        /**
-         * Resolves the stack state for the layout given a task stack.
-         */
-        public static StackState getStackStateForStack(TaskStack stack) {
-            SystemServicesProxy ssp = Recents.getSystemServices();
-            boolean hasFreeformWorkspaces = ssp.hasFreeformWorkspaceSupport();
-            int freeformCount = stack.getFreeformTaskCount();
-            int stackCount = stack.getStackTaskCount();
-            if (hasFreeformWorkspaces && stackCount > 0 && freeformCount > 0) {
-                return SPLIT;
-            } else if (hasFreeformWorkspaces && freeformCount > 0) {
-                return FREEFORM_ONLY;
-            } else {
-                return STACK_ONLY;
-            }
-        }
-
-        /**
-         * Computes the freeform and stack rect for this state.
-         *
-         * @param freeformRectOut the freeform rect to be written out
-         * @param stackRectOut the stack rect, we only write out the top of the stack
-         * @param taskStackBounds the full rect that the freeform rect can take up
-         */
-        public void computeRects(Rect freeformRectOut, Rect stackRectOut,
-                Rect taskStackBounds, int topMargin, int freeformGap, int stackBottomOffset) {
-            // The freeform height is the visible height (not including system insets) - padding
-            // above freeform and below stack - gap between the freeform and stack
-            int availableHeight = taskStackBounds.height() - topMargin - stackBottomOffset;
-            int ffPaddedHeight = (int) (availableHeight * freeformHeightPct);
-            int ffHeight = Math.max(0, ffPaddedHeight - freeformGap);
-            freeformRectOut.set(taskStackBounds.left,
-                    taskStackBounds.top + topMargin,
-                    taskStackBounds.right,
-                    taskStackBounds.top + topMargin + ffHeight);
-            stackRectOut.set(taskStackBounds.left,
-                    taskStackBounds.top,
-                    taskStackBounds.right,
-                    taskStackBounds.bottom);
-            if (ffPaddedHeight > 0) {
-                stackRectOut.top += ffPaddedHeight;
-            } else {
-                stackRectOut.top += topMargin;
-            }
-        }
-    }
-
-    /**
      * @return True if we should use the grid layout.
      */
     boolean useGridLayout() {
@@ -234,15 +164,11 @@
     }
 
     Context mContext;
-    private StackState mState = StackState.SPLIT;
     private TaskStackLayoutAlgorithmCallbacks mCb;
 
     // The task bounds (untransformed) for layout.  This rect is anchored at mTaskRoot.
     @ViewDebug.ExportedProperty(category="recents")
     public Rect mTaskRect = new Rect();
-    // The freeform workspace bounds, inset by the top system insets and is a fixed height
-    @ViewDebug.ExportedProperty(category="recents")
-    public Rect mFreeformRect = new Rect();
     // The stack bounds, inset from the top system insets, and runs to the bottom of the screen
     @ViewDebug.ExportedProperty(category="recents")
     public Rect mStackRect = new Rect();
@@ -268,10 +194,6 @@
     private int mBaseBottomMargin;
     private int mMinMargin;
 
-    // The gap between the freeform and stack layouts
-    @ViewDebug.ExportedProperty(category="recents")
-    private int mFreeformStackGap;
-
     // The initial offset that the focused task is from the top
     @ViewDebug.ExportedProperty(category="recents")
     private int mInitialTopOffset;
@@ -331,8 +253,6 @@
     // The last computed task counts
     @ViewDebug.ExportedProperty(category="recents")
     int mNumStackTasks;
-    @ViewDebug.ExportedProperty(category="recents")
-    int mNumFreeformTasks;
 
     // The min/max z translations
     @ViewDebug.ExportedProperty(category="recents")
@@ -344,8 +264,6 @@
     private SparseIntArray mTaskIndexMap = new SparseIntArray();
     private SparseArray<Float> mTaskIndexOverrideMap = new SparseArray<>();
 
-    // The freeform workspace layout
-    FreeformWorkspaceLayoutAlgorithm mFreeformLayoutAlgorithm;
     TaskGridLayoutAlgorithm mTaskGridLayoutAlgorithm;
     TaskStackLowRamLayoutAlgorithm mTaskStackLowRamLayoutAlgorithm;
 
@@ -356,7 +274,6 @@
     public TaskStackLayoutAlgorithm(Context context, TaskStackLayoutAlgorithmCallbacks cb) {
         mContext = context;
         mCb = cb;
-        mFreeformLayoutAlgorithm = new FreeformWorkspaceLayoutAlgorithm(context);
         mTaskGridLayoutAlgorithm = new TaskGridLayoutAlgorithm(context);
         mTaskStackLowRamLayoutAlgorithm = new TaskStackLowRamLayoutAlgorithm(context);
         reloadOnConfigurationChange(context);
@@ -393,7 +310,6 @@
                 R.dimen.recents_layout_initial_bottom_offset_tablet,
                 R.dimen.recents_layout_initial_bottom_offset_tablet,
                 R.dimen.recents_layout_initial_bottom_offset_tablet);
-        mFreeformLayoutAlgorithm.reloadOnConfigurationChange(context);
         mTaskGridLayoutAlgorithm.reloadOnConfigurationChange(context);
         mTaskStackLowRamLayoutAlgorithm.reloadOnConfigurationChange(context);
         mMinMargin = res.getDimensionPixelSize(R.dimen.recents_layout_min_margin);
@@ -408,8 +324,6 @@
                 R.dimen.recents_layout_side_margin_tablet_xlarge,
                 R.dimen.recents_layout_side_margin_tablet);
         mBaseBottomMargin = res.getDimensionPixelSize(R.dimen.recents_layout_bottom_margin);
-        mFreeformStackGap =
-                res.getDimensionPixelSize(R.dimen.recents_freeform_layout_bottom_margin);
         mTitleBarHeight = getDimensionForDevice(mContext,
                 R.dimen.recents_task_view_header_height,
                 R.dimen.recents_task_view_header_height,
@@ -462,8 +376,7 @@
      * Computes the stack and task rects.  The given task stack bounds already has the top/right
      * insets and left/right padding already applied.
      */
-    public void initialize(Rect displayRect, Rect windowRect, Rect taskStackBounds,
-            StackState state) {
+    public void initialize(Rect displayRect, Rect windowRect, Rect taskStackBounds) {
         Rect lastStackRect = new Rect(mStackRect);
 
         int topMargin = getScaleForExtent(windowRect, displayRect, mBaseTopMargin, mMinMargin, HEIGHT);
@@ -474,10 +387,9 @@
         mInitialBottomOffset = mBaseInitialBottomOffset;
 
         // Compute the stack bounds
-        mState = state;
         mStackBottomOffset = mSystemInsets.bottom + bottomMargin;
-        state.computeRects(mFreeformRect, mStackRect, taskStackBounds, topMargin,
-                mFreeformStackGap, mStackBottomOffset);
+        mStackRect.set(taskStackBounds);
+        mStackRect.top += topMargin;
 
         // The stack action button will take the full un-padded header space above the stack
         mStackActionButtonRect.set(mStackRect.left, mStackRect.top - topMargin,
@@ -530,26 +442,20 @@
         if (tasks.isEmpty()) {
             mFrontMostTaskP = 0;
             mMinScrollP = mMaxScrollP = mInitialScrollP = 0;
-            mNumStackTasks = mNumFreeformTasks = 0;
+            mNumStackTasks = 0;
             return;
         }
 
-        // Filter the set of freeform and stack tasks
-        ArrayList<Task> freeformTasks = new ArrayList<>();
+        // Filter the set of stack tasks
         ArrayList<Task> stackTasks = new ArrayList<>();
         for (int i = 0; i < tasks.size(); i++) {
             Task task = tasks.get(i);
             if (ignoreTasksSet.contains(task.key)) {
                 continue;
             }
-            if (task.isFreeformTask()) {
-                freeformTasks.add(task);
-            } else {
-                stackTasks.add(task);
-            }
+            stackTasks.add(task);
         }
         mNumStackTasks = stackTasks.size();
-        mNumFreeformTasks = freeformTasks.size();
 
         // Put each of the tasks in the progress map at a fixed index (does not need to actually
         // map to a scroll position, just by index)
@@ -559,11 +465,6 @@
             mTaskIndexMap.put(task.key.id, i);
         }
 
-        // Update the freeform tasks
-        if (!freeformTasks.isEmpty()) {
-            mFreeformLayoutAlgorithm.update(freeformTasks, this);
-        }
-
         // Calculate the min/max/initial scroll
         Task launchTask = stack.getLaunchTarget();
         int launchTaskIndex = launchTask != null
@@ -582,7 +483,7 @@
             } else {
                 mInitialScrollP = Utilities.clamp(launchTaskIndex - 1, mMinScrollP, mMaxScrollP);
             }
-        } else if (!ssp.hasFreeformWorkspaceSupport() && mNumStackTasks == 1) {
+        } else if (mNumStackTasks == 1) {
             // If there is one stack task, ignore the min/max/initial scroll positions
             mMinScrollP = 0;
             mMaxScrollP = 0;
@@ -603,9 +504,7 @@
             boolean scrollToFront = launchState.launchedFromHome || launchState.launchedFromPipApp
                     || launchState.launchedWithNextPipApp || launchState.launchedViaDockGesture;
 
-            if (launchState.launchedFromBlacklistedApp) {
-                mInitialScrollP = mMaxScrollP;
-            } else if (launchState.launchedWithAltTab) {
+            if (launchState.launchedWithAltTab) {
                 mInitialScrollP = Utilities.clamp(launchTaskIndex, mMinScrollP, mMaxScrollP);
             } else if (Recents.getConfiguration().isLowRamDevice) {
                 mInitialScrollP = mTaskStackLowRamLayoutAlgorithm.getInitialScrollP(mNumStackTasks,
@@ -633,7 +532,6 @@
         boolean scrollToFront = launchState.launchedFromHome ||
                 launchState.launchedFromPipApp ||
                 launchState.launchedWithNextPipApp ||
-                launchState.launchedFromBlacklistedApp ||
                 launchState.launchedViaDockGesture;
         if (getInitialFocusState() == STATE_UNFOCUSED && mNumStackTasks > 1) {
             if (ignoreScrollToFront || (!launchState.launchedWithAltTab && !scrollToFront)) {
@@ -767,7 +665,7 @@
     public int getInitialFocusState() {
         RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
         RecentsDebugFlags debugFlags = Recents.getDebugFlags();
-        if (debugFlags.isPagingEnabled() || launchState.launchedWithAltTab) {
+        if (launchState.launchedWithAltTab) {
             return STATE_FOCUSED;
         } else {
             return STATE_UNFOCUSED;
@@ -794,13 +692,6 @@
     }
 
     /**
-     * Returns the current stack state.
-     */
-    public StackState getStackState() {
-        return mState;
-    }
-
-    /**
      * Returns whether this stack layout has been initialized.
      */
     public boolean isInitialized() {
@@ -825,62 +716,44 @@
             return new VisibilityReport(1, 1);
         }
 
-        // Quick return when there are no stack tasks
-        if (mNumStackTasks == 0) {
-            return new VisibilityReport(mNumFreeformTasks > 0 ? Math.max(mNumFreeformTasks, 1) : 0,
-                    mNumFreeformTasks > 0 ? Math.max(mNumFreeformTasks, 1) : 0);
-        }
-
         // Otherwise, walk backwards in the stack and count the number of tasks and visible
-        // thumbnails and add that to the total freeform task count
+        // thumbnails and add that to the total task count
         TaskViewTransform tmpTransform = new TaskViewTransform();
         Range currentRange = getInitialFocusState() > 0f ? mFocusedRange : mUnfocusedRange;
         currentRange.offset(mInitialScrollP);
         int taskBarHeight = mContext.getResources().getDimensionPixelSize(
                 R.dimen.recents_task_view_header_height);
-        int numVisibleTasks = mNumFreeformTasks > 0 ? Math.max(mNumFreeformTasks, 1) : 0;
-        int numVisibleThumbnails = mNumFreeformTasks > 0 ? Math.max(mNumFreeformTasks, 0) : 0;
+        int numVisibleTasks = 0;
+        int numVisibleThumbnails = 0;
         float prevScreenY = Integer.MAX_VALUE;
         for (int i = tasks.size() - 1; i >= 0; i--) {
             Task task = tasks.get(i);
 
-            // Skip freeform
-            if (task.isFreeformTask()) {
-                continue;
-            }
-
             // Skip invisible
             float taskProgress = getStackScrollForTask(task);
             if (!currentRange.isInRange(taskProgress)) {
                 continue;
             }
 
-            boolean isFrontMostTaskInGroup = task.group == null || task.group.isFrontMostTask(task);
-            if (isFrontMostTaskInGroup) {
-                getStackTransform(taskProgress, taskProgress, mInitialScrollP, mFocusState,
-                        tmpTransform, null, false /* ignoreSingleTaskCase */,
-                        false /* forceUpdate */);
-                float screenY = tmpTransform.rect.top;
-                boolean hasVisibleThumbnail = (prevScreenY - screenY) > taskBarHeight;
-                if (hasVisibleThumbnail) {
-                    numVisibleThumbnails++;
-                    numVisibleTasks++;
-                    prevScreenY = screenY;
-                } else {
-                    // Once we hit the next front most task that does not have a visible thumbnail,
-                    // walk through remaining visible set
-                    for (int j = i; j >= 0; j--) {
-                        taskProgress = getStackScrollForTask(tasks.get(j));
-                        if (!currentRange.isInRange(taskProgress)) {
-                            break;
-                        }
-                        numVisibleTasks++;
-                    }
-                    break;
-                }
-            } else {
-                // Affiliated task, no thumbnail
+            getStackTransform(taskProgress, taskProgress, mInitialScrollP, mFocusState,
+                    tmpTransform, null, false /* ignoreSingleTaskCase */, false /* forceUpdate */);
+            float screenY = tmpTransform.rect.top;
+            boolean hasVisibleThumbnail = (prevScreenY - screenY) > taskBarHeight;
+            if (hasVisibleThumbnail) {
+                numVisibleThumbnails++;
                 numVisibleTasks++;
+                prevScreenY = screenY;
+            } else {
+                // Once we hit the next front most task that does not have a visible thumbnail,
+                // walk through remaining visible set
+                for (int j = i; j >= 0; j--) {
+                    taskProgress = getStackScrollForTask(tasks.get(j));
+                    if (!currentRange.isInRange(taskProgress)) {
+                        break;
+                    }
+                    numVisibleTasks++;
+                }
+                break;
             }
         }
         return new VisibilityReport(numVisibleTasks, numVisibleThumbnails);
@@ -906,10 +779,7 @@
     public TaskViewTransform getStackTransform(Task task, float stackScroll, int focusState,
             TaskViewTransform transformOut, TaskViewTransform frontTransform, boolean forceUpdate,
             boolean ignoreTaskOverrides) {
-        if (mFreeformLayoutAlgorithm.isTransformAvailable(task, this)) {
-            mFreeformLayoutAlgorithm.getTransform(task, transformOut, this);
-            return transformOut;
-        } else if (useGridLayout()) {
+        if (useGridLayout()) {
             int taskIndex = mTaskIndexMap.get(task.key.id);
             int taskCount = mTaskIndexMap.size();
             mTaskGridLayoutAlgorithm.getTransform(taskIndex, taskCount, transformOut, this);
@@ -1024,7 +894,7 @@
         float z;
         float dimAlpha;
         float viewOutlineAlpha;
-        if (!ssp.hasFreeformWorkspaceSupport() && mNumStackTasks == 1 && !ignoreSingleTaskCase) {
+        if (mNumStackTasks == 1 && !ignoreSingleTaskCase) {
             // When there is exactly one task, then decouple the task from the stack and just move
             // in screen space
             float tmpP = (mMinScrollP - stackScroll) / mNumStackTasks;
@@ -1378,7 +1248,6 @@
         writer.print("insets="); writer.print(Utilities.dumpRect(mSystemInsets));
         writer.print(" stack="); writer.print(Utilities.dumpRect(mStackRect));
         writer.print(" task="); writer.print(Utilities.dumpRect(mTaskRect));
-        writer.print(" freeform="); writer.print(Utilities.dumpRect(mFreeformRect));
         writer.print(" actionButton="); writer.print(Utilities.dumpRect(mStackActionButtonRect));
         writer.println();
 
diff --git a/com/android/systemui/recents/views/TaskStackView.java b/com/android/systemui/recents/views/TaskStackView.java
index 3160ee0..428113a 100644
--- a/com/android/systemui/recents/views/TaskStackView.java
+++ b/com/android/systemui/recents/views/TaskStackView.java
@@ -16,22 +16,15 @@
 
 package com.android.systemui.recents.views;
 
-import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
-import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
 import android.annotation.IntDef;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.Resources;
-import android.graphics.Canvas;
 import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.GradientDrawable;
 import android.os.Bundle;
 import android.provider.Settings;
 import android.util.ArrayMap;
@@ -64,7 +57,6 @@
 import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationCompletedEvent;
 import com.android.systemui.recents.events.activity.HideRecentsEvent;
 import com.android.systemui.recents.events.activity.HideStackActionButtonEvent;
-import com.android.systemui.recents.events.activity.IterateRecentsEvent;
 import com.android.systemui.recents.events.activity.LaunchMostRecentTaskRequestEvent;
 import com.android.systemui.recents.events.activity.LaunchNextTaskRequestEvent;
 import com.android.systemui.recents.events.activity.LaunchTaskEvent;
@@ -83,13 +75,11 @@
 import com.android.systemui.recents.events.ui.DismissTaskViewEvent;
 import com.android.systemui.recents.events.ui.RecentsGrowingEvent;
 import com.android.systemui.recents.events.ui.TaskViewDismissedEvent;
-import com.android.systemui.recents.events.ui.UpdateFreeformTaskViewVisibilityEvent;
 import com.android.systemui.recents.events.ui.UserInteractionEvent;
 import com.android.systemui.recents.events.ui.dragndrop.DragDropTargetChangedEvent;
 import com.android.systemui.recents.events.ui.dragndrop.DragEndCancelledEvent;
 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.dragndrop.DragStartInitializeDropTargetsEvent;
 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;
@@ -97,9 +87,10 @@
 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.misc.Utilities;
-import com.android.systemui.recents.model.Task;
-import com.android.systemui.recents.model.TaskStack;
+import com.android.systemui.shared.recents.utilities.AnimationProps;
+import com.android.systemui.shared.recents.utilities.Utilities;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.model.TaskStack;
 import com.android.systemui.recents.views.grid.GridTaskView;
 import com.android.systemui.recents.views.grid.TaskGridLayoutAlgorithm;
 import com.android.systemui.recents.views.grid.TaskViewFocusFrame;
@@ -153,8 +144,6 @@
     @ViewDebug.ExportedProperty(deepExport=true, prefix="touch_")
     private TaskStackViewTouchHandler mTouchHandler;
     private TaskStackAnimationHelper mAnimationHelper;
-    private GradientDrawable mFreeformWorkspaceBackground;
-    private ObjectAnimator mFreeformWorkspaceBackgroundAnimator;
     private ViewPool<TaskView, Task> mViewPool;
 
     private ArrayList<TaskView> mTaskViews = new ArrayList<>();
@@ -239,20 +228,6 @@
                 }
             };
 
-    // The drop targets for a task drag
-    private DropTarget mFreeformWorkspaceDropTarget = new DropTarget() {
-        @Override
-        public boolean acceptsDrop(int x, int y, int width, int height, Rect insets,
-                boolean isCurrentTarget) {
-            // This drop target has a fixed bounds and should be checked last, so just fall through
-            // if it is the current target
-            if (!isCurrentTarget) {
-                return mLayoutAlgorithm.mFreeformRect.contains(x, y);
-            }
-            return false;
-        }
-    };
-
     private DropTarget mStackDropTarget = new DropTarget() {
         @Override
         public boolean acceptsDrop(int x, int y, int width, int height, Rect insets,
@@ -312,17 +287,6 @@
             }
         });
         setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
-        if (ssp.hasFreeformWorkspaceSupport()) {
-            setWillNotDraw(false);
-        }
-
-        mFreeformWorkspaceBackground = (GradientDrawable) getContext().getDrawable(
-                R.drawable.recents_freeform_workspace_bg);
-        mFreeformWorkspaceBackground.setCallback(this);
-        if (ssp.hasFreeformWorkspaceSupport()) {
-            mFreeformWorkspaceBackground.setColor(
-                    getContext().getColor(R.color.recents_freeform_workspace_bg_color));
-        }
     }
 
     @Override
@@ -359,12 +323,7 @@
         readSystemFlags();
         mTaskViewsClipDirty = true;
         mUIDozeTrigger.stopDozing();
-        if (isResumingFromVisible) {
-            // Animate in the freeform workspace
-            int ffBgAlpha = mLayoutAlgorithm.getStackState().freeformBackgroundAlpha;
-            animateFreeformWorkspaceBackgroundAlpha(ffBgAlpha, new AnimationProps(150,
-                    Interpolators.FAST_OUT_SLOW_IN));
-        } else {
+        if (!isResumingFromVisible) {
             mStackScroller.reset();
             mStableLayoutAlgorithm.reset();
             mLayoutAlgorithm.reset();
@@ -387,7 +346,7 @@
 
         // Only notify if we are already initialized, otherwise, everything will pick up all the
         // new and old tasks when we next layout
-        mStack.setTasks(getContext(), stack, allowNotifyStackChanges && isInitialized);
+        mStack.setTasks(stack, allowNotifyStackChanges && isInitialized);
     }
 
     /** Returns the task stack. */
@@ -422,23 +381,13 @@
 
     /**
      * Returns the front most task view.
-     *
-     * @param stackTasksOnly if set, will return the front most task view in the stack (by default
-     *                       the front most task view will be freeform since they are placed above
-     *                       stack tasks)
      */
-    private TaskView getFrontMostTaskView(boolean stackTasksOnly) {
+    private TaskView getFrontMostTaskView() {
         List<TaskView> taskViews = getTaskViews();
-        int taskViewCount = taskViews.size();
-        for (int i = taskViewCount - 1; i >= 0; i--) {
-            TaskView tv = taskViews.get(i);
-            Task task = tv.getTask();
-            if (stackTasksOnly && task.isFreeformTask()) {
-                continue;
-            }
-            return tv;
+        if (taskViews.isEmpty()) {
+            return null;
         }
-        return null;
+        return taskViews.get(taskViews.size() - 1);
     }
 
     /**
@@ -500,8 +449,6 @@
      * visible range includes all tasks at the target stack scroll. This is useful for ensure that
      * all views necessary for a transition or animation will be visible at the start.
      *
-     * This call ignores freeform tasks.
-     *
      * @param taskTransforms The set of task view transforms to reuse, this list will be sized to
      *                       match the size of {@param tasks}
      * @param tasks The set of tasks for which to generate transforms
@@ -524,7 +471,7 @@
         boolean useTargetStackScroll = Float.compare(curStackScroll, targetStackScroll) != 0;
 
         // We can reuse the task transforms where possible to reduce object allocation
-        Utilities.matchTaskListSize(tasks, taskTransforms);
+        matchTaskListSize(tasks, taskTransforms);
 
         // Update the stack transforms
         TaskViewTransform frontTransform = null;
@@ -554,12 +501,6 @@
                 continue;
             }
 
-            // For freeform tasks, only calculate the stack transform and skip the calculation of
-            // the visible stack indices
-            if (task.isFreeformTask()) {
-                continue;
-            }
-
             frontTransform = transform;
             frontTransformAtTarget = transformAtTarget;
             if (transform.visible) {
@@ -622,7 +563,7 @@
                 transform = mCurrentTaskTransforms.get(taskIndex);
             }
 
-            if (task.isFreeformTask() || (transform != null && transform.visible)) {
+            if (transform != null && transform.visible) {
                 mTmpTaskViewMap.put(task.key, tv);
             } else {
                 if (mTouchExplorationEnabled && Utilities.isDescendentAccessibilityFocused(tv)) {
@@ -643,24 +584,20 @@
                 continue;
             }
 
-            // Skip the invisible non-freeform stack tasks
-            if (!task.isFreeformTask() && !transform.visible) {
+            // Skip the invisible stack tasks
+            if (!transform.visible) {
                 continue;
             }
 
             TaskView tv = mTmpTaskViewMap.get(task.key);
             if (tv == null) {
                 tv = mViewPool.pickUpViewFromPool(task, task);
-                if (task.isFreeformTask()) {
-                    updateTaskViewToTransform(tv, transform, AnimationProps.IMMEDIATE);
+                if (transform.rect.top <= mLayoutAlgorithm.mStackRect.top) {
+                    updateTaskViewToTransform(tv, mLayoutAlgorithm.getBackOfStackTransform(),
+                            AnimationProps.IMMEDIATE);
                 } else {
-                    if (transform.rect.top <= mLayoutAlgorithm.mStackRect.top) {
-                        updateTaskViewToTransform(tv, mLayoutAlgorithm.getBackOfStackTransform(),
-                                AnimationProps.IMMEDIATE);
-                    } else {
-                        updateTaskViewToTransform(tv, mLayoutAlgorithm.getFrontOfStackTransform(),
-                                AnimationProps.IMMEDIATE);
-                    }
+                    updateTaskViewToTransform(tv, mLayoutAlgorithm.getFrontOfStackTransform(),
+                            AnimationProps.IMMEDIATE);
                 }
             } else {
                 // Reattach it in the right z order
@@ -764,7 +701,7 @@
      */
     public void getCurrentTaskTransforms(ArrayList<Task> tasks,
             ArrayList<TaskViewTransform> transformsOut) {
-        Utilities.matchTaskListSize(tasks, transformsOut);
+        matchTaskListSize(tasks, transformsOut);
         int focusState = mLayoutAlgorithm.getFocusState();
         for (int i = tasks.size() - 1; i >= 0; i--) {
             Task task = tasks.get(i);
@@ -787,7 +724,7 @@
      */
     public void getLayoutTaskTransforms(float stackScroll, int focusState, ArrayList<Task> tasks,
             boolean ignoreTaskOverrides, ArrayList<TaskViewTransform> transformsOut) {
-        Utilities.matchTaskListSize(tasks, transformsOut);
+        matchTaskListSize(tasks, transformsOut);
         for (int i = tasks.size() - 1; i >= 0; i--) {
             Task task = tasks.get(i);
             TaskViewTransform transform = transformsOut.get(i);
@@ -887,13 +824,6 @@
         // Compute the min and max scroll values
         mLayoutAlgorithm.update(mStack, mIgnoreTasks, launchState);
 
-        // Update the freeform workspace background
-        SystemServicesProxy ssp = Recents.getSystemServices();
-        if (ssp.hasFreeformWorkspaceSupport()) {
-            mTmpRect.set(mLayoutAlgorithm.mFreeformRect);
-            mFreeformWorkspaceBackground.setBounds(mTmpRect);
-        }
-
         if (boundScrollToNewMinMax) {
             mStackScroller.boundScroll();
         }
@@ -906,8 +836,7 @@
         mWindowRect.set(mStableWindowRect);
         mStackBounds.set(mStableStackBounds);
         mLayoutAlgorithm.setSystemInsets(mStableLayoutAlgorithm.mSystemInsets);
-        mLayoutAlgorithm.initialize(mDisplayRect, mWindowRect, mStackBounds,
-                TaskStackLayoutAlgorithm.StackState.getStackStateForStack(mStack));
+        mLayoutAlgorithm.initialize(mDisplayRect, mWindowRect, mStackBounds);
         updateLayoutAlgorithm(true /* boundScroll */);
     }
 
@@ -1028,21 +957,10 @@
         if (focusedTask != null) {
             if (stackTasksOnly) {
                 List<Task> tasks =  mStack.getStackTasks();
-                if (focusedTask.isFreeformTask()) {
-                    // Try and focus the front most stack task
-                    TaskView tv = getFrontMostTaskView(stackTasksOnly);
-                    if (tv != null) {
-                        newIndex = mStack.indexOfStackTask(tv.getTask());
-                    }
-                } else {
-                    // Try the next task if it is a stack task
-                    int tmpNewIndex = newIndex + (forward ? -1 : 1);
-                    if (0 <= tmpNewIndex && tmpNewIndex < tasks.size()) {
-                        Task t = tasks.get(tmpNewIndex);
-                        if (!t.isFreeformTask()) {
-                            newIndex = tmpNewIndex;
-                        }
-                    }
+                // Try the next task if it is a stack task
+                int tmpNewIndex = newIndex + (forward ? -1 : 1);
+                if (0 <= tmpNewIndex && tmpNewIndex < tasks.size()) {
+                    newIndex = tmpNewIndex;
                 }
             } else {
                 // No restrictions, lets just move to the new task (looping forward/backwards if
@@ -1127,7 +1045,7 @@
                 return tv.getTask();
             }
         }
-        TaskView frontTv = getFrontMostTaskView(true /* stackTasksOnly */);
+        TaskView frontTv = getFrontMostTaskView();
         if (frontTv != null) {
             return frontTv.getTask();
         }
@@ -1278,10 +1196,8 @@
         }
 
         // Compute the rects in the stack algorithm
-        mStableLayoutAlgorithm.initialize(mDisplayRect, mStableWindowRect, mStableStackBounds,
-                TaskStackLayoutAlgorithm.StackState.getStackStateForStack(mStack));
-        mLayoutAlgorithm.initialize(mDisplayRect, mWindowRect, mStackBounds,
-                TaskStackLayoutAlgorithm.StackState.getStackStateForStack(mStack));
+        mStableLayoutAlgorithm.initialize(mDisplayRect, mStableWindowRect, mStableStackBounds);
+        mLayoutAlgorithm.initialize(mDisplayRect, mWindowRect, mStackBounds);
         updateLayoutAlgorithm(false /* boundScroll */);
 
         // If this is the first layout, then scroll to the front of the stack, then update the
@@ -1404,11 +1320,6 @@
         // Setup the view for the enter animation
         mAnimationHelper.prepareForEnterAnimation();
 
-        // Animate in the freeform workspace
-        int ffBgAlpha = mLayoutAlgorithm.getStackState().freeformBackgroundAlpha;
-        animateFreeformWorkspaceBackgroundAlpha(ffBgAlpha, new AnimationProps(150,
-                Interpolators.FAST_OUT_SLOW_IN));
-
         // Set the task focused state without requesting view focus, and leave the focus animations
         // until after the enter-animation
         RecentsConfiguration config = Recents.getConfiguration();
@@ -1456,43 +1367,6 @@
         return null;
     }
 
-    @Override
-    protected void onDraw(Canvas canvas) {
-        super.onDraw(canvas);
-
-        // Draw the freeform workspace background
-        SystemServicesProxy ssp = Recents.getSystemServices();
-        if (ssp.hasFreeformWorkspaceSupport()) {
-            if (mFreeformWorkspaceBackground.getAlpha() > 0) {
-                mFreeformWorkspaceBackground.draw(canvas);
-            }
-        }
-    }
-
-    @Override
-    protected boolean verifyDrawable(Drawable who) {
-        if (who == mFreeformWorkspaceBackground) {
-            return true;
-        }
-        return super.verifyDrawable(who);
-    }
-
-    /**
-     * Launches the freeform tasks.
-     */
-    public boolean launchFreeformTasks() {
-        ArrayList<Task> tasks = mStack.getFreeformTasks();
-        if (!tasks.isEmpty()) {
-            Task frontTask = tasks.get(tasks.size() - 1);
-            if (frontTask != null && frontTask.isFreeformTask()) {
-                EventBus.getDefault().send(new LaunchTaskEvent(getChildViewForTask(frontTask),
-                        frontTask, null, false));
-                return true;
-            }
-        }
-        return false;
-    }
-
     /**** TaskStackCallbacks Implementation ****/
 
     @Override
@@ -1671,8 +1545,7 @@
         }
 
         // Restore the action button visibility if it is the front most task view
-        if (mScreenPinningEnabled && tv.getTask() ==
-                mStack.getStackFrontMostTask(false /* includeFreeform */)) {
+        if (mScreenPinningEnabled && tv.getTask() == mStack.getStackFrontMostTask()) {
             tv.showActionButton(false /* fadeIn */, 0 /* fadeInDuration */);
         }
     }
@@ -1688,7 +1561,6 @@
 
         // If the doze trigger has already fired, then update the state for this task view
         if (mUIDozeTrigger.isAsleep() ||
-                Recents.getSystemServices().hasFreeformWorkspaceSupport() ||
                 useGridLayout() || Recents.getConfiguration().isLowRamDevice) {
             tv.setNoUserInteractionState();
         }
@@ -1820,21 +1692,17 @@
 
     public final void onBusEvent(LaunchMostRecentTaskRequestEvent event) {
         if (mStack.getTaskCount() > 0) {
-            Task mostRecentTask = mStack.getStackFrontMostTask(true /* includeFreefromTasks */);
+            Task mostRecentTask = mStack.getStackFrontMostTask();
             launchTask(mostRecentTask);
         }
     }
 
     public final void onBusEvent(ShowStackActionButtonEvent event) {
-        if (RecentsDebugFlags.Static.EnableStackActionButton) {
-            mStackActionButtonVisible = true;
-        }
+        mStackActionButtonVisible = true;
     }
 
     public final void onBusEvent(HideStackActionButtonEvent event) {
-        if (RecentsDebugFlags.Static.EnableStackActionButton) {
-            mStackActionButtonVisible = false;
-        }
+        mStackActionButtonVisible = false;
     }
 
     public final void onBusEvent(LaunchNextTaskRequestEvent event) {
@@ -1891,11 +1759,6 @@
         // Start the task animations
         mAnimationHelper.startExitToHomeAnimation(event.animated, event.getAnimationTrigger());
 
-        // Dismiss the freeform workspace background
-        int taskViewExitToHomeDuration = TaskStackAnimationHelper.EXIT_TO_HOME_TRANSLATION_DURATION;
-        animateFreeformWorkspaceBackgroundAlpha(0, new AnimationProps(taskViewExitToHomeDuration,
-                Interpolators.FAST_OUT_SLOW_IN));
-
         // Dismiss the grid task view focus frame
         if (mTaskViewFocusFrame != null) {
             mTaskViewFocusFrame.moveGridTaskViewFocus(null);
@@ -1977,8 +1840,7 @@
         mStackScroller.stopScroller();
         mStackScroller.stopBoundScrollAnimation();
 
-        setRelativeFocusedTask(true, false /* stackTasksOnly */, true /* animated */, false,
-                event.timerIndicatorDuration);
+        setRelativeFocusedTask(true, false /* stackTasksOnly */, true /* animated */, false, 0);
     }
 
     public final void onBusEvent(FocusPreviousTaskViewEvent event) {
@@ -2002,8 +1864,7 @@
                     EventBus.getDefault().send(new FocusPreviousTaskViewEvent());
                     break;
                 case DOWN:
-                    EventBus.getDefault().send(
-                        new FocusNextTaskViewEvent(0 /* timerIndicatorDuration */));
+                    EventBus.getDefault().send(new FocusNextTaskViewEvent());
                     break;
             }
         }
@@ -2014,7 +1875,7 @@
         mUIDozeTrigger.poke();
 
         RecentsDebugFlags debugFlags = Recents.getDebugFlags();
-        if (debugFlags.isFastToggleRecentsEnabled() && mFocusedTask != null) {
+        if (mFocusedTask != null) {
             TaskView tv = getChildViewForTask(mFocusedTask);
             if (tv != null) {
                 tv.getHeaderView().cancelFocusTimerIndicator();
@@ -2026,11 +1887,6 @@
         // Ensure that the drag task is not animated
         addIgnoreTask(event.task);
 
-        if (event.task.isFreeformTask()) {
-            // Animate to the front of the stack
-            mStackScroller.animateScroll(mLayoutAlgorithm.mInitialScrollP, null);
-        }
-
         // Enlarge the dragged view slightly
         float finalScale = event.taskView.getScaleX() * DRAG_SCALE_FACTOR;
         mLayoutAlgorithm.getStackTransform(event.task, getScroller().getStackScroll(),
@@ -2042,22 +1898,14 @@
                 new AnimationProps(DRAG_SCALE_DURATION, Interpolators.FAST_OUT_SLOW_IN));
     }
 
-    public final void onBusEvent(DragStartInitializeDropTargetsEvent event) {
-        SystemServicesProxy ssp = Recents.getSystemServices();
-        if (ssp.hasFreeformWorkspaceSupport()) {
-            event.handler.registerDropTargetForCurrentDrag(mStackDropTarget);
-            event.handler.registerDropTargetForCurrentDrag(mFreeformWorkspaceDropTarget);
-        }
-    }
-
     public final void onBusEvent(DragDropTargetChangedEvent event) {
         AnimationProps animation = new AnimationProps(SLOW_SYNC_STACK_DURATION,
                 Interpolators.FAST_OUT_SLOW_IN);
         boolean ignoreTaskOverrides = false;
-        if (event.dropTarget instanceof TaskStack.DockState) {
+        if (event.dropTarget instanceof DockState) {
             // Calculate the new task stack bounds that matches the window size that Recents will
             // have after the drop
-            final TaskStack.DockState dockState = (TaskStack.DockState) event.dropTarget;
+            final DockState dockState = (DockState) event.dropTarget;
             Rect systemInsets = new Rect(mStableLayoutAlgorithm.mSystemInsets);
             // When docked, the nav bar insets are consumed and the activity is measured without
             // insets.  However, the window bounds include the insets, so we need to subtract them
@@ -2069,8 +1917,7 @@
                     height, mDividerSize, systemInsets,
                     mLayoutAlgorithm, getResources(), mWindowRect));
             mLayoutAlgorithm.setSystemInsets(systemInsets);
-            mLayoutAlgorithm.initialize(mDisplayRect, mWindowRect, mStackBounds,
-                    TaskStackLayoutAlgorithm.StackState.getStackStateForStack(mStack));
+            mLayoutAlgorithm.initialize(mDisplayRect, mWindowRect, mStackBounds);
             updateLayoutAlgorithm(true /* boundScroll */);
             ignoreTaskOverrides = true;
         } else {
@@ -2085,39 +1932,13 @@
 
     public final void onBusEvent(final DragEndEvent event) {
         // We don't handle drops on the dock regions
-        if (event.dropTarget instanceof TaskStack.DockState) {
+        if (event.dropTarget instanceof DockState) {
             // However, we do need to reset the overrides, since the last state of this task stack
             // view layout was ignoring task overrides (see DragDropTargetChangedEvent handler)
             mLayoutAlgorithm.clearUnfocusedTaskOverrides();
             return;
         }
 
-        boolean isFreeformTask = event.task.isFreeformTask();
-        boolean hasChangedWindowingMode =
-                (!isFreeformTask && event.dropTarget == mFreeformWorkspaceDropTarget) ||
-                        (isFreeformTask && event.dropTarget == mStackDropTarget);
-
-        if (hasChangedWindowingMode) {
-            // Move the task to the right position in the stack (ie. the front of the stack if
-            // freeform or the front of the stack if fullscreen). Note, we MUST move the tasks
-            // before we update their stack ids, otherwise, the keys will have changed.
-            if (event.dropTarget == mFreeformWorkspaceDropTarget) {
-                mStack.setTaskWindowingMode(event.task, WINDOWING_MODE_FREEFORM);
-            } else if (event.dropTarget == mStackDropTarget) {
-                mStack.setTaskWindowingMode(event.task, WINDOWING_MODE_FULLSCREEN);
-            }
-            updateLayoutAlgorithm(true /* boundScroll */);
-
-            // Move the task to the new stack in the system after the animation completes
-            event.addPostAnimationCallback(new Runnable() {
-                @Override
-                public void run() {
-                    SystemServicesProxy ssp = Recents.getSystemServices();
-                    ssp.setTaskWindowingMode(event.task.key.id, event.task.key.windowingMode);
-                }
-            });
-        }
-
         // Restore the task, so that relayout will apply to it below
         removeIgnoreTask(event.task);
 
@@ -2152,13 +1973,6 @@
         event.getAnimationTrigger().increment();
     }
 
-    public final void onBusEvent(IterateRecentsEvent event) {
-        if (!mEnterAnimationComplete) {
-            // Cancel the previous task's window transition before animating the focused state
-            EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(null));
-        }
-    }
-
     public final void onBusEvent(EnterRecentsWindowAnimationCompletedEvent event) {
         mEnterAnimationComplete = true;
         tryStartEnterAnimation();
@@ -2177,9 +1991,7 @@
             // Add a runnable to the post animation ref counter to clear all the views
             trigger.addLastDecrementRunnable(() -> {
                 // Start the dozer to trigger to trigger any UI that shows after a timeout
-                if (!Recents.getSystemServices().hasFreeformWorkspaceSupport()) {
-                    mUIDozeTrigger.startDozing();
-                }
+                mUIDozeTrigger.startDozing();
 
                 // Update the focused state here -- since we only set the focused task without
                 // requesting view focus in onFirstLayout(), actually request view focus and
@@ -2202,18 +2014,6 @@
         mStackReloaded = false;
     }
 
-    public final void onBusEvent(UpdateFreeformTaskViewVisibilityEvent event) {
-        List<TaskView> taskViews = getTaskViews();
-        int taskViewCount = taskViews.size();
-        for (int i = 0; i < taskViewCount; i++) {
-            TaskView tv = taskViews.get(i);
-            Task task = tv.getTask();
-            if (task.isFreeformTask()) {
-                tv.setVisibility(event.visible ? View.VISIBLE : View.INVISIBLE);
-            }
-        }
-    }
-
     public final void onBusEvent(final MultiWindowStateChangedEvent event) {
         if (event.inMultiWindow || !event.showDeferredAnimation) {
             setTasks(event.stack, true /* allowNotifyStackChanges */);
@@ -2315,27 +2115,6 @@
     }
 
     /**
-     * Starts an alpha animation on the freeform workspace background.
-     */
-    private void animateFreeformWorkspaceBackgroundAlpha(int targetAlpha,
-            AnimationProps animation) {
-        if (mFreeformWorkspaceBackground.getAlpha() == targetAlpha) {
-            return;
-        }
-
-        Utilities.cancelAnimationWithoutCallbacks(mFreeformWorkspaceBackgroundAnimator);
-        mFreeformWorkspaceBackgroundAnimator = ObjectAnimator.ofInt(mFreeformWorkspaceBackground,
-                Utilities.DRAWABLE_ALPHA, mFreeformWorkspaceBackground.getAlpha(), targetAlpha);
-        mFreeformWorkspaceBackgroundAnimator.setStartDelay(
-                animation.getDuration(AnimationProps.ALPHA));
-        mFreeformWorkspaceBackgroundAnimator.setDuration(
-                animation.getDuration(AnimationProps.ALPHA));
-        mFreeformWorkspaceBackgroundAnimator.setInterpolator(
-                animation.getInterpolator(AnimationProps.ALPHA));
-        mFreeformWorkspaceBackgroundAnimator.start();
-    }
-
-    /**
      * Returns the insert index for the task in the current set of task views. If the given task
      * is already in the task view list, then this method returns the insert index assuming it
      * is first removed at the previous index.
@@ -2421,6 +2200,24 @@
         }
     }
 
+    /**
+     * Updates {@param transforms} to be the same size as {@param tasks}.
+     */
+    private void matchTaskListSize(List<Task> tasks, List<TaskViewTransform> transforms) {
+        // We can reuse the task transforms where possible to reduce object allocation
+        int taskTransformCount = transforms.size();
+        int taskCount = tasks.size();
+        if (taskTransformCount < taskCount) {
+            // If there are less transforms than tasks, then add as many transforms as necessary
+            for (int i = taskTransformCount; i < taskCount; i++) {
+                transforms.add(new TaskViewTransform());
+            }
+        } else if (taskTransformCount > taskCount) {
+            // If there are more transforms than tasks, then just subset the transform list
+            transforms.subList(taskCount, taskTransformCount).clear();
+        }
+    }
+
     public void dump(String prefix, PrintWriter writer) {
         String innerPrefix = prefix + "  ";
         String id = Integer.toHexString(System.identityHashCode(this));
diff --git a/com/android/systemui/recents/views/TaskStackViewScroller.java b/com/android/systemui/recents/views/TaskStackViewScroller.java
index 0b20b10..6b23977 100644
--- a/com/android/systemui/recents/views/TaskStackViewScroller.java
+++ b/com/android/systemui/recents/views/TaskStackViewScroller.java
@@ -24,7 +24,6 @@
 import android.content.Context;
 import android.util.FloatProperty;
 import android.util.Log;
-import android.util.MutableFloat;
 import android.util.Property;
 import android.view.ViewConfiguration;
 import android.view.ViewDebug;
@@ -33,7 +32,8 @@
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
 import com.android.systemui.recents.Recents;
-import com.android.systemui.recents.misc.Utilities;
+import com.android.systemui.shared.recents.utilities.AnimationProps;
+import com.android.systemui.shared.recents.utilities.Utilities;
 import com.android.systemui.recents.views.lowram.TaskStackLowRamLayoutAlgorithm;
 import com.android.systemui.statusbar.FlingAnimationUtils;
 
diff --git a/com/android/systemui/recents/views/TaskStackViewTouchHandler.java b/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
index 32a249c..b9ca248 100644
--- a/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
+++ b/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
@@ -21,7 +21,6 @@
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Path;
-import android.graphics.Rect;
 import android.util.ArrayMap;
 import android.util.MutableBoolean;
 import android.view.InputDevice;
@@ -45,9 +44,9 @@
 import com.android.systemui.recents.events.ui.StackViewScrolledEvent;
 import com.android.systemui.recents.events.ui.TaskViewDismissedEvent;
 import com.android.systemui.recents.misc.FreePathInterpolator;
-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.shared.recents.utilities.AnimationProps;
+import com.android.systemui.shared.recents.utilities.Utilities;
+import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.statusbar.FlingAnimationUtils;
 
 import java.util.ArrayList;
@@ -403,18 +402,6 @@
             return;
         }
 
-        // If tapping on the freeform workspace background, just launch the first freeform task
-        SystemServicesProxy ssp = Recents.getSystemServices();
-        if (ssp.hasFreeformWorkspaceSupport()) {
-            Rect freeformRect = mSv.mLayoutAlgorithm.mFreeformRect;
-            if (freeformRect.top <= y && y <= freeformRect.bottom) {
-                if (mSv.launchFreeformTasks()) {
-                    // TODO: Animate Recents away as we launch the freeform tasks
-                    return;
-                }
-            }
-        }
-
         // The user intentionally tapped on the background, which is like a tap on the "desktop".
         // Hide recents and transition to the launcher.
         EventBus.getDefault().send(new HideRecentsEvent(false, true));
diff --git a/com/android/systemui/recents/views/TaskView.java b/com/android/systemui/recents/views/TaskView.java
index 9d63964..b440847 100644
--- a/com/android/systemui/recents/views/TaskView.java
+++ b/com/android/systemui/recents/views/TaskView.java
@@ -16,8 +16,6 @@
 
 package com.android.systemui.recents.views;
 
-import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
-
 import android.animation.Animator;
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
@@ -53,10 +51,10 @@
 import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent;
 import com.android.systemui.recents.misc.ReferenceCountedTrigger;
 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.recents.model.TaskStack;
-import com.android.systemui.recents.model.ThumbnailData;
+import com.android.systemui.shared.recents.utilities.AnimationProps;
+import com.android.systemui.shared.recents.utilities.Utilities;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.model.ThumbnailData;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -196,9 +194,7 @@
      * Called from RecentsActivity when it is relaunched.
      */
     void onReload(boolean isResumingFromVisible) {
-        if (!Recents.getSystemServices().hasFreeformWorkspaceSupport()) {
-            resetNoUserInteractionState();
-        }
+        resetNoUserInteractionState();
         if (!isResumingFromVisible) {
             resetViewProperties();
         }
@@ -415,9 +411,7 @@
      * view.
      */
     boolean shouldClipViewInStack() {
-        // Never clip for freeform tasks or if invisible
-        if (mTask.isFreeformTask() || getVisibility() != View.VISIBLE ||
-                Recents.getConfiguration().isLowRamDevice) {
+        if (getVisibility() != View.VISIBLE || Recents.getConfiguration().isLowRamDevice) {
             return false;
         }
         return mClipViewInStack;
@@ -715,7 +709,7 @@
     /**** Events ****/
 
     public final void onBusEvent(DragEndEvent event) {
-        if (!(event.dropTarget instanceof TaskStack.DockState)) {
+        if (!(event.dropTarget instanceof DockState)) {
             event.addPostAnimationCallback(() -> {
                 // Reset the clip state for the drag view after the end animation completes
                 setClipViewInStack(true);
diff --git a/com/android/systemui/recents/views/TaskViewAccessibilityDelegate.java b/com/android/systemui/recents/views/TaskViewAccessibilityDelegate.java
index 0c6b6b8..0fc507b 100644
--- a/com/android/systemui/recents/views/TaskViewAccessibilityDelegate.java
+++ b/com/android/systemui/recents/views/TaskViewAccessibilityDelegate.java
@@ -28,11 +28,10 @@
 import com.android.systemui.R;
 import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.events.ui.ShowApplicationInfoEvent;
 import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
 import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent;
-import com.android.systemui.recents.misc.Utilities;
-import com.android.systemui.recents.model.TaskStack;
+import com.android.systemui.shared.recents.utilities.Utilities;
+import com.android.systemui.shared.recents.model.TaskStack;
 
 public class TaskViewAccessibilityDelegate extends View.AccessibilityDelegate {
     private static final String TAG = "TaskViewAccessibilityDelegate";
@@ -61,14 +60,14 @@
         super.onInitializeAccessibilityNodeInfo(host, info);
         if (ActivityManager.supportsSplitScreenMultiWindow(mTaskView.getContext())
                 && !Recents.getSystemServices().hasDockedTask()) {
-            TaskStack.DockState[] dockStates = Recents.getConfiguration()
+            DockState[] dockStates = Recents.getConfiguration()
                     .getDockStatesForCurrentOrientation();
-            for (TaskStack.DockState dockState: dockStates) {
-                if (dockState == TaskStack.DockState.TOP) {
+            for (DockState dockState: dockStates) {
+                if (dockState == DockState.TOP) {
                     info.addAction(mActions.get(SPLIT_TASK_TOP));
-                } else if (dockState == TaskStack.DockState.LEFT) {
+                } else if (dockState == DockState.LEFT) {
                     info.addAction(mActions.get(SPLIT_TASK_LEFT));
-                } else if (dockState == TaskStack.DockState.RIGHT) {
+                } else if (dockState == DockState.RIGHT) {
                     info.addAction(mActions.get(SPLIT_TASK_RIGHT));
                 }
             }
@@ -78,11 +77,11 @@
     @Override
     public boolean performAccessibilityAction(View host, int action, Bundle args) {
         if (action == SPLIT_TASK_TOP) {
-            simulateDragIntoMultiwindow(TaskStack.DockState.TOP);
+            simulateDragIntoMultiwindow(DockState.TOP);
         } else if (action == SPLIT_TASK_LEFT) {
-            simulateDragIntoMultiwindow(TaskStack.DockState.LEFT);
+            simulateDragIntoMultiwindow(DockState.LEFT);
         } else if (action == SPLIT_TASK_RIGHT) {
-            simulateDragIntoMultiwindow(TaskStack.DockState.RIGHT);
+            simulateDragIntoMultiwindow(DockState.RIGHT);
         } else {
             return super.performAccessibilityAction(host, action, args);
         }
@@ -90,8 +89,7 @@
     }
 
     /** Simulate a user drag event to split the screen to the respected side */
-    private void simulateDragIntoMultiwindow(TaskStack.DockState dockState) {
-        int orientation = Utilities.getAppConfiguration(mTaskView.getContext()).orientation;
+    private void simulateDragIntoMultiwindow(DockState dockState) {
         EventBus.getDefault().send(new DragStartEvent(mTaskView.getTask(), mTaskView,
                 new Point(0,0), false /* isUserTouchInitiated */));
         EventBus.getDefault().send(new DragEndEvent(mTaskView.getTask(), mTaskView, dockState));
diff --git a/com/android/systemui/recents/views/TaskViewHeader.java b/com/android/systemui/recents/views/TaskViewHeader.java
index 198ecae..0272a90 100644
--- a/com/android/systemui/recents/views/TaskViewHeader.java
+++ b/com/android/systemui/recents/views/TaskViewHeader.java
@@ -17,8 +17,6 @@
 package com.android.systemui.recents.views;
 
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 
 import android.animation.Animator;
@@ -33,7 +31,6 @@
 import android.graphics.ColorFilter;
 import android.graphics.Paint;
 import android.graphics.PixelFormat;
-import android.graphics.PorterDuff;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.RippleDrawable;
@@ -59,8 +56,10 @@
 import com.android.systemui.recents.events.activity.LaunchTaskEvent;
 import com.android.systemui.recents.events.ui.ShowApplicationInfoEvent;
 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.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.PackageManagerWrapper;
+import com.android.systemui.shared.recents.utilities.Utilities;
+import com.android.systemui.shared.recents.model.Task;
 
 /* The task bar view */
 public class TaskViewHeader extends FrameLayout
@@ -164,8 +163,6 @@
     float mDimAlpha;
     Drawable mLightDismissDrawable;
     Drawable mDarkDismissDrawable;
-    Drawable mLightFreeformIcon;
-    Drawable mDarkFreeformIcon;
     Drawable mLightFullscreenIcon;
     Drawable mDarkFullscreenIcon;
     Drawable mLightInfoIcon;
@@ -173,6 +170,8 @@
     int mTaskBarViewLightTextColor;
     int mTaskBarViewDarkTextColor;
     int mDisabledTaskBarBackgroundColor;
+    String mDismissDescFormat;
+    String mAppInfoDescFormat;
     int mTaskWindowingMode = WINDOWING_MODE_UNDEFINED;
 
     // Header background
@@ -215,14 +214,15 @@
         mHighlightHeight = res.getDimensionPixelSize(R.dimen.recents_task_view_highlight);
         mTaskBarViewLightTextColor = context.getColor(R.color.recents_task_bar_light_text_color);
         mTaskBarViewDarkTextColor = context.getColor(R.color.recents_task_bar_dark_text_color);
-        mLightFreeformIcon = context.getDrawable(R.drawable.recents_move_task_freeform_light);
-        mDarkFreeformIcon = context.getDrawable(R.drawable.recents_move_task_freeform_dark);
         mLightFullscreenIcon = context.getDrawable(R.drawable.recents_move_task_fullscreen_light);
         mDarkFullscreenIcon = context.getDrawable(R.drawable.recents_move_task_fullscreen_dark);
         mLightInfoIcon = context.getDrawable(R.drawable.recents_info_light);
         mDarkInfoIcon = context.getDrawable(R.drawable.recents_info_dark);
         mDisabledTaskBarBackgroundColor =
                 context.getColor(R.color.recents_task_bar_disabled_background_color);
+        mDismissDescFormat = mContext.getString(
+                R.string.accessibility_recents_item_will_be_dismissed);
+        mAppInfoDescFormat = mContext.getString(R.string.accessibility_recents_item_open_app_info);
 
         // Configure the background and dim
         mBackground = new HighlightColorDrawable();
@@ -249,9 +249,6 @@
         mIconView.setOnLongClickListener(this);
         mTitleView = findViewById(R.id.title);
         mDismissButton = findViewById(R.id.dismiss_task);
-        if (ssp.hasFreeformWorkspaceSupport()) {
-            mMoveTaskButton = findViewById(R.id.move_task);
-        }
 
         onConfigurationChanged();
     }
@@ -341,20 +338,6 @@
         boolean showDismissIcon = true;
         int rightInset = width - getMeasuredWidth();
 
-        if (mTask != null && mTask.isFreeformTask()) {
-            // For freeform tasks, we always show the app icon, and only show the title, move-task
-            // icon, and the dismiss icon if there is room
-            int appIconWidth = mIconView.getMeasuredWidth();
-            int titleWidth = (int) mTitleView.getPaint().measureText(mTask.title);
-            int dismissWidth = mDismissButton.getMeasuredWidth();
-            int moveTaskWidth = mMoveTaskButton != null
-                    ? mMoveTaskButton.getMeasuredWidth()
-                    : 0;
-            showTitle = width >= (appIconWidth + dismissWidth + moveTaskWidth + titleWidth);
-            showMoveIcon = width >= (appIconWidth + dismissWidth + moveTaskWidth);
-            showDismissIcon = width >= (appIconWidth + dismissWidth);
-        }
-
         mTitleView.setVisibility(showTitle ? View.VISIBLE : View.INVISIBLE);
         if (mMoveTaskButton != null) {
             mMoveTaskButton.setVisibility(showMoveIcon ? View.VISIBLE : View.INVISIBLE);
@@ -477,44 +460,14 @@
                 mTaskBarViewLightTextColor : mTaskBarViewDarkTextColor);
         mDismissButton.setImageDrawable(t.useLightOnPrimaryColor ?
                 mLightDismissDrawable : mDarkDismissDrawable);
-        mDismissButton.setContentDescription(t.dismissDescription);
+        mDismissButton.setContentDescription(String.format(mDismissDescFormat, t.titleDescription));
         mDismissButton.setOnClickListener(this);
         mDismissButton.setClickable(false);
         ((RippleDrawable) mDismissButton.getBackground()).setForceSoftware(true);
 
-        // When freeform workspaces are enabled, then update the move-task button depending on the
-        // current task
-        if (mMoveTaskButton != null) {
-            if (t.isFreeformTask()) {
-                mTaskWindowingMode = WINDOWING_MODE_FULLSCREEN;
-                mMoveTaskButton.setImageDrawable(t.useLightOnPrimaryColor
-                        ? mLightFullscreenIcon
-                        : mDarkFullscreenIcon);
-            } else {
-                mTaskWindowingMode = WINDOWING_MODE_FREEFORM;
-                mMoveTaskButton.setImageDrawable(t.useLightOnPrimaryColor
-                        ? mLightFreeformIcon
-                        : mDarkFreeformIcon);
-            }
-            mMoveTaskButton.setOnClickListener(this);
-            mMoveTaskButton.setClickable(false);
-            ((RippleDrawable) mMoveTaskButton.getBackground()).setForceSoftware(true);
-        }
-
-        if (Recents.getDebugFlags().isFastToggleRecentsEnabled()) {
-            if (mFocusTimerIndicator == null) {
-                mFocusTimerIndicator = (ProgressBar) Utilities.findViewStubById(this,
-                        R.id.focus_timer_indicator_stub).inflate();
-            }
-            mFocusTimerIndicator.getProgressDrawable()
-                    .setColorFilter(
-                            getSecondaryColor(t.colorPrimary, t.useLightOnPrimaryColor),
-                            PorterDuff.Mode.SRC_IN);
-        }
-
         // In accessibility, a single click on the focused app info button will show it
         if (touchExplorationEnabled) {
-            mIconView.setContentDescription(t.appInfoDescription);
+            mIconView.setContentDescription(String.format(mAppInfoDescFormat, t.titleDescription));
             mIconView.setOnClickListener(this);
             mIconView.setClickable(true);
         }
@@ -651,7 +604,7 @@
         SystemServicesProxy ssp = Recents.getSystemServices();
         ComponentName cn = mTask.key.getComponent();
         int userId = mTask.key.userId;
-        ActivityInfo activityInfo = ssp.getActivityInfo(cn, userId);
+        ActivityInfo activityInfo = PackageManagerWrapper.getInstance().getActivityInfo(cn, userId);
         if (activityInfo == null) {
             return;
         }
@@ -671,11 +624,12 @@
         }
 
         // Update the overlay contents for the current app
-        mAppTitleView.setText(ssp.getBadgedApplicationLabel(activityInfo.applicationInfo, userId));
+        mAppTitleView.setText(ActivityManagerWrapper.getInstance().getBadgedApplicationLabel(
+                activityInfo.applicationInfo, userId));
         mAppTitleView.setTextColor(mTask.useLightOnPrimaryColor ?
                 mTaskBarViewLightTextColor : mTaskBarViewDarkTextColor);
-        mAppIconView.setImageDrawable(ssp.getBadgedApplicationIcon(activityInfo.applicationInfo,
-                userId));
+        mAppIconView.setImageDrawable(ActivityManagerWrapper.getInstance().getBadgedApplicationIcon(
+                activityInfo.applicationInfo, userId));
         mAppInfoView.setImageDrawable(mTask.useLightOnPrimaryColor
                 ? mLightInfoIcon
                 : mDarkInfoIcon);
diff --git a/com/android/systemui/recents/views/TaskViewThumbnail.java b/com/android/systemui/recents/views/TaskViewThumbnail.java
index a2190b3..4152b05 100644
--- a/com/android/systemui/recents/views/TaskViewThumbnail.java
+++ b/com/android/systemui/recents/views/TaskViewThumbnail.java
@@ -37,9 +37,9 @@
 import com.android.systemui.R;
 import com.android.systemui.recents.events.EventBus;
 import com.android.systemui.recents.events.ui.TaskSnapshotChangedEvent;
-import com.android.systemui.recents.misc.Utilities;
-import com.android.systemui.recents.model.Task;
-import com.android.systemui.recents.model.ThumbnailData;
+import com.android.systemui.shared.recents.utilities.Utilities;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.model.ThumbnailData;
 import java.io.PrintWriter;
 
 
@@ -245,10 +245,6 @@
     public void updateThumbnailMatrix() {
         mThumbnailScale = 1f;
         if (mBitmapShader != null && mThumbnailData != null) {
-            // We consider this a stack task if it is not freeform (ie. has no bounds) or has been
-            // dragged into the stack from the freeform workspace
-            boolean isStackTask = !mTask.isFreeformTask() || mTask.bounds == null;
-            int xOffset, yOffset = 0;
             if (mTaskViewRect.isEmpty()) {
                 // If we haven't measured , skip the thumbnail drawing and only draw the background
                 // color
@@ -266,7 +262,7 @@
                     mThumbnailScale = (float) (mTaskViewRect.height() - mTitleBarHeight)
                             / (float) mThumbnailRect.height();
                 }
-            } else if (isStackTask) {
+            } else {
                 float invThumbnailScale = 1f / mFullscreenThumbnailScale;
                 if (mDisplayOrientation == Configuration.ORIENTATION_PORTRAIT) {
                     if (mThumbnailData.orientation == Configuration.ORIENTATION_PORTRAIT) {
@@ -283,12 +279,6 @@
                     // Otherwise, scale the screenshot to fit 1:1 in the current orientation
                     mThumbnailScale = invThumbnailScale;
                 }
-            } else {
-                // Otherwise, if this is a freeform task with task bounds, then scale the thumbnail
-                // to fit the entire bitmap into the task bounds
-                mThumbnailScale = Math.min(
-                        (float) mTaskViewRect.width() / mThumbnailRect.width(),
-                        (float) mTaskViewRect.height() / mThumbnailRect.height());
             }
             mMatrix.setTranslate(-mThumbnailData.insets.left * mFullscreenThumbnailScale,
                     -mThumbnailData.insets.top * mFullscreenThumbnailScale);
diff --git a/com/android/systemui/recents/views/TaskViewTransform.java b/com/android/systemui/recents/views/TaskViewTransform.java
index 397f24e..9b717e0 100644
--- a/com/android/systemui/recents/views/TaskViewTransform.java
+++ b/com/android/systemui/recents/views/TaskViewTransform.java
@@ -21,11 +21,11 @@
 import android.animation.PropertyValuesHolder;
 import android.graphics.Rect;
 import android.graphics.RectF;
-import android.util.IntProperty;
 import android.util.Property;
 import android.view.View;
 
-import com.android.systemui.recents.misc.Utilities;
+import com.android.systemui.shared.recents.utilities.AnimationProps;
+import com.android.systemui.shared.recents.utilities.Utilities;
 
 import java.util.ArrayList;
 
@@ -59,7 +59,7 @@
 
     public boolean visible = false;
 
-    // This is a window-space rect used for positioning the task in the stack and freeform workspace
+    // This is a window-space rect used for positioning the task in the stack
     public RectF rect = new RectF();
 
     /**
diff --git a/com/android/systemui/recents/views/grid/TaskGridLayoutAlgorithm.java b/com/android/systemui/recents/views/grid/TaskGridLayoutAlgorithm.java
index c513202..ccda4b5 100644
--- a/com/android/systemui/recents/views/grid/TaskGridLayoutAlgorithm.java
+++ b/com/android/systemui/recents/views/grid/TaskGridLayoutAlgorithm.java
@@ -25,10 +25,9 @@
 import android.view.WindowManager;
 
 import com.android.systemui.R;
-import com.android.systemui.recents.events.ui.focus.NavigateTaskViewEvent;
 import com.android.systemui.recents.events.ui.focus.NavigateTaskViewEvent.Direction;
-import com.android.systemui.recents.misc.Utilities;
-import com.android.systemui.recents.model.Task;
+import com.android.systemui.shared.recents.utilities.Utilities;
+import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.recents.views.TaskStackLayoutAlgorithm;
 import com.android.systemui.recents.views.TaskViewTransform;
 
diff --git a/com/android/systemui/recents/views/grid/TaskViewFocusFrame.java b/com/android/systemui/recents/views/grid/TaskViewFocusFrame.java
index 86ed583..95f1d58 100644
--- a/com/android/systemui/recents/views/grid/TaskViewFocusFrame.java
+++ b/com/android/systemui/recents/views/grid/TaskViewFocusFrame.java
@@ -23,7 +23,7 @@
 
 import android.view.ViewTreeObserver.OnGlobalFocusChangeListener;
 import com.android.systemui.R;
-import com.android.systemui.recents.model.TaskStack;
+import com.android.systemui.shared.recents.model.TaskStack;
 import com.android.systemui.recents.views.TaskStackView;
 
 public class TaskViewFocusFrame extends View implements OnGlobalFocusChangeListener {
diff --git a/com/android/systemui/recents/views/lowram/TaskStackLowRamLayoutAlgorithm.java b/com/android/systemui/recents/views/lowram/TaskStackLowRamLayoutAlgorithm.java
index 17e6b9e..49cac26 100644
--- a/com/android/systemui/recents/views/lowram/TaskStackLowRamLayoutAlgorithm.java
+++ b/com/android/systemui/recents/views/lowram/TaskStackLowRamLayoutAlgorithm.java
@@ -23,8 +23,8 @@
 import com.android.systemui.R;
 import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.RecentsActivityLaunchState;
-import com.android.systemui.recents.misc.Utilities;
-import com.android.systemui.recents.model.Task;
+import com.android.systemui.shared.recents.utilities.Utilities;
+import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.recents.views.TaskStackLayoutAlgorithm;
 import com.android.systemui.recents.views.TaskViewTransform;
 
diff --git a/com/android/systemui/shared/recents/model/BackgroundTaskLoader.java b/com/android/systemui/shared/recents/model/BackgroundTaskLoader.java
new file mode 100644
index 0000000..ddd27b0
--- /dev/null
+++ b/com/android/systemui/shared/recents/model/BackgroundTaskLoader.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2017 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.shared.recents.model;
+
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.util.Log;
+
+import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.PackageManagerWrapper;
+
+/**
+ * Background task resource loader
+ */
+class BackgroundTaskLoader implements Runnable {
+    static String TAG = "BackgroundTaskLoader";
+    static boolean DEBUG = false;
+
+    private Context mContext;
+    private final HandlerThread mLoadThread;
+    private final Handler mLoadThreadHandler;
+    private final Handler mMainThreadHandler;
+
+    private final TaskResourceLoadQueue mLoadQueue;
+    private final TaskKeyLruCache<Drawable> mIconCache;
+    private final BitmapDrawable mDefaultIcon;
+
+    private boolean mStarted;
+    private boolean mCancelled;
+    private boolean mWaitingOnLoadQueue;
+
+    private final OnIdleChangedListener mOnIdleChangedListener;
+
+    /** Constructor, creates a new loading thread that loads task resources in the background */
+    public BackgroundTaskLoader(TaskResourceLoadQueue loadQueue,
+            TaskKeyLruCache<Drawable> iconCache, BitmapDrawable defaultIcon,
+            OnIdleChangedListener onIdleChangedListener) {
+        mLoadQueue = loadQueue;
+        mIconCache = iconCache;
+        mDefaultIcon = defaultIcon;
+        mMainThreadHandler = new Handler();
+        mOnIdleChangedListener = onIdleChangedListener;
+        mLoadThread = new HandlerThread("Recents-TaskResourceLoader",
+                android.os.Process.THREAD_PRIORITY_BACKGROUND);
+        mLoadThread.start();
+        mLoadThreadHandler = new Handler(mLoadThread.getLooper());
+    }
+
+    /** Restarts the loader thread */
+    void start(Context context) {
+        mContext = context;
+        mCancelled = false;
+        if (!mStarted) {
+            // Start loading on the load thread
+            mStarted = true;
+            mLoadThreadHandler.post(this);
+        } else {
+            // Notify the load thread to start loading again
+            synchronized (mLoadThread) {
+                mLoadThread.notifyAll();
+            }
+        }
+    }
+
+    /** Requests the loader thread to stop after the current iteration */
+    void stop() {
+        // Mark as cancelled for the thread to pick up
+        mCancelled = true;
+        // If we are waiting for the load queue for more tasks, then we can just reset the
+        // Context now, since nothing is using it
+        if (mWaitingOnLoadQueue) {
+            mContext = null;
+        }
+    }
+
+    @Override
+    public void run() {
+        while (true) {
+            if (mCancelled) {
+                // We have to unset the context here, since the background thread may be using it
+                // when we call stop()
+                mContext = null;
+                // If we are cancelled, then wait until we are started again
+                synchronized(mLoadThread) {
+                    try {
+                        mLoadThread.wait();
+                    } catch (InterruptedException ie) {
+                        ie.printStackTrace();
+                    }
+                }
+            } else {
+                // If we've stopped the loader, then fall through to the above logic to wait on
+                // the load thread
+                processLoadQueueItem();
+
+                // If there are no other items in the list, then just wait until something is added
+                if (!mCancelled && mLoadQueue.isEmpty()) {
+                    synchronized(mLoadQueue) {
+                        try {
+                            mWaitingOnLoadQueue = true;
+                            mMainThreadHandler.post(
+                                    () -> mOnIdleChangedListener.onIdleChanged(true));
+                            mLoadQueue.wait();
+                            mMainThreadHandler.post(
+                                    () -> mOnIdleChangedListener.onIdleChanged(false));
+                            mWaitingOnLoadQueue = false;
+                        } catch (InterruptedException ie) {
+                            ie.printStackTrace();
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * This needs to be in a separate method to work around an surprising interpreter behavior:
+     * The register will keep the local reference to cachedThumbnailData even if it falls out of
+     * scope. Putting it into a method fixes this issue.
+     */
+    private void processLoadQueueItem() {
+        // Load the next item from the queue
+        final Task t = mLoadQueue.nextTask();
+        if (t != null) {
+            Drawable cachedIcon = mIconCache.get(t.key);
+
+            // Load the icon if it is stale or we haven't cached one yet
+            if (cachedIcon == null) {
+                cachedIcon = ActivityManagerWrapper.getInstance().getBadgedTaskDescriptionIcon(
+                        mContext, t.taskDescription, t.key.userId, mContext.getResources());
+
+                if (cachedIcon == null) {
+                    ActivityInfo info = PackageManagerWrapper.getInstance().getActivityInfo(
+                            t.key.getComponent(), t.key.userId);
+                    if (info != null) {
+                        if (DEBUG) Log.d(TAG, "Loading icon: " + t.key);
+                        cachedIcon = ActivityManagerWrapper.getInstance().getBadgedActivityIcon(
+                                info, t.key.userId);
+                    }
+                }
+
+                if (cachedIcon == null) {
+                    cachedIcon = mDefaultIcon;
+                }
+
+                // At this point, even if we can't load the icon, we will set the
+                // default icon.
+                mIconCache.put(t.key, cachedIcon);
+            }
+
+            if (DEBUG) Log.d(TAG, "Loading thumbnail: " + t.key);
+            final ThumbnailData thumbnailData =
+                    ActivityManagerWrapper.getInstance().getTaskThumbnail(t.key.id,
+                            true /* reducedResolution */);
+
+            if (!mCancelled) {
+                // Notify that the task data has changed
+                final Drawable finalIcon = cachedIcon;
+                mMainThreadHandler.post(
+                        () -> t.notifyTaskDataLoaded(thumbnailData, finalIcon));
+            }
+        }
+    }
+
+    interface OnIdleChangedListener {
+        void onIdleChanged(boolean idle);
+    }
+}
diff --git a/com/android/systemui/shared/recents/model/FilteredTaskList.java b/com/android/systemui/shared/recents/model/FilteredTaskList.java
new file mode 100644
index 0000000..898d64a
--- /dev/null
+++ b/com/android/systemui/shared/recents/model/FilteredTaskList.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2017 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.shared.recents.model;
+
+import android.util.ArrayMap;
+import android.util.SparseArray;
+
+import com.android.systemui.shared.recents.model.Task.TaskKey;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A list of filtered tasks.
+ */
+class FilteredTaskList {
+
+    private final ArrayList<Task> mTasks = new ArrayList<>();
+    private final ArrayList<Task> mFilteredTasks = new ArrayList<>();
+    private final ArrayMap<TaskKey, Integer> mFilteredTaskIndices = new ArrayMap<>();
+    private TaskFilter mFilter;
+
+    /** Sets the task filter, and returns whether the set of filtered tasks have changed. */
+    boolean setFilter(TaskFilter filter) {
+        ArrayList<Task> prevFilteredTasks = new ArrayList<>(mFilteredTasks);
+        mFilter = filter;
+        updateFilteredTasks();
+        return !prevFilteredTasks.equals(mFilteredTasks);
+    }
+
+    /** Adds a new task to the task list */
+    void add(Task t) {
+        mTasks.add(t);
+        updateFilteredTasks();
+    }
+
+    /** Sets the list of tasks */
+    void set(List<Task> tasks) {
+        mTasks.clear();
+        mTasks.addAll(tasks);
+        updateFilteredTasks();
+    }
+
+    /** Removes a task from the base list only if it is in the filtered list */
+    boolean remove(Task t) {
+        if (mFilteredTasks.contains(t)) {
+            boolean removed = mTasks.remove(t);
+            updateFilteredTasks();
+            return removed;
+        }
+        return false;
+    }
+
+    /** Returns the index of this task in the list of filtered tasks */
+    int indexOf(Task t) {
+        if (t != null && mFilteredTaskIndices.containsKey(t.key)) {
+            return mFilteredTaskIndices.get(t.key);
+        }
+        return -1;
+    }
+
+    /** Returns the size of the list of filtered tasks */
+    int size() {
+        return mFilteredTasks.size();
+    }
+
+    /** Returns whether the filtered list contains this task */
+    boolean contains(Task t) {
+        return mFilteredTaskIndices.containsKey(t.key);
+    }
+
+    /** Updates the list of filtered tasks whenever the base task list changes */
+    private void updateFilteredTasks() {
+        mFilteredTasks.clear();
+        if (mFilter != null) {
+            // Create a sparse array from task id to Task
+            SparseArray<Task> taskIdMap = new SparseArray<>();
+            int taskCount = mTasks.size();
+            for (int i = 0; i < taskCount; i++) {
+                Task t = mTasks.get(i);
+                taskIdMap.put(t.key.id, t);
+            }
+
+            for (int i = 0; i < taskCount; i++) {
+                Task t = mTasks.get(i);
+                if (mFilter.acceptTask(taskIdMap, t, i)) {
+                    mFilteredTasks.add(t);
+                }
+            }
+        } else {
+            mFilteredTasks.addAll(mTasks);
+        }
+        updateFilteredTaskIndices();
+    }
+
+    /** Updates the mapping of tasks to indices. */
+    private void updateFilteredTaskIndices() {
+        int taskCount = mFilteredTasks.size();
+        mFilteredTaskIndices.clear();
+        for (int i = 0; i < taskCount; i++) {
+            Task t = mFilteredTasks.get(i);
+            mFilteredTaskIndices.put(t.key, i);
+        }
+    }
+
+    /** Returns the list of filtered tasks */
+    ArrayList<Task> getTasks() {
+        return mFilteredTasks;
+    }
+}
diff --git a/com/android/systemui/recents/model/HighResThumbnailLoader.java b/com/android/systemui/shared/recents/model/HighResThumbnailLoader.java
similarity index 92%
rename from com/android/systemui/recents/model/HighResThumbnailLoader.java
rename to com/android/systemui/shared/recents/model/HighResThumbnailLoader.java
index 6414ea1..24ba998 100644
--- a/com/android/systemui/recents/model/HighResThumbnailLoader.java
+++ b/com/android/systemui/shared/recents/model/HighResThumbnailLoader.java
@@ -14,7 +14,7 @@
  * limitations under the License
  */
 
-package com.android.systemui.recents.model;
+package com.android.systemui.shared.recents.model;
 
 import static android.os.Process.setThreadPriority;
 
@@ -25,10 +25,8 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.recents.Recents;
-import com.android.systemui.recents.RecentsConfiguration;
-import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.model.Task.TaskCallbacks;
+import com.android.systemui.shared.recents.model.Task.TaskCallbacks;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
 
 import java.util.ArrayDeque;
 import java.util.ArrayList;
@@ -38,6 +36,8 @@
  */
 public class HighResThumbnailLoader implements TaskCallbacks {
 
+    private final ActivityManagerWrapper mActivityManager;
+
     @GuardedBy("mLoadQueue")
     private final ArrayDeque<Task> mLoadQueue = new ArrayDeque<>();
     @GuardedBy("mLoadQueue")
@@ -46,20 +46,21 @@
     private boolean mLoaderIdling;
 
     private final ArrayList<Task> mVisibleTasks = new ArrayList<>();
+
     private final Thread mLoadThread;
     private final Handler mMainThreadHandler;
-    private final SystemServicesProxy mSystemServicesProxy;
     private final boolean mIsLowRamDevice;
     private boolean mLoading;
     private boolean mVisible;
     private boolean mFlingingFast;
     private boolean mTaskLoadQueueIdle;
 
-    public HighResThumbnailLoader(SystemServicesProxy ssp, Looper looper, boolean isLowRamDevice) {
+    public HighResThumbnailLoader(ActivityManagerWrapper activityManager, Looper looper,
+            boolean isLowRamDevice) {
+        mActivityManager = activityManager;
         mMainThreadHandler = new Handler(looper);
         mLoadThread = new Thread(mLoader, "Recents-HighResThumbnailLoader");
         mLoadThread.start();
-        mSystemServicesProxy = ssp;
         mIsLowRamDevice = isLowRamDevice;
     }
 
@@ -220,7 +221,7 @@
         }
 
         private void loadTask(Task t) {
-            ThumbnailData thumbnail = mSystemServicesProxy.getTaskThumbnail(t.key.id,
+            ThumbnailData thumbnail = mActivityManager.getTaskThumbnail(t.key.id,
                     false /* reducedResolution */);
             mMainThreadHandler.post(() -> {
                 synchronized (mLoadQueue) {
diff --git a/com/android/systemui/shared/recents/model/RecentsTaskLoadPlan.java b/com/android/systemui/shared/recents/model/RecentsTaskLoadPlan.java
new file mode 100644
index 0000000..806a073
--- /dev/null
+++ b/com/android/systemui/shared/recents/model/RecentsTaskLoadPlan.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2014 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.shared.recents.model;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+
+import android.app.ActivityManager;
+import android.app.KeyguardManager;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.util.SparseBooleanArray;
+
+import com.android.systemui.shared.recents.model.Task.TaskKey;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+
+/**
+ * This class stores the loading state as it goes through multiple stages of loading:
+ *   1) preloadRawTasks() will load the raw set of recents tasks from the system
+ *   2) preloadPlan() will construct a new task stack with all metadata and only icons and
+ *      thumbnails that are currently in the cache
+ *   3) executePlan() will actually load and fill in the icons and thumbnails according to the load
+ *      options specified, such that we can transition into the Recents activity seamlessly
+ */
+public class RecentsTaskLoadPlan {
+
+    /** The set of conditions to load tasks. */
+    public static class Options {
+        public int runningTaskId = -1;
+        public boolean loadIcons = true;
+        public boolean loadThumbnails = false;
+        public boolean onlyLoadForCache = false;
+        public boolean onlyLoadPausedActivities = false;
+        public int numVisibleTasks = 0;
+        public int numVisibleTaskThumbnails = 0;
+    }
+
+    private final Context mContext;
+    private final KeyguardManager mKeyguardManager;
+
+    private List<ActivityManager.RecentTaskInfo> mRawTasks;
+    private TaskStack mStack;
+
+    private final SparseBooleanArray mTmpLockedUsers = new SparseBooleanArray();
+
+    public RecentsTaskLoadPlan(Context context) {
+        mContext = context;
+        mKeyguardManager = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);
+    }
+
+    /**
+     * An optimization to preload the raw list of tasks. The raw tasks are saved in least-recent
+     * to most-recent order.
+     *
+     * Note: Do not lock, callers should synchronize on the loader before making this call.
+     */
+    void preloadRawTasks() {
+        int currentUserId = ActivityManagerWrapper.getInstance().getCurrentUserId();
+        mRawTasks = ActivityManagerWrapper.getInstance().getRecentTasks(
+                ActivityManager.getMaxRecentTasksStatic(), currentUserId);
+
+        // Since the raw tasks are given in most-recent to least-recent order, we need to reverse it
+        Collections.reverse(mRawTasks);
+    }
+
+    /**
+     * Preloads the list of recent tasks from the system. After this call, the TaskStack will
+     * have a list of all the recent tasks with their metadata, not including icons or
+     * thumbnails which were not cached and have to be loaded.
+     *
+     * The tasks will be ordered by:
+     * - least-recent to most-recent stack tasks
+     *
+     * Note: Do not lock, since this can be calling back to the loader, which separately also drives
+     * this call (callers should synchronize on the loader before making this call).
+     */
+    void preloadPlan(RecentsTaskLoader loader, int runningTaskId) {
+        Resources res = mContext.getResources();
+        ArrayList<Task> allTasks = new ArrayList<>();
+        if (mRawTasks == null) {
+            preloadRawTasks();
+        }
+
+        int taskCount = mRawTasks.size();
+        for (int i = 0; i < taskCount; i++) {
+            ActivityManager.RecentTaskInfo t = mRawTasks.get(i);
+
+            // Compose the task key
+            final int windowingMode = t.configuration.windowConfiguration.getWindowingMode();
+            TaskKey taskKey = new TaskKey(t.persistentId, windowingMode, t.baseIntent,
+                    t.userId, t.lastActiveTime);
+
+            boolean isFreeformTask = windowingMode == WINDOWING_MODE_FREEFORM;
+            boolean isStackTask = !isFreeformTask;
+            boolean isLaunchTarget = taskKey.id == runningTaskId;
+
+            ActivityInfo info = loader.getAndUpdateActivityInfo(taskKey);
+            if (info == null) {
+                continue;
+            }
+
+            // Load the title, icon, and color
+            String title = loader.getAndUpdateActivityTitle(taskKey, t.taskDescription);
+            String titleDescription = loader.getAndUpdateContentDescription(taskKey,
+                    t.taskDescription);
+            Drawable icon = isStackTask
+                    ? loader.getAndUpdateActivityIcon(taskKey, t.taskDescription, res, false)
+                    : null;
+            ThumbnailData thumbnail = loader.getAndUpdateThumbnail(taskKey,
+                    false /* loadIfNotCached */, false /* storeInCache */);
+            int activityColor = loader.getActivityPrimaryColor(t.taskDescription);
+            int backgroundColor = loader.getActivityBackgroundColor(t.taskDescription);
+            boolean isSystemApp = (info != null) &&
+                    ((info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0);
+
+            // TODO: Refactor to not do this every preload
+            if (mTmpLockedUsers.indexOfKey(t.userId) < 0) {
+                mTmpLockedUsers.put(t.userId, mKeyguardManager.isDeviceLocked(t.userId));
+            }
+            boolean isLocked = mTmpLockedUsers.get(t.userId);
+
+            // Add the task to the stack
+            Task task = new Task(taskKey, icon,
+                    thumbnail, title, titleDescription, activityColor, backgroundColor,
+                    isLaunchTarget, isStackTask, isSystemApp, t.supportsSplitScreenMultiWindow,
+                    t.taskDescription, t.resizeMode, t.topActivity, isLocked);
+
+            allTasks.add(task);
+        }
+
+        // Initialize the stacks
+        mStack = new TaskStack();
+        mStack.setTasks(allTasks, false /* notifyStackChanges */);
+    }
+
+    /**
+     * Called to apply the actual loading based on the specified conditions.
+     *
+     * Note: Do not lock, since this can be calling back to the loader, which separately also drives
+     * this call (callers should synchronize on the loader before making this call).
+     */
+    void executePlan(Options opts, RecentsTaskLoader loader) {
+        Resources res = mContext.getResources();
+
+        // Iterate through each of the tasks and load them according to the load conditions.
+        ArrayList<Task> tasks = mStack.getStackTasks();
+        int taskCount = tasks.size();
+        for (int i = 0; i < taskCount; i++) {
+            Task task = tasks.get(i);
+            TaskKey taskKey = task.key;
+
+            boolean isRunningTask = (task.key.id == opts.runningTaskId);
+            boolean isVisibleTask = i >= (taskCount - opts.numVisibleTasks);
+            boolean isVisibleThumbnail = i >= (taskCount - opts.numVisibleTaskThumbnails);
+
+            // If requested, skip the running task
+            if (opts.onlyLoadPausedActivities && isRunningTask) {
+                continue;
+            }
+
+            if (opts.loadIcons && (isRunningTask || isVisibleTask)) {
+                if (task.icon == null) {
+                    task.icon = loader.getAndUpdateActivityIcon(taskKey, task.taskDescription, res,
+                            true);
+                }
+            }
+            if (opts.loadThumbnails && isVisibleThumbnail) {
+                task.thumbnail = loader.getAndUpdateThumbnail(taskKey,
+                        true /* loadIfNotCached */, true /* storeInCache */);
+            }
+        }
+    }
+
+    /**
+     * Returns the TaskStack from the preloaded list of recent tasks.
+     */
+    public TaskStack getTaskStack() {
+        return mStack;
+    }
+
+    /** Returns whether there are any tasks in any stacks. */
+    public boolean hasTasks() {
+        if (mStack != null) {
+            return mStack.getTaskCount() > 0;
+        }
+        return false;
+    }
+}
diff --git a/com/android/systemui/shared/recents/model/RecentsTaskLoader.java b/com/android/systemui/shared/recents/model/RecentsTaskLoader.java
new file mode 100644
index 0000000..de4c72c
--- /dev/null
+++ b/com/android/systemui/shared/recents/model/RecentsTaskLoader.java
@@ -0,0 +1,453 @@
+/*
+ * Copyright (C) 2014 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.shared.recents.model;
+
+import android.app.ActivityManager;
+import android.content.ComponentCallbacks2;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Looper;
+import android.os.Trace;
+import android.util.Log;
+import android.util.LruCache;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan.Options;
+import com.android.systemui.shared.recents.model.Task.TaskKey;
+import com.android.systemui.shared.recents.model.TaskKeyLruCache.EvictionCallback;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.PackageManagerWrapper;
+
+import java.io.PrintWriter;
+import java.util.Map;
+
+
+/**
+ * Recents task loader
+ */
+public class RecentsTaskLoader {
+    private static final String TAG = "RecentsTaskLoader";
+    private static final boolean DEBUG = false;
+
+    /** Levels of svelte in increasing severity/austerity. */
+    // No svelting.
+    public static final int SVELTE_NONE = 0;
+    // Limit thumbnail cache to number of visible thumbnails when Recents was loaded, disable
+    // caching thumbnails as you scroll.
+    public static final int SVELTE_LIMIT_CACHE = 1;
+    // Disable the thumbnail cache, load thumbnails asynchronously when the activity loads and
+    // evict all thumbnails when hidden.
+    public static final int SVELTE_DISABLE_CACHE = 2;
+    // Disable all thumbnail loading.
+    public static final int SVELTE_DISABLE_LOADING = 3;
+
+    private final Context mContext;
+
+    // This activity info LruCache is useful because it can be expensive to retrieve ActivityInfos
+    // for many tasks, which we use to get the activity labels and icons.  Unlike the other caches
+    // below, this is per-package so we can't invalidate the items in the cache based on the last
+    // active time.  Instead, we rely on the PackageMonitor to keep us informed whenever a
+    // package in the cache has been updated, so that we may remove it.
+    private final LruCache<ComponentName, ActivityInfo> mActivityInfoCache;
+    private final TaskKeyLruCache<Drawable> mIconCache;
+    private final TaskKeyLruCache<String> mActivityLabelCache;
+    private final TaskKeyLruCache<String> mContentDescriptionCache;
+    private final TaskResourceLoadQueue mLoadQueue;
+    private final BackgroundTaskLoader mLoader;
+    private final HighResThumbnailLoader mHighResThumbnailLoader;
+    @GuardedBy("this")
+    private final TaskKeyStrongCache<ThumbnailData> mThumbnailCache = new TaskKeyStrongCache<>();
+    @GuardedBy("this")
+    private final TaskKeyStrongCache<ThumbnailData> mTempCache = new TaskKeyStrongCache<>();
+    private final int mMaxThumbnailCacheSize;
+    private final int mMaxIconCacheSize;
+    private int mNumVisibleTasksLoaded;
+    private int mSvelteLevel;
+
+    private int mDefaultTaskBarBackgroundColor;
+    private int mDefaultTaskViewBackgroundColor;
+    private final BitmapDrawable mDefaultIcon;
+
+    private EvictionCallback mClearActivityInfoOnEviction = new EvictionCallback() {
+        @Override
+        public void onEntryEvicted(TaskKey key) {
+            if (key != null) {
+                mActivityInfoCache.remove(key.getComponent());
+            }
+        }
+    };
+
+    public RecentsTaskLoader(Context context, int maxThumbnailCacheSize, int maxIconCacheSize,
+            int svelteLevel) {
+        mContext = context;
+        mMaxThumbnailCacheSize = maxThumbnailCacheSize;
+        mMaxIconCacheSize = maxIconCacheSize;
+        mSvelteLevel = svelteLevel;
+
+        // Create the default assets
+        Bitmap icon = Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8);
+        icon.eraseColor(0);
+        mDefaultIcon = new BitmapDrawable(context.getResources(), icon);
+
+        // Initialize the proxy, cache and loaders
+        int numRecentTasks = ActivityManager.getMaxRecentTasksStatic();
+        mHighResThumbnailLoader = new HighResThumbnailLoader(ActivityManagerWrapper.getInstance(),
+                Looper.getMainLooper(), ActivityManager.isLowRamDeviceStatic());
+        mLoadQueue = new TaskResourceLoadQueue();
+        mIconCache = new TaskKeyLruCache<>(mMaxIconCacheSize, mClearActivityInfoOnEviction);
+        mActivityLabelCache = new TaskKeyLruCache<>(numRecentTasks, mClearActivityInfoOnEviction);
+        mContentDescriptionCache = new TaskKeyLruCache<>(numRecentTasks,
+                mClearActivityInfoOnEviction);
+        mActivityInfoCache = new LruCache<>(numRecentTasks);
+        mLoader = new BackgroundTaskLoader(mLoadQueue, mIconCache, mDefaultIcon,
+                mHighResThumbnailLoader::setTaskLoadQueueIdle);
+    }
+
+    /**
+     * Sets the default task bar/view colors if none are provided by the app.
+     */
+    public void setDefaultColors(int defaultTaskBarBackgroundColor,
+            int defaultTaskViewBackgroundColor) {
+        mDefaultTaskBarBackgroundColor = defaultTaskBarBackgroundColor;
+        mDefaultTaskViewBackgroundColor = defaultTaskViewBackgroundColor;
+    }
+
+    /** Returns the size of the app icon cache. */
+    public int getIconCacheSize() {
+        return mMaxIconCacheSize;
+    }
+
+    /** Returns the size of the thumbnail cache. */
+    public int getThumbnailCacheSize() {
+        return mMaxThumbnailCacheSize;
+    }
+
+    public HighResThumbnailLoader getHighResThumbnailLoader() {
+        return mHighResThumbnailLoader;
+    }
+
+    /** Preloads recents tasks using the specified plan to store the output. */
+    public synchronized void preloadTasks(RecentsTaskLoadPlan plan, int runningTaskId) {
+        try {
+            Trace.beginSection("preloadPlan");
+            plan.preloadPlan(this, runningTaskId);
+        } finally {
+            Trace.endSection();
+        }
+    }
+
+    /** Begins loading the heavy task data according to the specified options. */
+    public synchronized void loadTasks(RecentsTaskLoadPlan plan, Options opts) {
+        if (opts == null) {
+            throw new RuntimeException("Requires load options");
+        }
+        if (opts.onlyLoadForCache && opts.loadThumbnails) {
+            // If we are loading for the cache, we'd like to have the real cache only include the
+            // visible thumbnails. However, we also don't want to reload already cached thumbnails.
+            // Thus, we copy over the current entries into a second cache, and clear the real cache,
+            // such that the real cache only contains visible thumbnails.
+            mTempCache.copyEntries(mThumbnailCache);
+            mThumbnailCache.evictAll();
+        }
+        plan.executePlan(opts, this);
+        mTempCache.evictAll();
+        if (!opts.onlyLoadForCache) {
+            mNumVisibleTasksLoaded = opts.numVisibleTasks;
+        }
+    }
+
+    /**
+     * Acquires the task resource data directly from the cache, loading if necessary.
+     */
+    public void loadTaskData(Task t) {
+        Drawable icon = mIconCache.getAndInvalidateIfModified(t.key);
+        icon = icon != null ? icon : mDefaultIcon;
+        mLoadQueue.addTask(t);
+        t.notifyTaskDataLoaded(t.thumbnail, icon);
+    }
+
+    /** Releases the task resource data back into the pool. */
+    public void unloadTaskData(Task t) {
+        mLoadQueue.removeTask(t);
+        t.notifyTaskDataUnloaded(mDefaultIcon);
+    }
+
+    /** Completely removes the resource data from the pool. */
+    public void deleteTaskData(Task t, boolean notifyTaskDataUnloaded) {
+        mLoadQueue.removeTask(t);
+        mIconCache.remove(t.key);
+        mActivityLabelCache.remove(t.key);
+        mContentDescriptionCache.remove(t.key);
+        if (notifyTaskDataUnloaded) {
+            t.notifyTaskDataUnloaded(mDefaultIcon);
+        }
+    }
+
+    /**
+     * Handles signals from the system, trimming memory when requested to prevent us from running
+     * out of memory.
+     */
+    public synchronized void onTrimMemory(int level) {
+        switch (level) {
+            case ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN:
+                // Stop the loader immediately when the UI is no longer visible
+                stopLoader();
+                mIconCache.trimToSize(Math.max(mNumVisibleTasksLoaded,
+                        mMaxIconCacheSize / 2));
+                break;
+            case ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE:
+            case ComponentCallbacks2.TRIM_MEMORY_BACKGROUND:
+                // We are leaving recents, so trim the data a bit
+                mIconCache.trimToSize(Math.max(1, mMaxIconCacheSize / 2));
+                mActivityInfoCache.trimToSize(Math.max(1,
+                        ActivityManager.getMaxRecentTasksStatic() / 2));
+                break;
+            case ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW:
+            case ComponentCallbacks2.TRIM_MEMORY_MODERATE:
+                // We are going to be low on memory
+                mIconCache.trimToSize(Math.max(1, mMaxIconCacheSize / 4));
+                mActivityInfoCache.trimToSize(Math.max(1,
+                        ActivityManager.getMaxRecentTasksStatic() / 4));
+                break;
+            case ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL:
+            case ComponentCallbacks2.TRIM_MEMORY_COMPLETE:
+                // We are low on memory, so release everything
+                mIconCache.evictAll();
+                mActivityInfoCache.evictAll();
+                // The cache is small, only clear the label cache when we are critical
+                mActivityLabelCache.evictAll();
+                mContentDescriptionCache.evictAll();
+                mThumbnailCache.evictAll();
+                break;
+            default:
+                break;
+        }
+    }
+
+    public void onPackageChanged(String packageName) {
+        // Remove all the cached activity infos for this package.  The other caches do not need to
+        // be pruned at this time, as the TaskKey expiration checks will flush them next time their
+        // cached contents are requested
+        Map<ComponentName, ActivityInfo> activityInfoCache = mActivityInfoCache.snapshot();
+        for (ComponentName cn : activityInfoCache.keySet()) {
+            if (cn.getPackageName().equals(packageName)) {
+                if (DEBUG) {
+                    Log.d(TAG, "Removing activity info from cache: " + cn);
+                }
+                mActivityInfoCache.remove(cn);
+            }
+        }
+    }
+
+    /**
+     * Returns the cached task label if the task key is not expired, updating the cache if it is.
+     */
+    String getAndUpdateActivityTitle(TaskKey taskKey, ActivityManager.TaskDescription td) {
+        // Return the task description label if it exists
+        if (td != null && td.getLabel() != null) {
+            return td.getLabel();
+        }
+        // Return the cached activity label if it exists
+        String label = mActivityLabelCache.getAndInvalidateIfModified(taskKey);
+        if (label != null) {
+            return label;
+        }
+        // All short paths failed, load the label from the activity info and cache it
+        ActivityInfo activityInfo = getAndUpdateActivityInfo(taskKey);
+        if (activityInfo != null) {
+            label = ActivityManagerWrapper.getInstance().getBadgedActivityLabel(activityInfo,
+                    taskKey.userId);
+            mActivityLabelCache.put(taskKey, label);
+            return label;
+        }
+        // If the activity info does not exist or fails to load, return an empty label for now,
+        // but do not cache it
+        return "";
+    }
+
+    /**
+     * Returns the cached task content description if the task key is not expired, updating the
+     * cache if it is.
+     */
+    String getAndUpdateContentDescription(TaskKey taskKey, ActivityManager.TaskDescription td) {
+        // Return the cached content description if it exists
+        String label = mContentDescriptionCache.getAndInvalidateIfModified(taskKey);
+        if (label != null) {
+            return label;
+        }
+
+        // All short paths failed, load the label from the activity info and cache it
+        ActivityInfo activityInfo = getAndUpdateActivityInfo(taskKey);
+        if (activityInfo != null) {
+            label = ActivityManagerWrapper.getInstance().getBadgedContentDescription(
+                    activityInfo, taskKey.userId, td);
+            if (td == null) {
+                // Only add to the cache if the task description is null, otherwise, it is possible
+                // for the task description to change between calls without the last active time
+                // changing (ie. between preloading and Overview starting) which would lead to stale
+                // content descriptions
+                // TODO: Investigate improving this
+                mContentDescriptionCache.put(taskKey, label);
+            }
+            return label;
+        }
+        // If the content description does not exist, return an empty label for now, but do not
+        // cache it
+        return "";
+    }
+
+    /**
+     * Returns the cached task icon if the task key is not expired, updating the cache if it is.
+     */
+    Drawable getAndUpdateActivityIcon(TaskKey taskKey, ActivityManager.TaskDescription td,
+            Resources res, boolean loadIfNotCached) {
+        // Return the cached activity icon if it exists
+        Drawable icon = mIconCache.getAndInvalidateIfModified(taskKey);
+        if (icon != null) {
+            return icon;
+        }
+
+        if (loadIfNotCached) {
+            // Return and cache the task description icon if it exists
+            icon = ActivityManagerWrapper.getInstance().getBadgedTaskDescriptionIcon(mContext, td,
+                    taskKey.userId, res);
+            if (icon != null) {
+                mIconCache.put(taskKey, icon);
+                return icon;
+            }
+
+            // Load the icon from the activity info and cache it
+            ActivityInfo activityInfo = getAndUpdateActivityInfo(taskKey);
+            if (activityInfo != null) {
+                icon = ActivityManagerWrapper.getInstance().getBadgedActivityIcon(activityInfo,
+                        taskKey.userId);
+                if (icon != null) {
+                    mIconCache.put(taskKey, icon);
+                    return icon;
+                }
+            }
+        }
+        // We couldn't load any icon
+        return null;
+    }
+
+    /**
+     * Returns the cached thumbnail if the task key is not expired, updating the cache if it is.
+     */
+    synchronized ThumbnailData getAndUpdateThumbnail(TaskKey taskKey, boolean loadIfNotCached,
+            boolean storeInCache) {
+        ThumbnailData cached = mThumbnailCache.getAndInvalidateIfModified(taskKey);
+        if (cached != null) {
+            return cached;
+        }
+
+        cached = mTempCache.getAndInvalidateIfModified(taskKey);
+        if (cached != null) {
+            mThumbnailCache.put(taskKey, cached);
+            return cached;
+        }
+
+        if (loadIfNotCached) {
+            if (mSvelteLevel < SVELTE_DISABLE_LOADING) {
+                // Load the thumbnail from the system
+                ThumbnailData thumbnailData = ActivityManagerWrapper.getInstance().getTaskThumbnail(
+                        taskKey.id, true /* reducedResolution */);
+                if (thumbnailData.thumbnail != null) {
+                    if (storeInCache) {
+                        mThumbnailCache.put(taskKey, thumbnailData);
+                    }
+                    return thumbnailData;
+                }
+            }
+        }
+
+        // We couldn't load any thumbnail
+        return null;
+    }
+
+    /**
+     * Returns the task's primary color if possible, defaulting to the default color if there is
+     * no specified primary color.
+     */
+    int getActivityPrimaryColor(ActivityManager.TaskDescription td) {
+        if (td != null && td.getPrimaryColor() != 0) {
+            return td.getPrimaryColor();
+        }
+        return mDefaultTaskBarBackgroundColor;
+    }
+
+    /**
+     * Returns the task's background color if possible.
+     */
+    int getActivityBackgroundColor(ActivityManager.TaskDescription td) {
+        if (td != null && td.getBackgroundColor() != 0) {
+            return td.getBackgroundColor();
+        }
+        return mDefaultTaskViewBackgroundColor;
+    }
+
+    /**
+     * Returns the activity info for the given task key, retrieving one from the system if the
+     * task key is expired.
+     */
+    ActivityInfo getAndUpdateActivityInfo(TaskKey taskKey) {
+        ComponentName cn = taskKey.getComponent();
+        ActivityInfo activityInfo = mActivityInfoCache.get(cn);
+        if (activityInfo == null) {
+            activityInfo = PackageManagerWrapper.getInstance().getActivityInfo(cn, taskKey.userId);
+            if (cn == null || activityInfo == null) {
+                Log.e(TAG, "Unexpected null component name or activity info: " + cn + ", " +
+                        activityInfo);
+                return null;
+            }
+            mActivityInfoCache.put(cn, activityInfo);
+        }
+        return activityInfo;
+    }
+
+    /**
+     * Starts loading tasks.
+     */
+    public void startLoader(Context ctx) {
+        mLoader.start(ctx);
+    }
+
+    /**
+     * Stops the task loader and clears all queued, pending task loads.
+     */
+    private void stopLoader() {
+        mLoader.stop();
+        mLoadQueue.clearTasks();
+    }
+
+    public synchronized void dump(String prefix, PrintWriter writer) {
+        String innerPrefix = prefix + "  ";
+
+        writer.print(prefix); writer.println(TAG);
+        writer.print(prefix); writer.println("Icon Cache");
+        mIconCache.dump(innerPrefix, writer);
+        writer.print(prefix); writer.println("Thumbnail Cache");
+        mThumbnailCache.dump(innerPrefix, writer);
+        writer.print(prefix); writer.println("Temp Thumbnail Cache");
+        mTempCache.dump(innerPrefix, writer);
+    }
+}
diff --git a/com/android/systemui/recents/model/Task.java b/com/android/systemui/shared/recents/model/Task.java
similarity index 70%
rename from com/android/systemui/recents/model/Task.java
rename to com/android/systemui/shared/recents/model/Task.java
index abdb5cb..6bddbe0 100644
--- a/com/android/systemui/recents/model/Task.java
+++ b/com/android/systemui/shared/recents/model/Task.java
@@ -14,22 +14,17 @@
  * limitations under the License.
  */
 
-package com.android.systemui.recents.model;
+package com.android.systemui.shared.recents.model;
 
-import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
-
-import android.app.ActivityManager;
+import android.app.ActivityManager.TaskDescription;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.graphics.Color;
-import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.view.ViewDebug;
 
-import com.android.systemui.recents.Recents;
-import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.Utilities;
+import com.android.systemui.shared.recents.utilities.Utilities;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -45,11 +40,11 @@
     /* Task callbacks */
     public interface TaskCallbacks {
         /* Notifies when a task has been bound */
-        public void onTaskDataLoaded(Task task, ThumbnailData thumbnailData);
+        void onTaskDataLoaded(Task task, ThumbnailData thumbnailData);
         /* Notifies when a task has been unbound */
-        public void onTaskDataUnloaded();
+        void onTaskDataUnloaded();
         /* Notifies when a task's windowing mode has changed. */
-        public void onTaskWindowingModeChanged();
+        void onTaskWindowingModeChanged();
     }
 
     /* The Task Key represents the unique primary key for the task */
@@ -63,19 +58,15 @@
         @ViewDebug.ExportedProperty(category="recents")
         public final int userId;
         @ViewDebug.ExportedProperty(category="recents")
-        public long firstActiveTime;
-        @ViewDebug.ExportedProperty(category="recents")
         public long lastActiveTime;
 
         private int mHashCode;
 
-        public TaskKey(int id, int windowingMode, Intent intent, int userId, long firstActiveTime,
-                long lastActiveTime) {
+        public TaskKey(int id, int windowingMode, Intent intent, int userId, long lastActiveTime) {
             this.id = id;
             this.windowingMode = windowingMode;
             this.baseIntent = intent;
             this.userId = userId;
-            this.firstActiveTime = firstActiveTime;
             this.lastActiveTime = lastActiveTime;
             updateHashCode();
         }
@@ -125,20 +116,6 @@
     public int temporarySortIndexInStack;
 
     /**
-     * The group will be computed separately from the initialization of the task
-     */
-    @ViewDebug.ExportedProperty(deepExport=true, prefix="group_")
-    public TaskGrouping group;
-    /**
-     * The affiliationTaskId is the task id of the parent task or itself if it is not affiliated
-     * with any task.
-     */
-    @ViewDebug.ExportedProperty(category="recents")
-    public int affiliationTaskId;
-    @ViewDebug.ExportedProperty(category="recents")
-    public int affiliationColor;
-
-    /**
      * The icon is the task description icon (if provided), which falls back to the activity icon,
      * which can then fall back to the application icon.
      */
@@ -149,10 +126,6 @@
     @ViewDebug.ExportedProperty(category="recents")
     public String titleDescription;
     @ViewDebug.ExportedProperty(category="recents")
-    public String dismissDescription;
-    @ViewDebug.ExportedProperty(category="recents")
-    public String appInfoDescription;
-    @ViewDebug.ExportedProperty(category="recents")
     public int colorPrimary;
     @ViewDebug.ExportedProperty(category="recents")
     public int colorBackground;
@@ -160,15 +133,9 @@
     public boolean useLightOnPrimaryColor;
 
     /**
-     * The bounds of the task, used only if it is a freeform task.
-     */
-    @ViewDebug.ExportedProperty(category="recents")
-    public Rect bounds;
-
-    /**
      * The task description for this task, only used to reload task icons.
      */
-    public ActivityManager.TaskDescription taskDescription;
+    public TaskDescription taskDescription;
 
     /**
      * The state isLaunchTarget will be set for the correct task upon launching Recents.
@@ -200,28 +167,20 @@
         // Do nothing
     }
 
-    public Task(TaskKey key, int affiliationTaskId, int affiliationColor, Drawable icon,
-            ThumbnailData thumbnail, String title, String titleDescription,
-            String dismissDescription, String appInfoDescription, int colorPrimary,
-            int colorBackground, boolean isLaunchTarget, boolean isStackTask, boolean isSystemApp,
-            boolean isDockable, Rect bounds, ActivityManager.TaskDescription taskDescription,
-            int resizeMode, ComponentName topActivity, boolean isLocked) {
-        boolean isInAffiliationGroup = (affiliationTaskId != key.id);
-        boolean hasAffiliationGroupColor = isInAffiliationGroup && (affiliationColor != 0);
+    public Task(TaskKey key, Drawable icon, ThumbnailData thumbnail, String title,
+            String titleDescription, int colorPrimary, int colorBackground, boolean isLaunchTarget,
+            boolean isStackTask, boolean isSystemApp, boolean isDockable,
+            TaskDescription taskDescription, int resizeMode, ComponentName topActivity,
+            boolean isLocked) {
         this.key = key;
-        this.affiliationTaskId = affiliationTaskId;
-        this.affiliationColor = affiliationColor;
         this.icon = icon;
         this.thumbnail = thumbnail;
         this.title = title;
         this.titleDescription = titleDescription;
-        this.dismissDescription = dismissDescription;
-        this.appInfoDescription = appInfoDescription;
-        this.colorPrimary = hasAffiliationGroupColor ? affiliationColor : colorPrimary;
+        this.colorPrimary = colorPrimary;
         this.colorBackground = colorBackground;
         this.useLightOnPrimaryColor = Utilities.computeContrastBetweenColors(this.colorPrimary,
                 Color.WHITE) > 3f;
-        this.bounds = bounds;
         this.taskDescription = taskDescription;
         this.isLaunchTarget = isLaunchTarget;
         this.isStackTask = isStackTask;
@@ -237,19 +196,13 @@
      */
     public void copyFrom(Task o) {
         this.key = o.key;
-        this.group = o.group;
-        this.affiliationTaskId = o.affiliationTaskId;
-        this.affiliationColor = o.affiliationColor;
         this.icon = o.icon;
         this.thumbnail = o.thumbnail;
         this.title = o.title;
         this.titleDescription = o.titleDescription;
-        this.dismissDescription = o.dismissDescription;
-        this.appInfoDescription = o.appInfoDescription;
         this.colorPrimary = o.colorPrimary;
         this.colorBackground = o.colorBackground;
         this.useLightOnPrimaryColor = o.useLightOnPrimaryColor;
-        this.bounds = o.bounds;
         this.taskDescription = o.taskDescription;
         this.isLaunchTarget = o.isLaunchTarget;
         this.isStackTask = o.isStackTask;
@@ -276,11 +229,6 @@
         mCallbacks.remove(cb);
     }
 
-    /** Set the grouping */
-    public void setGroup(TaskGrouping group) {
-        this.group = group;
-    }
-
     /** Updates the task's windowing mode. */
     public void setWindowingMode(int windowingMode) {
         key.setWindowingMode(windowingMode);
@@ -290,14 +238,6 @@
         }
     }
 
-    /**
-     * Returns whether this task is on the freeform task stack.
-     */
-    public boolean isFreeformTask() {
-        SystemServicesProxy ssp = Recents.getSystemServices();
-        return ssp.hasFreeformWorkspaceSupport() && key.windowingMode == WINDOWING_MODE_FREEFORM;
-    }
-
     /** Notifies the callback listeners that this task has been loaded */
     public void notifyTaskDataLoaded(ThumbnailData thumbnailData, Drawable applicationIcon) {
         this.icon = applicationIcon;
@@ -318,13 +258,6 @@
     }
 
     /**
-     * Returns whether this task is affiliated with another task.
-     */
-    public boolean isAffiliatedTask() {
-        return key.id != affiliationTaskId;
-    }
-
-    /**
      * Returns the top activity component.
      */
     public ComponentName getTopComponent() {
@@ -347,18 +280,12 @@
 
     public void dump(String prefix, PrintWriter writer) {
         writer.print(prefix); writer.print(key);
-        if (isAffiliatedTask()) {
-            writer.print(" "); writer.print("affTaskId=" + affiliationTaskId);
-        }
         if (!isDockable) {
             writer.print(" dockable=N");
         }
         if (isLaunchTarget) {
             writer.print(" launchTarget=Y");
         }
-        if (isFreeformTask()) {
-            writer.print(" freeform=Y");
-        }
         if (isLocked) {
             writer.print(" locked=Y");
         }
diff --git a/android/arch/paging/StringPagedList.java b/com/android/systemui/shared/recents/model/TaskFilter.java
similarity index 60%
copy from android/arch/paging/StringPagedList.java
copy to com/android/systemui/shared/recents/model/TaskFilter.java
index 5318d38..9a1ff54 100644
--- a/android/arch/paging/StringPagedList.java
+++ b/com/android/systemui/shared/recents/model/TaskFilter.java
@@ -11,15 +11,17 @@
  * 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.
+ * limitations under the License
  */
 
-package android.arch.paging;
+package com.android.systemui.shared.recents.model;
 
-import java.util.Arrays;
+import android.util.SparseArray;
 
-public class StringPagedList extends NullPaddedList<String> {
-    public StringPagedList(int leadingNulls, int trailingNulls, String... items) {
-        super(leadingNulls, Arrays.asList(items), trailingNulls);
-    }
+/**
+ * An interface for a task filter to query whether a particular task should show in a stack.
+ */
+interface TaskFilter {
+    /** Returns whether the filter accepts the specified task */
+    boolean acceptTask(SparseArray<Task> taskIdMap, Task t, int index);
 }
diff --git a/com/android/systemui/recents/model/TaskKeyCache.java b/com/android/systemui/shared/recents/model/TaskKeyCache.java
similarity index 88%
rename from com/android/systemui/recents/model/TaskKeyCache.java
rename to com/android/systemui/shared/recents/model/TaskKeyCache.java
index 247a654..4bf3500 100644
--- a/com/android/systemui/recents/model/TaskKeyCache.java
+++ b/com/android/systemui/shared/recents/model/TaskKeyCache.java
@@ -14,12 +14,12 @@
  * limitations under the License
  */
 
-package com.android.systemui.recents.model;
+package com.android.systemui.shared.recents.model;
 
 import android.util.Log;
 import android.util.SparseArray;
 
-import com.android.systemui.recents.model.Task.TaskKey;
+import com.android.systemui.shared.recents.model.Task.TaskKey;
 
 /**
  * Base class for both strong and LRU task key cache.
@@ -34,7 +34,7 @@
      * Gets a specific entry in the cache with the specified key, regardless of whether the cached
      * value is valid or not.
      */
-    final V get(Task.TaskKey key) {
+    final V get(TaskKey key) {
         return getCacheEntry(key.id);
     }
 
@@ -42,8 +42,8 @@
      * Returns the value only if the key is valid (has not been updated since the last time it was
      * in the cache)
      */
-    final V getAndInvalidateIfModified(Task.TaskKey key) {
-        Task.TaskKey lastKey = mKeys.get(key.id);
+    final V getAndInvalidateIfModified(TaskKey key) {
+        TaskKey lastKey = mKeys.get(key.id);
         if (lastKey != null) {
             if ((lastKey.windowingMode != key.windowingMode) ||
                     (lastKey.lastActiveTime != key.lastActiveTime)) {
@@ -59,7 +59,7 @@
     }
 
     /** Puts an entry in the cache for a specific key. */
-    final void put(Task.TaskKey key, V value) {
+    final void put(TaskKey key, V value) {
         if (key == null || value == null) {
             Log.e(TAG, "Unexpected null key or value: " + key + ", " + value);
             return;
@@ -70,7 +70,7 @@
 
 
     /** Removes a cache entry for a specific key. */
-    final void remove(Task.TaskKey key) {
+    final void remove(TaskKey key) {
         // Remove the key after the cache value because we need it to make the callback
         removeCacheEntry(key.id);
         mKeys.remove(key.id);
diff --git a/com/android/systemui/recents/model/TaskKeyLruCache.java b/com/android/systemui/shared/recents/model/TaskKeyLruCache.java
similarity index 91%
rename from com/android/systemui/recents/model/TaskKeyLruCache.java
rename to com/android/systemui/shared/recents/model/TaskKeyLruCache.java
index 778df6b..0ba2c3b 100644
--- a/com/android/systemui/recents/model/TaskKeyLruCache.java
+++ b/com/android/systemui/shared/recents/model/TaskKeyLruCache.java
@@ -14,14 +14,16 @@
  * limitations under the License.
  */
 
-package com.android.systemui.recents.model;
+package com.android.systemui.shared.recents.model;
 
 import android.util.LruCache;
 
+import com.android.systemui.shared.recents.model.Task.TaskKey;
+
 import java.io.PrintWriter;
 
 /**
- * A mapping of {@link Task.TaskKey} to value, with additional LRU functionality where the least
+ * A mapping of {@link TaskKey} to value, with additional LRU functionality where the least
  * recently referenced key/values will be evicted as more values than the given cache size are
  * inserted.
  *
@@ -31,7 +33,7 @@
 public class TaskKeyLruCache<V> extends TaskKeyCache<V> {
 
     public interface EvictionCallback {
-        public void onEntryEvicted(Task.TaskKey key);
+        void onEntryEvicted(TaskKey key);
     }
 
     private final LruCache<Integer, V> mCache;
diff --git a/com/android/systemui/recents/model/TaskKeyStrongCache.java b/com/android/systemui/shared/recents/model/TaskKeyStrongCache.java
similarity index 92%
rename from com/android/systemui/recents/model/TaskKeyStrongCache.java
rename to com/android/systemui/shared/recents/model/TaskKeyStrongCache.java
index c84df8a..4408ece 100644
--- a/com/android/systemui/recents/model/TaskKeyStrongCache.java
+++ b/com/android/systemui/shared/recents/model/TaskKeyStrongCache.java
@@ -14,13 +14,11 @@
  * limitations under the License
  */
 
-package com.android.systemui.recents.model;
+package com.android.systemui.shared.recents.model;
 
 import android.util.ArrayMap;
-import android.util.Log;
-import android.util.SparseArray;
 
-import com.android.systemui.recents.model.Task.TaskKey;
+import com.android.systemui.shared.recents.model.Task.TaskKey;
 
 import java.io.PrintWriter;
 
diff --git a/com/android/systemui/shared/recents/model/TaskResourceLoadQueue.java b/com/android/systemui/shared/recents/model/TaskResourceLoadQueue.java
new file mode 100644
index 0000000..fbb6ace
--- /dev/null
+++ b/com/android/systemui/shared/recents/model/TaskResourceLoadQueue.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2017 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.shared.recents.model;
+
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+/**
+ * A Task load queue
+ */
+class TaskResourceLoadQueue {
+
+    private final ConcurrentLinkedQueue<Task> mQueue = new ConcurrentLinkedQueue<>();
+
+    /** Adds a new task to the load queue */
+    void addTask(Task t) {
+        if (!mQueue.contains(t)) {
+            mQueue.add(t);
+        }
+        synchronized(this) {
+            notifyAll();
+        }
+    }
+
+    /**
+     * Retrieves the next task from the load queue, as well as whether we want that task to be
+     * force reloaded.
+     */
+    Task nextTask() {
+        return mQueue.poll();
+    }
+
+    /** Removes a task from the load queue */
+    void removeTask(Task t) {
+        mQueue.remove(t);
+    }
+
+    /** Clears all the tasks from the load queue */
+    void clearTasks() {
+        mQueue.clear();
+    }
+
+    /** Returns whether the load queue is empty */
+    boolean isEmpty() {
+        return mQueue.isEmpty();
+    }
+}
diff --git a/com/android/systemui/shared/recents/model/TaskStack.java b/com/android/systemui/shared/recents/model/TaskStack.java
new file mode 100644
index 0000000..693379d
--- /dev/null
+++ b/com/android/systemui/shared/recents/model/TaskStack.java
@@ -0,0 +1,403 @@
+/*
+ * Copyright (C) 2014 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.shared.recents.model;
+
+import android.content.ComponentName;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.SparseArray;
+
+import com.android.systemui.shared.recents.model.Task.TaskKey;
+import com.android.systemui.shared.recents.utilities.AnimationProps;
+import com.android.systemui.shared.system.PackageManagerWrapper;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+
+/**
+ * The task stack contains a list of multiple tasks.
+ */
+public class TaskStack {
+
+    private static final String TAG = "TaskStack";
+
+    /** Task stack callbacks */
+    public interface TaskStackCallbacks {
+        /**
+         * Notifies when a new task has been added to the stack.
+         */
+        void onStackTaskAdded(TaskStack stack, Task newTask);
+
+        /**
+         * Notifies when a task has been removed from the stack.
+         */
+        void onStackTaskRemoved(TaskStack stack, Task removedTask, Task newFrontMostTask,
+                AnimationProps animation, boolean fromDockGesture,
+                boolean dismissRecentsIfAllRemoved);
+
+        /**
+         * Notifies when all tasks have been removed from the stack.
+         */
+        void onStackTasksRemoved(TaskStack stack);
+
+        /**
+         * Notifies when tasks in the stack have been updated.
+         */
+        void onStackTasksUpdated(TaskStack stack);
+    }
+
+    private final ArrayList<Task> mRawTaskList = new ArrayList<>();
+    private final FilteredTaskList mStackTaskList = new FilteredTaskList();
+    private TaskStackCallbacks mCb;
+
+    public TaskStack() {
+        // Ensure that we only show stack tasks
+        mStackTaskList.setFilter((taskIdMap, t, index) -> t.isStackTask);
+    }
+
+    /** Sets the callbacks for this task stack. */
+    public void setCallbacks(TaskStackCallbacks cb) {
+        mCb = cb;
+    }
+
+    /**
+     * Removes a task from the stack, with an additional {@param animation} hint to the callbacks on
+     * how they should update themselves.
+     */
+    public void removeTask(Task t, AnimationProps animation, boolean fromDockGesture) {
+        removeTask(t, animation, fromDockGesture, true /* dismissRecentsIfAllRemoved */);
+    }
+
+    /**
+     * Removes a task from the stack, with an additional {@param animation} hint to the callbacks on
+     * how they should update themselves.
+     */
+    public void removeTask(Task t, AnimationProps animation, boolean fromDockGesture,
+            boolean dismissRecentsIfAllRemoved) {
+        if (mStackTaskList.contains(t)) {
+            mStackTaskList.remove(t);
+            Task newFrontMostTask = getStackFrontMostTask();
+            if (mCb != null) {
+                // Notify that a task has been removed
+                mCb.onStackTaskRemoved(this, t, newFrontMostTask, animation,
+                        fromDockGesture, dismissRecentsIfAllRemoved);
+            }
+        }
+        mRawTaskList.remove(t);
+    }
+
+    /**
+     * Removes all tasks from the stack.
+     */
+    public void removeAllTasks(boolean notifyStackChanges) {
+        ArrayList<Task> tasks = mStackTaskList.getTasks();
+        for (int i = tasks.size() - 1; i >= 0; i--) {
+            Task t = tasks.get(i);
+            mStackTaskList.remove(t);
+            mRawTaskList.remove(t);
+        }
+        if (mCb != null && notifyStackChanges) {
+            // Notify that all tasks have been removed
+            mCb.onStackTasksRemoved(this);
+        }
+    }
+
+
+    /**
+     * @see #setTasks(List, boolean)
+     */
+    public void setTasks(TaskStack stack, boolean notifyStackChanges) {
+        setTasks(stack.mRawTaskList, notifyStackChanges);
+    }
+
+    /**
+     * Sets a few tasks in one go, without calling any callbacks.
+     *
+     * @param tasks the new set of tasks to replace the current set.
+     * @param notifyStackChanges whether or not to callback on specific changes to the list of tasks.
+     */
+    public void setTasks(List<Task> tasks, boolean notifyStackChanges) {
+        // Compute a has set for each of the tasks
+        ArrayMap<TaskKey, Task> currentTasksMap = createTaskKeyMapFromList(mRawTaskList);
+        ArrayMap<TaskKey, Task> newTasksMap = createTaskKeyMapFromList(tasks);
+        ArrayList<Task> addedTasks = new ArrayList<>();
+        ArrayList<Task> removedTasks = new ArrayList<>();
+        ArrayList<Task> allTasks = new ArrayList<>();
+
+        // Disable notifications if there are no callbacks
+        if (mCb == null) {
+            notifyStackChanges = false;
+        }
+
+        // Remove any tasks that no longer exist
+        int taskCount = mRawTaskList.size();
+        for (int i = taskCount - 1; i >= 0; i--) {
+            Task task = mRawTaskList.get(i);
+            if (!newTasksMap.containsKey(task.key)) {
+                if (notifyStackChanges) {
+                    removedTasks.add(task);
+                }
+            }
+        }
+
+        // Add any new tasks
+        taskCount = tasks.size();
+        for (int i = 0; i < taskCount; i++) {
+            Task newTask = tasks.get(i);
+            Task currentTask = currentTasksMap.get(newTask.key);
+            if (currentTask == null && notifyStackChanges) {
+                addedTasks.add(newTask);
+            } else if (currentTask != null) {
+                // The current task has bound callbacks, so just copy the data from the new task
+                // state and add it back into the list
+                currentTask.copyFrom(newTask);
+                newTask = currentTask;
+            }
+            allTasks.add(newTask);
+        }
+
+        // Sort all the tasks to ensure they are ordered correctly
+        for (int i = allTasks.size() - 1; i >= 0; i--) {
+            allTasks.get(i).temporarySortIndexInStack = i;
+        }
+
+        mStackTaskList.set(allTasks);
+        mRawTaskList.clear();
+        mRawTaskList.addAll(allTasks);
+
+        // Only callback for the removed tasks after the stack has updated
+        int removedTaskCount = removedTasks.size();
+        Task newFrontMostTask = getStackFrontMostTask();
+        for (int i = 0; i < removedTaskCount; i++) {
+            mCb.onStackTaskRemoved(this, removedTasks.get(i), newFrontMostTask,
+                    AnimationProps.IMMEDIATE, false /* fromDockGesture */,
+                    true /* dismissRecentsIfAllRemoved */);
+        }
+
+        // Only callback for the newly added tasks after this stack has been updated
+        int addedTaskCount = addedTasks.size();
+        for (int i = 0; i < addedTaskCount; i++) {
+            mCb.onStackTaskAdded(this, addedTasks.get(i));
+        }
+
+        // Notify that the task stack has been updated
+        if (notifyStackChanges) {
+            mCb.onStackTasksUpdated(this);
+        }
+    }
+
+    /**
+     * Gets the front-most task in the stack.
+     */
+    public Task getStackFrontMostTask() {
+        ArrayList<Task> stackTasks = mStackTaskList.getTasks();
+        if (stackTasks.isEmpty()) {
+            return null;
+        }
+        return stackTasks.get(stackTasks.size() - 1);
+    }
+
+    /** Gets the task keys */
+    public ArrayList<TaskKey> getTaskKeys() {
+        ArrayList<TaskKey> taskKeys = new ArrayList<>();
+        ArrayList<Task> tasks = computeAllTasksList();
+        int taskCount = tasks.size();
+        for (int i = 0; i < taskCount; i++) {
+            Task task = tasks.get(i);
+            taskKeys.add(task.key);
+        }
+        return taskKeys;
+    }
+
+    /**
+     * Returns the set of "active" (non-historical) tasks in the stack that have been used recently.
+     */
+    public ArrayList<Task> getStackTasks() {
+        return mStackTaskList.getTasks();
+    }
+
+    /**
+     * Computes a set of all the active and historical tasks.
+     */
+    public ArrayList<Task> computeAllTasksList() {
+        ArrayList<Task> tasks = new ArrayList<>();
+        tasks.addAll(mStackTaskList.getTasks());
+        return tasks;
+    }
+
+    /**
+     * Returns the number of stacktasks.
+     */
+    public int getTaskCount() {
+        return mStackTaskList.size();
+    }
+
+    /**
+     * Returns the number of stack tasks.
+     */
+    public int getStackTaskCount() {
+        return mStackTaskList.size();
+    }
+
+    /**
+     * Returns the task in stack tasks which is the launch target.
+     */
+    public Task getLaunchTarget() {
+        ArrayList<Task> tasks = mStackTaskList.getTasks();
+        int taskCount = tasks.size();
+        for (int i = 0; i < taskCount; i++) {
+            Task task = tasks.get(i);
+            if (task.isLaunchTarget) {
+                return task;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns whether the next launch target should actually be the PiP task.
+     */
+    public boolean isNextLaunchTargetPip(long lastPipTime) {
+        Task launchTarget = getLaunchTarget();
+        Task nextLaunchTarget = getNextLaunchTargetRaw();
+        if (nextLaunchTarget != null && lastPipTime > 0) {
+            // If the PiP time is more recent than the next launch target, then launch the PiP task
+            return lastPipTime > nextLaunchTarget.key.lastActiveTime;
+        } else if (launchTarget != null && lastPipTime > 0 && getTaskCount() == 1) {
+            // Otherwise, if there is no next launch target, but there is a PiP, then launch
+            // the PiP task
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Returns the task in stack tasks which should be launched next if Recents are toggled
+     * again, or null if there is no task to be launched. Callers should check
+     * {@link #isNextLaunchTargetPip(long)} before fetching the next raw launch target from the
+     * stack.
+     */
+    public Task getNextLaunchTarget() {
+        Task nextLaunchTarget = getNextLaunchTargetRaw();
+        if (nextLaunchTarget != null) {
+            return nextLaunchTarget;
+        }
+        return getStackTasks().get(getTaskCount() - 1);
+    }
+
+    private Task getNextLaunchTargetRaw() {
+        int taskCount = getTaskCount();
+        if (taskCount == 0) {
+            return null;
+        }
+        int launchTaskIndex = indexOfStackTask(getLaunchTarget());
+        if (launchTaskIndex != -1 && launchTaskIndex > 0) {
+            return getStackTasks().get(launchTaskIndex - 1);
+        }
+        return null;
+    }
+
+    /** Returns the index of this task in this current task stack */
+    public int indexOfStackTask(Task t) {
+        return mStackTaskList.indexOf(t);
+    }
+
+    /** Finds the task with the specified task id. */
+    public Task findTaskWithId(int taskId) {
+        ArrayList<Task> tasks = computeAllTasksList();
+        int taskCount = tasks.size();
+        for (int i = 0; i < taskCount; i++) {
+            Task task = tasks.get(i);
+            if (task.key.id == taskId) {
+                return task;
+            }
+        }
+        return null;
+    }
+    
+    /**
+     * Computes the components of tasks in this stack that have been removed as a result of a change
+     * in the specified package.
+     */
+    public ArraySet<ComponentName> computeComponentsRemoved(String packageName, int userId) {
+        // Identify all the tasks that should be removed as a result of the package being removed.
+        // Using a set to ensure that we callback once per unique component.
+        ArraySet<ComponentName> existingComponents = new ArraySet<>();
+        ArraySet<ComponentName> removedComponents = new ArraySet<>();
+        ArrayList<TaskKey> taskKeys = getTaskKeys();
+        int taskKeyCount = taskKeys.size();
+        for (int i = 0; i < taskKeyCount; i++) {
+            TaskKey t = taskKeys.get(i);
+
+            // Skip if this doesn't apply to the current user
+            if (t.userId != userId) continue;
+
+            ComponentName cn = t.getComponent();
+            if (cn.getPackageName().equals(packageName)) {
+                if (existingComponents.contains(cn)) {
+                    // If we know that the component still exists in the package, then skip
+                    continue;
+                }
+                if (PackageManagerWrapper.getInstance().getActivityInfo(cn, userId) != null) {
+                    existingComponents.add(cn);
+                } else {
+                    removedComponents.add(cn);
+                }
+            }
+        }
+        return removedComponents;
+    }
+
+    @Override
+    public String toString() {
+        String str = "Stack Tasks (" + mStackTaskList.size() + "):\n";
+        ArrayList<Task> tasks = mStackTaskList.getTasks();
+        int taskCount = tasks.size();
+        for (int i = 0; i < taskCount; i++) {
+            str += "    " + tasks.get(i).toString() + "\n";
+        }
+        return str;
+    }
+
+    /**
+     * Given a list of tasks, returns a map of each task's key to the task.
+     */
+    private ArrayMap<TaskKey, Task> createTaskKeyMapFromList(List<Task> tasks) {
+        ArrayMap<TaskKey, Task> map = new ArrayMap<>(tasks.size());
+        int taskCount = tasks.size();
+        for (int i = 0; i < taskCount; i++) {
+            Task task = tasks.get(i);
+            map.put(task.key, task);
+        }
+        return map;
+    }
+
+    public void dump(String prefix, PrintWriter writer) {
+        String innerPrefix = prefix + "  ";
+
+        writer.print(prefix); writer.print(TAG);
+        writer.print(" numStackTasks="); writer.print(mStackTaskList.size());
+        writer.println();
+        ArrayList<Task> tasks = mStackTaskList.getTasks();
+        int taskCount = tasks.size();
+        for (int i = 0; i < taskCount; i++) {
+            tasks.get(i).dump(innerPrefix, writer);
+        }
+    }
+}
diff --git a/com/android/systemui/shared/recents/model/ThumbnailData.java b/com/android/systemui/shared/recents/model/ThumbnailData.java
new file mode 100644
index 0000000..dd1763b
--- /dev/null
+++ b/com/android/systemui/shared/recents/model/ThumbnailData.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shared.recents.model;
+
+import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
+
+import android.app.ActivityManager.TaskSnapshot;
+import android.graphics.Bitmap;
+import android.graphics.Rect;
+
+/**
+ * Data for a single thumbnail.
+ */
+public class ThumbnailData {
+
+    public final Bitmap thumbnail;
+    public int orientation;
+    public Rect insets;
+    public boolean reducedResolution;
+    public float scale;
+
+    public ThumbnailData() {
+        thumbnail = null;
+        orientation = ORIENTATION_UNDEFINED;
+        insets = new Rect();
+        reducedResolution = false;
+        scale = 1f;
+    }
+
+    public ThumbnailData(TaskSnapshot snapshot) {
+        thumbnail = Bitmap.createHardwareBitmap(snapshot.getSnapshot());
+        insets = new Rect(snapshot.getContentInsets());
+        orientation = snapshot.getOrientation();
+        reducedResolution = snapshot.isReducedResolution();
+        scale = snapshot.getScale();
+    }
+}
diff --git a/com/android/systemui/recents/views/AnimationProps.java b/com/android/systemui/shared/recents/utilities/AnimationProps.java
similarity index 95%
rename from com/android/systemui/recents/views/AnimationProps.java
rename to com/android/systemui/shared/recents/utilities/AnimationProps.java
index 716d1bc..2de7f74 100644
--- a/com/android/systemui/recents/views/AnimationProps.java
+++ b/com/android/systemui/shared/recents/utilities/AnimationProps.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.recents.views;
+package com.android.systemui.shared.recents.utilities;
 
 import android.animation.Animator;
 import android.animation.AnimatorSet;
@@ -24,8 +24,7 @@
 import android.util.SparseLongArray;
 import android.view.View;
 import android.view.animation.Interpolator;
-
-import com.android.systemui.Interpolators;
+import android.view.animation.LinearInterpolator;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -37,7 +36,8 @@
  */
 public class AnimationProps {
 
-    public static final AnimationProps IMMEDIATE = new AnimationProps(0, Interpolators.LINEAR);
+    private static final Interpolator LINEAR_INTERPOLATOR = new LinearInterpolator();
+    public static final AnimationProps IMMEDIATE = new AnimationProps(0, LINEAR_INTERPOLATOR);
 
     @Retention(RetentionPolicy.SOURCE)
     @IntDef({ALL, TRANSLATION_X, TRANSLATION_Y, TRANSLATION_Z, ALPHA, SCALE, BOUNDS})
@@ -51,7 +51,6 @@
     public static final int SCALE = 5;
     public static final int BOUNDS = 6;
     public static final int DIM_ALPHA = 7;
-    public static final int FOCUS_STATE = 8;
 
     private SparseLongArray mPropStartDelay;
     private SparseLongArray mPropDuration;
@@ -195,9 +194,9 @@
             if (interp != null) {
                 return interp;
             }
-            return mPropInterpolators.get(ALL, Interpolators.LINEAR);
+            return mPropInterpolators.get(ALL, LINEAR_INTERPOLATOR);
         }
-        return Interpolators.LINEAR;
+        return LINEAR_INTERPOLATOR;
     }
 
     /**
diff --git a/com/android/systemui/recents/misc/RectFEvaluator.java b/com/android/systemui/shared/recents/utilities/RectFEvaluator.java
similarity index 95%
rename from com/android/systemui/recents/misc/RectFEvaluator.java
rename to com/android/systemui/shared/recents/utilities/RectFEvaluator.java
index 72511de..51c1b5a 100644
--- a/com/android/systemui/recents/misc/RectFEvaluator.java
+++ b/com/android/systemui/shared/recents/utilities/RectFEvaluator.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.systemui.recents.misc;
+package com.android.systemui.shared.recents.utilities;
 
 import android.animation.TypeEvaluator;
 import android.graphics.RectF;
@@ -23,7 +23,7 @@
  */
 public class RectFEvaluator implements TypeEvaluator<RectF> {
 
-    private RectF mRect = new RectF();
+    private final RectF mRect = new RectF();
 
     /**
      * This function returns the result of linearly interpolating the start and
diff --git a/com/android/systemui/recents/misc/Utilities.java b/com/android/systemui/shared/recents/utilities/Utilities.java
similarity index 90%
rename from com/android/systemui/recents/misc/Utilities.java
rename to com/android/systemui/shared/recents/utilities/Utilities.java
index 4349e30..a5d1963 100644
--- a/com/android/systemui/recents/misc/Utilities.java
+++ b/com/android/systemui/shared/recents/utilities/Utilities.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.recents.misc;
+package com.android.systemui.shared.recents.utilities;
 
 import android.animation.Animator;
 import android.animation.AnimatorSet;
@@ -38,12 +38,8 @@
 import android.view.ViewParent;
 import android.view.ViewStub;
 
-import com.android.systemui.recents.model.Task;
-import com.android.systemui.recents.views.TaskViewTransform;
-
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.List;
 
 /* Common code */
 public class Utilities {
@@ -76,7 +72,6 @@
 
     public static final RectFEvaluator RECTF_EVALUATOR = new RectFEvaluator();
     public static final RectEvaluator RECT_EVALUATOR = new RectEvaluator(new Rect());
-    public static final Rect EMPTY_RECT = new Rect();
 
     /**
      * @return the first parent walking up the view hierarchy that has the given class type.
@@ -253,24 +248,6 @@
     }
 
     /**
-     * Updates {@param transforms} to be the same size as {@param tasks}.
-     */
-    public static void matchTaskListSize(List<Task> tasks, List<TaskViewTransform> transforms) {
-        // We can reuse the task transforms where possible to reduce object allocation
-        int taskTransformCount = transforms.size();
-        int taskCount = tasks.size();
-        if (taskTransformCount < taskCount) {
-            // If there are less transforms than tasks, then add as many transforms as necessary
-            for (int i = taskTransformCount; i < taskCount; i++) {
-                transforms.add(new TaskViewTransform());
-            }
-        } else if (taskTransformCount > taskCount) {
-            // If there are more transforms than tasks, then just subset the transform list
-            transforms.subList(taskCount, taskTransformCount).clear();
-        }
-    }
-
-    /**
      * Used for debugging, converts DP to PX.
      */
     public static float dpToPx(Resources res, float dp) {
diff --git a/com/android/systemui/shared/system/ActivityManagerWrapper.java b/com/android/systemui/shared/system/ActivityManagerWrapper.java
new file mode 100644
index 0000000..3f93f76
--- /dev/null
+++ b/com/android/systemui/shared/system/ActivityManagerWrapper.java
@@ -0,0 +1,201 @@
+/*
+ * 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.shared.system;
+
+import static android.app.ActivityManager.RECENT_IGNORE_UNAVAILABLE;
+
+import android.annotation.NonNull;
+import android.app.ActivityManager;
+import android.app.ActivityManager.RecentTaskInfo;
+import android.app.AppGlobals;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
+import android.content.res.Resources;
+import android.content.res.Resources.NotFoundException;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.IconDrawableFactory;
+import android.util.Log;
+
+import com.android.systemui.shared.recents.model.ThumbnailData;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ActivityManagerWrapper {
+
+    private static final String TAG = "ActivityManagerWrapper";
+
+    private static final ActivityManagerWrapper sInstance = new ActivityManagerWrapper();
+
+    private final PackageManager mPackageManager;
+    private final IconDrawableFactory mDrawableFactory;
+
+    private ActivityManagerWrapper() {
+        final Context context = AppGlobals.getInitialApplication();
+        mPackageManager = context.getPackageManager();
+        mDrawableFactory = IconDrawableFactory.newInstance(context);
+    }
+
+    public static ActivityManagerWrapper getInstance() {
+        return sInstance;
+    }
+
+    /**
+     * @return the current user's id.
+     */
+    public int getCurrentUserId() {
+        UserInfo ui;
+        try {
+            ui = ActivityManager.getService().getCurrentUser();
+            return ui != null ? ui.id : 0;
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @return a list of the recents tasks.
+     */
+    public List<RecentTaskInfo> getRecentTasks(int numTasks, int userId) {
+        try {
+            return ActivityManager.getService().getRecentTasks(numTasks,
+                            RECENT_IGNORE_UNAVAILABLE, userId).getList();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to get recent tasks", e);
+            return new ArrayList<>();
+        }
+    }
+
+    /**
+     * @return the task snapshot for the given {@param taskId}.
+     */
+    public @NonNull ThumbnailData getTaskThumbnail(int taskId, boolean reducedResolution) {
+        ActivityManager.TaskSnapshot snapshot = null;
+        try {
+            snapshot = ActivityManager.getService().getTaskSnapshot(taskId, reducedResolution);
+        } catch (RemoteException e) {
+            Log.w(TAG, "Failed to retrieve task snapshot", e);
+        }
+        if (snapshot != null) {
+            return new ThumbnailData(snapshot);
+        } else {
+            return new ThumbnailData();
+        }
+    }
+
+    /**
+     * @return the task description icon, loading and badging it if it necessary.
+     */
+    public Drawable getBadgedTaskDescriptionIcon(Context context,
+            ActivityManager.TaskDescription taskDescription, int userId, Resources res) {
+        Bitmap tdIcon = taskDescription.getInMemoryIcon();
+        Drawable dIcon = null;
+        if (tdIcon != null) {
+            dIcon = new BitmapDrawable(res, tdIcon);
+        } else if (taskDescription.getIconResource() != 0) {
+            try {
+                dIcon = context.getDrawable(taskDescription.getIconResource());
+            } catch (NotFoundException e) {
+                Log.e(TAG, "Could not find icon drawable from resource", e);
+            }
+        } else {
+            tdIcon = ActivityManager.TaskDescription.loadTaskDescriptionIcon(
+                    taskDescription.getIconFilename(), userId);
+            if (tdIcon != null) {
+                dIcon = new BitmapDrawable(res, tdIcon);
+            }
+        }
+        if (dIcon != null) {
+            return getBadgedIcon(dIcon, userId);
+        }
+        return null;
+    }
+
+    /**
+     * @return the given icon for a user, badging if necessary.
+     */
+    private Drawable getBadgedIcon(Drawable icon, int userId) {
+        if (userId != UserHandle.myUserId()) {
+            icon = mPackageManager.getUserBadgedIcon(icon, new UserHandle(userId));
+        }
+        return icon;
+    }
+
+    /**
+     * @return the activity icon for the ActivityInfo for a user, badging if necessary.
+     */
+    public Drawable getBadgedActivityIcon(ActivityInfo info, int userId) {
+        return mDrawableFactory.getBadgedIcon(info, info.applicationInfo, userId);
+    }
+
+    /**
+     * @return the application icon for the ApplicationInfo for a user, badging if necessary.
+     */
+    public Drawable getBadgedApplicationIcon(ApplicationInfo appInfo, int userId) {
+        return mDrawableFactory.getBadgedIcon(appInfo, userId);
+    }
+
+    /**
+     * @return the activity label, badging if necessary.
+     */
+    public String getBadgedActivityLabel(ActivityInfo info, int userId) {
+        return getBadgedLabel(info.loadLabel(mPackageManager).toString(), userId);
+    }
+
+    /**
+     * @return the application label, badging if necessary.
+     */
+    public String getBadgedApplicationLabel(ApplicationInfo appInfo, int userId) {
+        return getBadgedLabel(appInfo.loadLabel(mPackageManager).toString(), userId);
+    }
+
+    /**
+     * @return the content description for a given task, badging it if necessary.  The content
+     * description joins the app and activity labels.
+     */
+    public String getBadgedContentDescription(ActivityInfo info, int userId,
+            ActivityManager.TaskDescription td) {
+        String activityLabel;
+        if (td != null && td.getLabel() != null) {
+            activityLabel = td.getLabel();
+        } else {
+            activityLabel = info.loadLabel(mPackageManager).toString();
+        }
+        String applicationLabel = info.applicationInfo.loadLabel(mPackageManager).toString();
+        String badgedApplicationLabel = getBadgedLabel(applicationLabel, userId);
+        return applicationLabel.equals(activityLabel)
+                ? badgedApplicationLabel
+                : badgedApplicationLabel + " " + activityLabel;
+    }
+
+    /**
+     * @return the given label for a user, badging if necessary.
+     */
+    private String getBadgedLabel(String label, int userId) {
+        if (userId != UserHandle.myUserId()) {
+            label = mPackageManager.getUserBadgedLabel(label, new UserHandle(userId)).toString();
+        }
+        return label;
+    }
+}
diff --git a/com/android/systemui/shared/system/PackageManagerWrapper.java b/com/android/systemui/shared/system/PackageManagerWrapper.java
new file mode 100644
index 0000000..d5e6e6e
--- /dev/null
+++ b/com/android/systemui/shared/system/PackageManagerWrapper.java
@@ -0,0 +1,50 @@
+/*
+ * 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.shared.system;
+
+import android.app.AppGlobals;
+import android.content.ComponentName;
+import android.content.pm.ActivityInfo;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
+import android.os.RemoteException;
+
+public class PackageManagerWrapper {
+
+    private static final String TAG = "PackageManagerWrapper";
+
+    private static final PackageManagerWrapper sInstance = new PackageManagerWrapper();
+
+    private static final IPackageManager mIPackageManager = AppGlobals.getPackageManager();
+
+    public static PackageManagerWrapper getInstance() {
+        return sInstance;
+    }
+
+    /**
+     * @return the activity info for a given {@param componentName} and {@param userId}.
+     */
+    public ActivityInfo getActivityInfo(ComponentName componentName, int userId) {
+        try {
+            return mIPackageManager.getActivityInfo(componentName, PackageManager.GET_META_DATA,
+                    userId);
+        } catch (RemoteException e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+}
diff --git a/com/android/systemui/shortcut/ShortcutKeyDispatcher.java b/com/android/systemui/shortcut/ShortcutKeyDispatcher.java
index 7699bb9..195f4d3 100644
--- a/com/android/systemui/shortcut/ShortcutKeyDispatcher.java
+++ b/com/android/systemui/shortcut/ShortcutKeyDispatcher.java
@@ -16,37 +16,28 @@
 
 package com.android.systemui.shortcut;
 
-import android.accessibilityservice.AccessibilityServiceInfo;
+import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT;
+import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
+import static android.os.UserHandle.USER_CURRENT;
+
 import android.app.ActivityManager;
-import android.app.IActivityManager;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.pm.ServiceInfo;
 import android.content.res.Configuration;
 import android.os.RemoteException;
-import android.os.UserHandle;
-import android.util.ArraySet;
-import android.util.DisplayMetrics;
 import android.util.Log;
 import android.view.IWindowManager;
 import android.view.KeyEvent;
 import android.view.WindowManager;
 import android.view.WindowManagerGlobal;
-import android.view.accessibility.AccessibilityManager;
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+
 import com.android.internal.policy.DividerSnapAlgorithm;
-import com.android.settingslib.accessibility.AccessibilityUtils;
-import com.android.systemui.R;
 import com.android.systemui.SystemUI;
 import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.misc.SystemServicesProxy;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.stackdivider.Divider;
 import com.android.systemui.stackdivider.DividerView;
-import com.android.systemui.statusbar.phone.NavigationBarGestureHelper;
 
 import java.util.List;
-import java.util.Set;
 
 /**
  * Dispatches shortcut to System UI components
@@ -58,7 +49,6 @@
 
     private ShortcutKeyServiceProxy mShortcutKeyServiceProxy = new ShortcutKeyServiceProxy(this);
     private IWindowManager mWindowManagerService = WindowManagerGlobal.getWindowManagerService();
-    private IActivityManager mActivityManager = ActivityManager.getService();
 
     protected final long META_MASK = ((long) KeyEvent.META_META_ON) << Integer.SIZE;
     protected final long ALT_MASK = ((long) KeyEvent.META_ALT_ON) << Integer.SIZE;
@@ -102,11 +92,10 @@
                 // If there is no window docked, we dock the top-most window.
                 Recents recents = getComponent(Recents.class);
                 int dockMode = (shortcutCode == SC_DOCK_LEFT)
-                        ? ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT
-                        : ActivityManager.DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT;
+                        ? DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT
+                        : DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT;
                 List<ActivityManager.RecentTaskInfo> taskList =
-                        SystemServicesProxy.getInstance(mContext).getRecentTasks(1,
-                                UserHandle.USER_CURRENT, false, new ArraySet<>());
+                        ActivityManagerWrapper.getInstance().getRecentTasks(1, USER_CURRENT);
                 recents.showRecentApps(
                         false /* triggeredFromAltTab */,
                         false /* fromHome */);
diff --git a/com/android/systemui/stackdivider/ForcedResizableInfoActivityController.java b/com/android/systemui/stackdivider/ForcedResizableInfoActivityController.java
index 578a18a..0997983 100644
--- a/com/android/systemui/stackdivider/ForcedResizableInfoActivityController.java
+++ b/com/android/systemui/stackdivider/ForcedResizableInfoActivityController.java
@@ -32,7 +32,7 @@
 import com.android.systemui.recents.events.activity.AppTransitionFinishedEvent;
 import com.android.systemui.recents.events.component.ShowUserToastEvent;
 import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener;
+import com.android.systemui.recents.misc.TaskStackChangeListener;
 import com.android.systemui.stackdivider.events.StartedDragingEvent;
 import com.android.systemui.stackdivider.events.StoppedDragingEvent;
 
@@ -76,7 +76,7 @@
         mContext = context;
         EventBus.getDefault().register(this);
         SystemServicesProxy.getInstance(context).registerTaskStackListener(
-                new TaskStackListener() {
+                new TaskStackChangeListener() {
                     @Override
                     public void onActivityForcedResizable(String packageName, int taskId,
                             int reason) {
diff --git a/com/android/systemui/statusbar/ExpandableNotificationRow.java b/com/android/systemui/statusbar/ExpandableNotificationRow.java
index 966e789..6c5f4b2 100644
--- a/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -36,6 +36,7 @@
 import android.util.AttributeSet;
 import android.util.FloatProperty;
 import android.util.Property;
+import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.NotificationHeaderView;
@@ -174,6 +175,11 @@
     private boolean mShowNoBackground;
     private ExpandableNotificationRow mNotificationParent;
     private OnExpandClickListener mOnExpandClickListener;
+
+    // Listener will be called when receiving a long click event.
+    // Use #setLongPressPosition to optionally assign positional data with the long press.
+    private LongPressListener mLongPressListener;
+
     private boolean mGroupExpansionChanging;
 
     /**
@@ -788,6 +794,10 @@
         mOnExpandClickListener = onExpandClickListener;
     }
 
+    public void setLongPressListener(LongPressListener longPressListener) {
+        mLongPressListener = longPressListener;
+    }
+
     @Override
     public void setOnClickListener(@Nullable OnClickListener l) {
         super.setOnClickListener(l);
@@ -1338,6 +1348,47 @@
         }
     }
 
+    private void doLongClickCallback() {
+        doLongClickCallback(getWidth() / 2, getHeight() / 2);
+    }
+
+    public void doLongClickCallback(int x, int y) {
+        createMenu();
+        MenuItem menuItem = getProvider().getLongpressMenuItem(mContext);
+        if (mLongPressListener != null && menuItem != null) {
+            mLongPressListener.onLongPress(this, x, y, menuItem);
+        }
+    }
+
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        if (KeyEvent.isConfirmKey(keyCode)) {
+            event.startTracking();
+            return true;
+        }
+        return super.onKeyDown(keyCode, event);
+    }
+
+    @Override
+    public boolean onKeyUp(int keyCode, KeyEvent event) {
+        if (KeyEvent.isConfirmKey(keyCode)) {
+            if (!event.isCanceled()) {
+                performClick();
+            }
+            return true;
+        }
+        return super.onKeyUp(keyCode, event);
+    }
+
+    @Override
+    public boolean onKeyLongPress(int keyCode, KeyEvent event) {
+        if (KeyEvent.isConfirmKey(keyCode)) {
+            doLongClickCallback();
+            return true;
+        }
+        return false;
+    }
+
     public void resetTranslation() {
         if (mTranslateAnim != null) {
             mTranslateAnim.cancel();
@@ -2205,6 +2256,7 @@
     @Override
     public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
         super.onInitializeAccessibilityNodeInfoInternal(info);
+        info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK);
         if (canViewBeDismissed()) {
             info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_DISMISS);
         }
@@ -2244,6 +2296,9 @@
             case AccessibilityNodeInfo.ACTION_EXPAND:
                 mExpandClickListener.onClick(this);
                 return true;
+            case AccessibilityNodeInfo.ACTION_LONG_CLICK:
+                doLongClickCallback();
+                return true;
         }
         return false;
     }
@@ -2332,4 +2387,15 @@
     protected void setChildrenContainer(NotificationChildrenContainer childrenContainer) {
         mChildrenContainer = childrenContainer;
     }
+
+    /**
+     * Equivalent to View.OnLongClickListener with coordinates
+     */
+    public interface LongPressListener {
+        /**
+         * Equivalent to {@link View.OnLongClickListener#onLongClick(View)} with coordinates
+         * @return whether the longpress was handled
+         */
+        boolean onLongPress(View v, int x, int y, MenuItem item);
+    }
 }
diff --git a/com/android/systemui/statusbar/car/CarStatusBar.java b/com/android/systemui/statusbar/car/CarStatusBar.java
index 59d3e0a..fed2ebe 100644
--- a/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -36,14 +36,18 @@
 import android.view.WindowManager;
 import android.widget.LinearLayout;
 
+import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.BatteryMeterView;
 import com.android.systemui.Dependency;
+import com.android.systemui.Prefs;
 import com.android.systemui.R;
-import com.android.systemui.SwipeHelper;
+import com.android.systemui.classifier.FalsingLog;
+import com.android.systemui.classifier.FalsingManager;
 import com.android.systemui.fragments.FragmentHostManager;
 import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener;
+import com.android.systemui.recents.misc.TaskStackChangeListener;
+import com.android.systemui.statusbar.ExpandableNotificationRow;
 import com.android.systemui.statusbar.NotificationData;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment;
@@ -51,10 +55,6 @@
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.UserSwitcherController;
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.systemui.classifier.FalsingLog;
-import com.android.systemui.classifier.FalsingManager;
-import com.android.systemui.Prefs;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -247,11 +247,12 @@
     }
 
     /**
-     * Returns the {@link com.android.systemui.SwipeHelper.LongPressListener} that will be
-     * triggered when a notification card is long-pressed.
+     * Returns the
+     * {@link com.android.systemui.statusbar.ExpandableNotificationRow.LongPressListener} that will
+     * be triggered when a notification card is long-pressed.
      */
     @Override
-    protected SwipeHelper.LongPressListener getNotificationLongClicker() {
+    protected ExpandableNotificationRow.LongPressListener getNotificationLongClicker() {
         // For the automative use case, we do not want to the user to be able to interact with
         // a notification other than a regular click. As a result, just return null for the
         // long click listener.
@@ -304,10 +305,10 @@
     }
 
     /**
-     * An implementation of TaskStackListener, that listens for changes in the system task
+     * An implementation of TaskStackChangeListener, that listens for changes in the system task
      * stack and notifies the navigation bar.
      */
-    private class TaskStackListenerImpl extends TaskStackListener {
+    private class TaskStackListenerImpl extends TaskStackChangeListener {
         @Override
         public void onTaskStackChanged() {
             SystemServicesProxy ssp = Recents.getSystemServices();
diff --git a/com/android/systemui/statusbar/phone/LockscreenWallpaper.java b/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
index 87f5ca7..40ddf5b 100644
--- a/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
+++ b/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
@@ -124,8 +124,8 @@
         } else {
             if (selectedUser != null) {
                 // Show the selected user's static wallpaper.
-                return LoaderResult.success(
-                        mWallpaperManager.getBitmapAsUser(selectedUser.getIdentifier()));
+                return LoaderResult.success(mWallpaperManager.getBitmapAsUser(
+                        selectedUser.getIdentifier(), true /* hardware */));
 
             } else {
                 // When there is no selected user, show the system wallpaper
diff --git a/com/android/systemui/statusbar/phone/NavigationBarTransitions.java b/com/android/systemui/statusbar/phone/NavigationBarTransitions.java
index f3ca66f..c950036 100644
--- a/com/android/systemui/statusbar/phone/NavigationBarTransitions.java
+++ b/com/android/systemui/statusbar/phone/NavigationBarTransitions.java
@@ -17,12 +17,19 @@
 package com.android.systemui.statusbar.phone;
 
 import android.content.Context;
+import android.os.Handler;
+import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.util.SparseArray;
+import android.view.Display;
+import android.view.IWallpaperVisibilityListener;
+import android.view.IWindowManager;
 import android.view.MotionEvent;
 import android.view.View;
+import android.view.WindowManagerGlobal;
 
 import com.android.internal.statusbar.IStatusBarService;
+import com.android.systemui.Dependency;
 import com.android.systemui.R;
 
 public final class NavigationBarTransitions extends BarTransitions {
@@ -30,6 +37,7 @@
     private final NavigationBarView mView;
     private final IStatusBarService mBarService;
     private final LightBarTransitionsController mLightTransitionsController;
+    private boolean mWallpaperVisible;
 
     private boolean mLightsOut;
     private boolean mAutoDim;
@@ -41,6 +49,21 @@
                 ServiceManager.getService(Context.STATUS_BAR_SERVICE));
         mLightTransitionsController = new LightBarTransitionsController(view.getContext(),
                 this::applyDarkIntensity);
+
+        IWindowManager windowManagerService = Dependency.get(IWindowManager.class);
+        Handler handler = Handler.getMain();
+        try {
+            mWallpaperVisible = windowManagerService.registerWallpaperVisibilityListener(
+                new IWallpaperVisibilityListener.Stub() {
+                    @Override
+                    public void onWallpaperVisibilityChanged(boolean newVisibility,
+                            int displayId) throws RemoteException {
+                        mWallpaperVisible = newVisibility;
+                        handler.post(() -> applyLightsOut(true, false));
+                    }
+                }, Display.DEFAULT_DISPLAY);
+        } catch (RemoteException e) {
+        }
     }
 
     public void init() {
@@ -57,7 +80,7 @@
 
     @Override
     protected boolean isLightsOut(int mode) {
-        return super.isLightsOut(mode) || mAutoDim;
+        return super.isLightsOut(mode) || (mAutoDim && !mWallpaperVisible);
     }
 
     public LightBarTransitionsController getLightTransitionsController() {
@@ -85,7 +108,7 @@
         // ok, everyone, stop it right there
         navButtons.animate().cancel();
 
-        final float navButtonsAlpha = lightsOut ? 0.5f : 1f;
+        final float navButtonsAlpha = lightsOut ? 0.6f : 1f;
 
         if (!animate) {
             navButtons.setAlpha(navButtonsAlpha);
diff --git a/com/android/systemui/statusbar/phone/NotificationPanelView.java b/com/android/systemui/statusbar/phone/NotificationPanelView.java
index 7b11ace..af03440 100644
--- a/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -704,7 +704,7 @@
                     mInitialHeightOnTouch = mQsExpansionHeight;
                     mQsTracking = true;
                     mIntercepting = false;
-                    mNotificationStackScroller.removeLongPressCallback();
+                    mNotificationStackScroller.cancelLongPress();
                 }
                 break;
             case MotionEvent.ACTION_POINTER_UP:
@@ -740,7 +740,7 @@
                     mInitialTouchY = y;
                     mInitialTouchX = x;
                     mIntercepting = false;
-                    mNotificationStackScroller.removeLongPressCallback();
+                    mNotificationStackScroller.cancelLongPress();
                     return true;
                 }
                 break;
diff --git a/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index 9c837ed..b876286 100644
--- a/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -66,7 +66,7 @@
 import com.android.systemui.qs.tiles.DndTile;
 import com.android.systemui.qs.tiles.RotationLockTile;
 import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener;
+import com.android.systemui.recents.misc.TaskStackChangeListener;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.CommandQueue.Callbacks;
 import com.android.systemui.statusbar.policy.BluetoothController;
@@ -639,12 +639,17 @@
     }
 
     private Intent getTaskIntent(int taskId, int userId) {
-        List<ActivityManager.RecentTaskInfo> tasks = mContext.getSystemService(ActivityManager.class)
-                .getRecentTasksForUser(NUM_TASKS_FOR_INSTANT_APP_INFO, 0, userId);
-        for (int i = 0; i < tasks.size(); i++) {
-            if (tasks.get(i).id == taskId) {
-                return tasks.get(i).baseIntent;
+        try {
+            final List<ActivityManager.RecentTaskInfo> tasks =
+                    ActivityManager.getService().getRecentTasks(
+                            NUM_TASKS_FOR_INSTANT_APP_INFO, 0, userId).getList();
+            for (int i = 0; i < tasks.size(); i++) {
+                if (tasks.get(i).id == taskId) {
+                    return tasks.get(i).baseIntent;
+                }
             }
+        } catch (RemoteException e) {
+            // Fall through
         }
         return null;
     }
@@ -763,7 +768,7 @@
         mIconController.setIconVisibility(mSlotDataSaver, isDataSaving);
     }
 
-    private final TaskStackListener mTaskListener = new TaskStackListener() {
+    private final TaskStackChangeListener mTaskListener = new TaskStackChangeListener() {
         @Override
         public void onTaskStackChanged() {
             // Listen for changes to stacks and then check which instant apps are foreground.
diff --git a/com/android/systemui/statusbar/phone/StatusBar.java b/com/android/systemui/statusbar/phone/StatusBar.java
index 54be857..9f03954 100644
--- a/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/com/android/systemui/statusbar/phone/StatusBar.java
@@ -160,7 +160,6 @@
 import com.android.systemui.Prefs;
 import com.android.systemui.R;
 import com.android.systemui.RecentsComponent;
-import com.android.systemui.SwipeHelper;
 import com.android.systemui.SystemUI;
 import com.android.systemui.SystemUIFactory;
 import com.android.systemui.UiOffloadThread;
@@ -4838,7 +4837,7 @@
 
     @Override
     public void onTouchSlopExceeded() {
-        mStackScroller.removeLongPressCallback();
+        mStackScroller.cancelLongPress();
         mStackScroller.checkSnoozeLeavebehind();
     }
 
@@ -5470,7 +5469,7 @@
 
         @Override
         public void onDoubleTap(float screenX, float screenY) {
-            if (screenX > 0 && screenY > 0 && mAmbientIndicationContainer != null 
+            if (screenX > 0 && screenY > 0 && mAmbientIndicationContainer != null
                 && mAmbientIndicationContainer.getVisibility() == View.VISIBLE) {
                 mAmbientIndicationContainer.getLocationOnScreen(mTmpInt2);
                 float viewX = screenX - mTmpInt2[0];
@@ -5882,8 +5881,7 @@
                 List<ActivityManager.RecentTaskInfo> recentTask = null;
                 try {
                     recentTask = ActivityManager.getService().getRecentTasks(1,
-                            ActivityManager.RECENT_WITH_EXCLUDED
-                            | ActivityManager.RECENT_INCLUDE_PROFILES,
+                            ActivityManager.RECENT_WITH_EXCLUDED,
                             mCurrentUserId).getList();
                 } catch (RemoteException e) {
                     // Abandon hope activity manager not running.
@@ -6296,14 +6294,15 @@
                 true /* removeControls */, x, y, true /* resetMenu */);
     }
 
-    protected SwipeHelper.LongPressListener getNotificationLongClicker() {
-        return new SwipeHelper.LongPressListener() {
+    protected ExpandableNotificationRow.LongPressListener getNotificationLongClicker() {
+        return new ExpandableNotificationRow.LongPressListener() {
             @Override
             public boolean onLongPress(View v, final int x, final int y,
                     MenuItem item) {
                 if (!(v instanceof ExpandableNotificationRow)) {
                     return false;
                 }
+
                 if (v.getWindowToken() == null) {
                     Log.e(TAG, "Trying to show notification guts, but not attached to window");
                     return false;
@@ -6318,7 +6317,7 @@
                     closeAndSaveGuts(false /* removeLeavebehind */, false /* force */,
                             true /* removeControls */, -1 /* x */, -1 /* y */,
                             true /* resetMenu */);
-                    return false;
+                    return true;
                 }
                 bindGuts(row, item);
                 NotificationGuts guts = row.getGuts();
@@ -6598,6 +6597,7 @@
         row.setRemoteViewClickHandler(mOnClickHandler);
         row.setInflationCallback(this);
         row.setSecureStateProvider(this::isKeyguardCurrentlySecure);
+        row.setLongPressListener(getNotificationLongClicker());
 
         // Get the app name.
         // Note that Notification.Builder#bindHeaderAppName has similar logic
diff --git a/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index 75532d9..1e14626 100644
--- a/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -240,7 +240,7 @@
      * motion.
      */
     private int mMaxScrollAfterExpand;
-    private SwipeHelper.LongPressListener mLongPressListener;
+    private ExpandableNotificationRow.LongPressListener mLongPressListener;
 
     private NotificationMenuRowPlugin mCurrMenuRow;
     private View mTranslatingParentView;
@@ -410,7 +410,6 @@
         mExpandHelper.setEventSource(this);
         mExpandHelper.setScrollAdapter(this);
         mSwipeHelper = new NotificationSwipeHelper(SwipeHelper.X, this, getContext());
-        mSwipeHelper.setLongPressListener(mLongPressListener);
         mStackScrollAlgorithm = createStackScrollAlgorithm(context);
         initView(context);
         mFalsingManager = FalsingManager.getInstance(context);
@@ -884,8 +883,7 @@
         return firstChild != null ? firstChild.getMinHeight() : mCollapsedSize;
     }
 
-    public void setLongPressListener(SwipeHelper.LongPressListener listener) {
-        mSwipeHelper.setLongPressListener(listener);
+    public void setLongPressListener(ExpandableNotificationRow.LongPressListener listener) {
         mLongPressListener = listener;
     }
 
@@ -1175,7 +1173,7 @@
         if (v instanceof ExpandableNotificationRow) {
             ((ExpandableNotificationRow) v).setUserLocked(userLocked);
         }
-        removeLongPressCallback();
+        cancelLongPress();
         requestDisallowInterceptTouchEvent(true);
     }
 
@@ -2581,7 +2579,7 @@
     public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
         super.requestDisallowInterceptTouchEvent(disallowIntercept);
         if (disallowIntercept) {
-            mSwipeHelper.removeLongPressCallback();
+            cancelLongPress();
         }
     }
 
@@ -3302,7 +3300,7 @@
         mIsBeingDragged = isDragged;
         if (isDragged) {
             requestDisallowInterceptTouchEvent(true);
-            removeLongPressCallback();
+            cancelLongPress();
         }
     }
 
@@ -3310,7 +3308,7 @@
     public void onWindowFocusChanged(boolean hasWindowFocus) {
         super.onWindowFocusChanged(hasWindowFocus);
         if (!hasWindowFocus) {
-            removeLongPressCallback();
+            cancelLongPress();
         }
     }
 
@@ -3324,7 +3322,7 @@
 
     @Override
     public void requestDisallowLongPress() {
-        removeLongPressCallback();
+        cancelLongPress();
     }
 
     @Override
@@ -3332,8 +3330,8 @@
         mDisallowDismissInThisMotion = true;
     }
 
-    public void removeLongPressCallback() {
-        mSwipeHelper.removeLongPressCallback();
+    public void cancelLongPress() {
+        mSwipeHelper.cancelLongPress();
     }
 
     @Override
diff --git a/com/android/systemui/util/wakelock/DelayedWakeLock.java b/com/android/systemui/util/wakelock/DelayedWakeLock.java
index b835909..5ec3dff 100644
--- a/com/android/systemui/util/wakelock/DelayedWakeLock.java
+++ b/com/android/systemui/util/wakelock/DelayedWakeLock.java
@@ -23,7 +23,7 @@
  */
 public class DelayedWakeLock implements WakeLock {
 
-    private static final long RELEASE_DELAY_MS = 100;
+    private static final long RELEASE_DELAY_MS = 120;
 
     private final Handler mHandler;
     private final WakeLock mInner;
diff --git a/com/android/uiautomator/testrunner/UiAutomatorTestCase.java b/com/android/uiautomator/testrunner/UiAutomatorTestCase.java
index 3d5476d..7c9aede 100644
--- a/com/android/uiautomator/testrunner/UiAutomatorTestCase.java
+++ b/com/android/uiautomator/testrunner/UiAutomatorTestCase.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2012 The Android Open Source Project
+ * Copyright (C) 2013 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -16,55 +16,24 @@
 
 package com.android.uiautomator.testrunner;
 
-import android.content.Context;
+import android.app.Instrumentation;
 import android.os.Bundle;
-import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.os.SystemClock;
-import android.view.inputmethod.InputMethodInfo;
+import android.test.InstrumentationTestCase;
 
-import com.android.internal.view.IInputMethodManager;
+import com.android.uiautomator.core.InstrumentationUiAutomatorBridge;
 import com.android.uiautomator.core.UiDevice;
 
-import junit.framework.TestCase;
-
-import java.util.List;
-
 /**
- * UI automation test should extend this class. This class provides access
- * to the following:
- * {@link UiDevice} instance
- * {@link Bundle} for command line parameters.
- * @since API Level 16
+ * UI Automator test case that is executed on the device.
  * @deprecated New tests should be written using UI Automator 2.0 which is available as part of the
  * Android Testing Support Library.
  */
 @Deprecated
-public class UiAutomatorTestCase extends TestCase {
+public class UiAutomatorTestCase extends InstrumentationTestCase {
 
-    private static final String DISABLE_IME = "disable_ime";
-    private static final String DUMMY_IME_PACKAGE = "com.android.testing.dummyime";
-    private UiDevice mUiDevice;
     private Bundle mParams;
     private IAutomationSupport mAutomationSupport;
-    private boolean mShouldDisableIme = false;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        mShouldDisableIme = "true".equals(mParams.getString(DISABLE_IME));
-        if (mShouldDisableIme) {
-            setDummyIme();
-        }
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        if (mShouldDisableIme) {
-            restoreActiveIme();
-        }
-        super.tearDown();
-    }
 
     /**
      * Get current instance of {@link UiDevice}. Works similar to calling the static
@@ -72,7 +41,7 @@
      * @since API Level 16
      */
     public UiDevice getUiDevice() {
-        return mUiDevice;
+        return UiDevice.getInstance();
     }
 
     /**
@@ -85,34 +54,43 @@
         return mParams;
     }
 
+    void setAutomationSupport(IAutomationSupport automationSupport) {
+        mAutomationSupport = automationSupport;
+    }
+
     /**
      * Provides support for running tests to report interim status
      *
      * @return IAutomationSupport
      * @since API Level 16
+     * @deprecated Use {@link Instrumentation#sendStatus(int, Bundle)} instead
      */
     public IAutomationSupport getAutomationSupport() {
+        if (mAutomationSupport == null) {
+            mAutomationSupport = new InstrumentationAutomationSupport(getInstrumentation());
+        }
         return mAutomationSupport;
     }
 
     /**
-     * package private
-     * @param uiDevice
+     * Initializes this test case.
+     *
+     * @param params Instrumentation arguments.
      */
-    void setUiDevice(UiDevice uiDevice) {
-        mUiDevice = uiDevice;
-    }
-
-    /**
-     * package private
-     * @param params
-     */
-    void setParams(Bundle params) {
+    void initialize(Bundle params) {
         mParams = params;
-    }
 
-    void setAutomationSupport(IAutomationSupport automationSupport) {
-        mAutomationSupport = automationSupport;
+        // check if this is a monkey test mode
+        String monkeyVal = mParams.getString("monkey");
+        if (monkeyVal != null) {
+            // only if the monkey key is specified, we alter the state of monkey
+            // else we should leave things as they are.
+            getInstrumentation().getUiAutomation().setRunAsMonkey(Boolean.valueOf(monkeyVal));
+        }
+
+        UiDevice.getInstance().initialize(new InstrumentationUiAutomatorBridge(
+                getInstrumentation().getContext(),
+                getInstrumentation().getUiAutomation()));
     }
 
     /**
@@ -123,28 +101,4 @@
     public void sleep(long ms) {
         SystemClock.sleep(ms);
     }
-
-    private void setDummyIme() throws RemoteException {
-        IInputMethodManager im = IInputMethodManager.Stub.asInterface(ServiceManager
-                .getService(Context.INPUT_METHOD_SERVICE));
-        List<InputMethodInfo> infos = im.getInputMethodList();
-        String id = null;
-        for (InputMethodInfo info : infos) {
-            if (DUMMY_IME_PACKAGE.equals(info.getComponent().getPackageName())) {
-                id = info.getId();
-            }
-        }
-        if (id == null) {
-            throw new RuntimeException(String.format(
-                    "Required testing fixture missing: IME package (%s)", DUMMY_IME_PACKAGE));
-        }
-        im.setInputMethod(null, id);
-    }
-
-    private void restoreActiveIme() throws RemoteException {
-        // TODO: figure out a way to restore active IME
-        // Currently retrieving active IME requires querying secure settings provider, which is hard
-        // to do without a Context; so the caveat here is that to make the post test device usable,
-        // the active IME needs to be manually switched.
-    }
 }
diff --git a/foo/bar/ComplexDao.java b/foo/bar/ComplexDao.java
index 89859e5..dbb54fb 100644
--- a/foo/bar/ComplexDao.java
+++ b/foo/bar/ComplexDao.java
@@ -1,69 +1,433 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
 package foo.bar;
-import android.arch.persistence.room.*;
-import java.util.List;
+
+import android.arch.lifecycle.ComputableLiveData;
 import android.arch.lifecycle.LiveData;
-@Dao
-abstract class ComplexDao {
-    static class FullName {
-        public int id;
-        public String fullName;
+import android.arch.persistence.room.InvalidationTracker.Observer;
+import android.arch.persistence.room.RoomDatabase;
+import android.arch.persistence.room.RoomSQLiteQuery;
+import android.arch.persistence.room.util.StringUtil;
+import android.database.Cursor;
+import android.support.annotation.NonNull;
+import java.lang.Integer;
+import java.lang.Override;
+import java.lang.String;
+import java.lang.StringBuilder;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import javax.annotation.Generated;
+
+@Generated("android.arch.persistence.room.RoomProcessor")
+public class ComplexDao_Impl extends ComplexDao {
+    private final RoomDatabase __db;
+
+    public ComplexDao_Impl(ComplexDatabase __db) {
+        super(__db);
+        this.__db = __db;
     }
 
-    private final ComplexDatabase mDb;
-
-    public ComplexDao(ComplexDatabase db) {
-        mDb = db;
-    }
-
-    @Transaction
+    @Override
     public boolean transactionMethod(int i, String s, long l) {
-        return true;
+        __db.beginTransaction();
+        try {
+            boolean _result = super.transactionMethod(i, s, l);
+            __db.setTransactionSuccessful();
+            return _result;
+        } finally {
+            __db.endTransaction();
+        }
     }
 
-    @Query("SELECT name || lastName as fullName, uid as id FROM user where uid = :id")
-    abstract public List<FullName> fullNames(int id);
+    @Override
+    public List<ComplexDao.FullName> fullNames(int id) {
+        final String _sql = "SELECT name || lastName as fullName, uid as id FROM user where uid = ?";
+        final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 1);
+        int _argIndex = 1;
+        _statement.bindLong(_argIndex, id);
+        final Cursor _cursor = __db.query(_statement);
+        try {
+            final int _cursorIndexOfFullName = _cursor.getColumnIndexOrThrow("fullName");
+            final int _cursorIndexOfId = _cursor.getColumnIndexOrThrow("id");
+            final List<ComplexDao.FullName> _result = new ArrayList<ComplexDao.FullName>(_cursor.getCount());
+            while(_cursor.moveToNext()) {
+                final ComplexDao.FullName _item;
+                _item = new ComplexDao.FullName();
+                _item.fullName = _cursor.getString(_cursorIndexOfFullName);
+                _item.id = _cursor.getInt(_cursorIndexOfId);
+                _result.add(_item);
+            }
+            return _result;
+        } finally {
+            _cursor.close();
+            _statement.release();
+        }
+    }
 
-    @Query("SELECT * FROM user where uid = :id")
-    abstract public User getById(int id);
+    @Override
+    public User getById(int id) {
+        final String _sql = "SELECT * FROM user where uid = ?";
+        final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 1);
+        int _argIndex = 1;
+        _statement.bindLong(_argIndex, id);
+        final Cursor _cursor = __db.query(_statement);
+        try {
+            final int _cursorIndexOfUid = _cursor.getColumnIndexOrThrow("uid");
+            final int _cursorIndexOfName = _cursor.getColumnIndexOrThrow("name");
+            final int _cursorIndexOfLastName = _cursor.getColumnIndexOrThrow("lastName");
+            final int _cursorIndexOfAge = _cursor.getColumnIndexOrThrow("ageColumn");
+            final User _result;
+            if(_cursor.moveToFirst()) {
+                _result = new User();
+                _result.uid = _cursor.getInt(_cursorIndexOfUid);
+                _result.name = _cursor.getString(_cursorIndexOfName);
+                final String _tmpLastName;
+                _tmpLastName = _cursor.getString(_cursorIndexOfLastName);
+                _result.setLastName(_tmpLastName);
+                _result.age = _cursor.getInt(_cursorIndexOfAge);
+            } else {
+                _result = null;
+            }
+            return _result;
+        } finally {
+            _cursor.close();
+            _statement.release();
+        }
+    }
 
-    @Query("SELECT * FROM user where name LIKE :name AND lastName LIKE :lastName")
-    abstract public User findByName(String name, String lastName);
+    @Override
+    public User findByName(String name, String lastName) {
+        final String _sql = "SELECT * FROM user where name LIKE ? AND lastName LIKE ?";
+        final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 2);
+        int _argIndex = 1;
+        if (name == null) {
+            _statement.bindNull(_argIndex);
+        } else {
+            _statement.bindString(_argIndex, name);
+        }
+        _argIndex = 2;
+        if (lastName == null) {
+            _statement.bindNull(_argIndex);
+        } else {
+            _statement.bindString(_argIndex, lastName);
+        }
+        final Cursor _cursor = __db.query(_statement);
+        try {
+            final int _cursorIndexOfUid = _cursor.getColumnIndexOrThrow("uid");
+            final int _cursorIndexOfName = _cursor.getColumnIndexOrThrow("name");
+            final int _cursorIndexOfLastName = _cursor.getColumnIndexOrThrow("lastName");
+            final int _cursorIndexOfAge = _cursor.getColumnIndexOrThrow("ageColumn");
+            final User _result;
+            if(_cursor.moveToFirst()) {
+                _result = new User();
+                _result.uid = _cursor.getInt(_cursorIndexOfUid);
+                _result.name = _cursor.getString(_cursorIndexOfName);
+                final String _tmpLastName;
+                _tmpLastName = _cursor.getString(_cursorIndexOfLastName);
+                _result.setLastName(_tmpLastName);
+                _result.age = _cursor.getInt(_cursorIndexOfAge);
+            } else {
+                _result = null;
+            }
+            return _result;
+        } finally {
+            _cursor.close();
+            _statement.release();
+        }
+    }
 
-    @Query("SELECT * FROM user where uid IN (:ids)")
-    abstract public List<User> loadAllByIds(int... ids);
+    @Override
+    public List<User> loadAllByIds(int... ids) {
+        StringBuilder _stringBuilder = StringUtil.newStringBuilder();
+        _stringBuilder.append("SELECT * FROM user where uid IN (");
+        final int _inputSize = ids.length;
+        StringUtil.appendPlaceholders(_stringBuilder, _inputSize);
+        _stringBuilder.append(")");
+        final String _sql = _stringBuilder.toString();
+        final int _argCount = 0 + _inputSize;
+        final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, _argCount);
+        int _argIndex = 1;
+        for (int _item : ids) {
+            _statement.bindLong(_argIndex, _item);
+            _argIndex ++;
+        }
+        final Cursor _cursor = __db.query(_statement);
+        try {
+            final int _cursorIndexOfUid = _cursor.getColumnIndexOrThrow("uid");
+            final int _cursorIndexOfName = _cursor.getColumnIndexOrThrow("name");
+            final int _cursorIndexOfLastName = _cursor.getColumnIndexOrThrow("lastName");
+            final int _cursorIndexOfAge = _cursor.getColumnIndexOrThrow("ageColumn");
+            final List<User> _result = new ArrayList<User>(_cursor.getCount());
+            while(_cursor.moveToNext()) {
+                final User _item_1;
+                _item_1 = new User();
+                _item_1.uid = _cursor.getInt(_cursorIndexOfUid);
+                _item_1.name = _cursor.getString(_cursorIndexOfName);
+                final String _tmpLastName;
+                _tmpLastName = _cursor.getString(_cursorIndexOfLastName);
+                _item_1.setLastName(_tmpLastName);
+                _item_1.age = _cursor.getInt(_cursorIndexOfAge);
+                _result.add(_item_1);
+            }
+            return _result;
+        } finally {
+            _cursor.close();
+            _statement.release();
+        }
+    }
 
-    @Query("SELECT ageColumn FROM user where uid = :id")
-    abstract int getAge(int id);
+    @Override
+    int getAge(int id) {
+        final String _sql = "SELECT ageColumn FROM user where uid = ?";
+        final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 1);
+        int _argIndex = 1;
+        _statement.bindLong(_argIndex, id);
+        final Cursor _cursor = __db.query(_statement);
+        try {
+            final int _result;
+            if(_cursor.moveToFirst()) {
+                _result = _cursor.getInt(0);
+            } else {
+                _result = 0;
+            }
+            return _result;
+        } finally {
+            _cursor.close();
+            _statement.release();
+        }
+    }
 
-    @Query("SELECT ageColumn FROM user where uid IN(:ids)")
-    abstract public int[] getAllAges(int... ids);
+    @Override
+    public int[] getAllAges(int... ids) {
+        StringBuilder _stringBuilder = StringUtil.newStringBuilder();
+        _stringBuilder.append("SELECT ageColumn FROM user where uid IN(");
+        final int _inputSize = ids.length;
+        StringUtil.appendPlaceholders(_stringBuilder, _inputSize);
+        _stringBuilder.append(")");
+        final String _sql = _stringBuilder.toString();
+        final int _argCount = 0 + _inputSize;
+        final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, _argCount);
+        int _argIndex = 1;
+        for (int _item : ids) {
+            _statement.bindLong(_argIndex, _item);
+            _argIndex ++;
+        }
+        final Cursor _cursor = __db.query(_statement);
+        try {
+            final int[] _result = new int[_cursor.getCount()];
+            int _index = 0;
+            while(_cursor.moveToNext()) {
+                final int _item_1;
+                _item_1 = _cursor.getInt(0);
+                _result[_index] = _item_1;
+                _index ++;
+            }
+            return _result;
+        } finally {
+            _cursor.close();
+            _statement.release();
+        }
+    }
 
-    @Query("SELECT ageColumn FROM user where uid IN(:ids)")
-    abstract public List<Integer> getAllAgesAsList(List<Integer> ids);
+    @Override
+    public List<Integer> getAllAgesAsList(List<Integer> ids) {
+        StringBuilder _stringBuilder = StringUtil.newStringBuilder();
+        _stringBuilder.append("SELECT ageColumn FROM user where uid IN(");
+        final int _inputSize = ids.size();
+        StringUtil.appendPlaceholders(_stringBuilder, _inputSize);
+        _stringBuilder.append(")");
+        final String _sql = _stringBuilder.toString();
+        final int _argCount = 0 + _inputSize;
+        final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, _argCount);
+        int _argIndex = 1;
+        for (Integer _item : ids) {
+            if (_item == null) {
+                _statement.bindNull(_argIndex);
+            } else {
+                _statement.bindLong(_argIndex, _item);
+            }
+            _argIndex ++;
+        }
+        final Cursor _cursor = __db.query(_statement);
+        try {
+            final List<Integer> _result = new ArrayList<Integer>(_cursor.getCount());
+            while(_cursor.moveToNext()) {
+                final Integer _item_1;
+                if (_cursor.isNull(0)) {
+                    _item_1 = null;
+                } else {
+                    _item_1 = _cursor.getInt(0);
+                }
+                _result.add(_item_1);
+            }
+            return _result;
+        } finally {
+            _cursor.close();
+            _statement.release();
+        }
+    }
 
-    @Query("SELECT * FROM user where uid = :id")
-    abstract public LiveData<User> getByIdLive(int id);
+    @Override
+    public LiveData<User> getByIdLive(int id) {
+        final String _sql = "SELECT * FROM user where uid = ?";
+        final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 1);
+        int _argIndex = 1;
+        _statement.bindLong(_argIndex, id);
+        return new ComputableLiveData<User>() {
+            private Observer _observer;
 
-    @Query("SELECT * FROM user where uid IN (:ids)")
-    abstract public LiveData<List<User>> loadUsersByIdsLive(int... ids);
+            @Override
+            protected User compute() {
+                if (_observer == null) {
+                    _observer = new Observer("user") {
+                        @Override
+                        public void onInvalidated(@NonNull Set<String> tables) {
+                            invalidate();
+                        }
+                    };
+                    __db.getInvalidationTracker().addWeakObserver(_observer);
+                }
+                final Cursor _cursor = __db.query(_statement);
+                try {
+                    final int _cursorIndexOfUid = _cursor.getColumnIndexOrThrow("uid");
+                    final int _cursorIndexOfName = _cursor.getColumnIndexOrThrow("name");
+                    final int _cursorIndexOfLastName = _cursor.getColumnIndexOrThrow("lastName");
+                    final int _cursorIndexOfAge = _cursor.getColumnIndexOrThrow("ageColumn");
+                    final User _result;
+                    if(_cursor.moveToFirst()) {
+                        _result = new User();
+                        _result.uid = _cursor.getInt(_cursorIndexOfUid);
+                        _result.name = _cursor.getString(_cursorIndexOfName);
+                        final String _tmpLastName;
+                        _tmpLastName = _cursor.getString(_cursorIndexOfLastName);
+                        _result.setLastName(_tmpLastName);
+                        _result.age = _cursor.getInt(_cursorIndexOfAge);
+                    } else {
+                        _result = null;
+                    }
+                    return _result;
+                } finally {
+                    _cursor.close();
+                }
+            }
 
-    @Query("SELECT ageColumn FROM user where uid IN(:ids1) OR uid IN (:ids2) OR uid IN (:ids3)")
-    abstract public List<Integer> getAllAgesAsList(List<Integer> ids1,
-            int[] ids2, int... ids3);
+            @Override
+            protected void finalize() {
+                _statement.release();
+            }
+        }.getLiveData();
+    }
+
+    @Override
+    public LiveData<List<User>> loadUsersByIdsLive(int... ids) {
+        StringBuilder _stringBuilder = StringUtil.newStringBuilder();
+        _stringBuilder.append("SELECT * FROM user where uid IN (");
+        final int _inputSize = ids.length;
+        StringUtil.appendPlaceholders(_stringBuilder, _inputSize);
+        _stringBuilder.append(")");
+        final String _sql = _stringBuilder.toString();
+        final int _argCount = 0 + _inputSize;
+        final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, _argCount);
+        int _argIndex = 1;
+        for (int _item : ids) {
+            _statement.bindLong(_argIndex, _item);
+            _argIndex ++;
+        }
+        return new ComputableLiveData<List<User>>() {
+            private Observer _observer;
+
+            @Override
+            protected List<User> compute() {
+                if (_observer == null) {
+                    _observer = new Observer("user") {
+                        @Override
+                        public void onInvalidated(@NonNull Set<String> tables) {
+                            invalidate();
+                        }
+                    };
+                    __db.getInvalidationTracker().addWeakObserver(_observer);
+                }
+                final Cursor _cursor = __db.query(_statement);
+                try {
+                    final int _cursorIndexOfUid = _cursor.getColumnIndexOrThrow("uid");
+                    final int _cursorIndexOfName = _cursor.getColumnIndexOrThrow("name");
+                    final int _cursorIndexOfLastName = _cursor.getColumnIndexOrThrow("lastName");
+                    final int _cursorIndexOfAge = _cursor.getColumnIndexOrThrow("ageColumn");
+                    final List<User> _result = new ArrayList<User>(_cursor.getCount());
+                    while(_cursor.moveToNext()) {
+                        final User _item_1;
+                        _item_1 = new User();
+                        _item_1.uid = _cursor.getInt(_cursorIndexOfUid);
+                        _item_1.name = _cursor.getString(_cursorIndexOfName);
+                        final String _tmpLastName;
+                        _tmpLastName = _cursor.getString(_cursorIndexOfLastName);
+                        _item_1.setLastName(_tmpLastName);
+                        _item_1.age = _cursor.getInt(_cursorIndexOfAge);
+                        _result.add(_item_1);
+                    }
+                    return _result;
+                } finally {
+                    _cursor.close();
+                }
+            }
+
+            @Override
+            protected void finalize() {
+                _statement.release();
+            }
+        }.getLiveData();
+    }
+
+    @Override
+    public List<Integer> getAllAgesAsList(List<Integer> ids1, int[] ids2, int... ids3) {
+        StringBuilder _stringBuilder = StringUtil.newStringBuilder();
+        _stringBuilder.append("SELECT ageColumn FROM user where uid IN(");
+        final int _inputSize = ids1.size();
+        StringUtil.appendPlaceholders(_stringBuilder, _inputSize);
+        _stringBuilder.append(") OR uid IN (");
+        final int _inputSize_1 = ids2.length;
+        StringUtil.appendPlaceholders(_stringBuilder, _inputSize_1);
+        _stringBuilder.append(") OR uid IN (");
+        final int _inputSize_2 = ids3.length;
+        StringUtil.appendPlaceholders(_stringBuilder, _inputSize_2);
+        _stringBuilder.append(")");
+        final String _sql = _stringBuilder.toString();
+        final int _argCount = 0 + _inputSize + _inputSize_1 + _inputSize_2;
+        final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, _argCount);
+        int _argIndex = 1;
+        for (Integer _item : ids1) {
+            if (_item == null) {
+                _statement.bindNull(_argIndex);
+            } else {
+                _statement.bindLong(_argIndex, _item);
+            }
+            _argIndex ++;
+        }
+        _argIndex = 1 + _inputSize;
+        for (int _item_1 : ids2) {
+            _statement.bindLong(_argIndex, _item_1);
+            _argIndex ++;
+        }
+        _argIndex = 1 + _inputSize + _inputSize_1;
+        for (int _item_2 : ids3) {
+            _statement.bindLong(_argIndex, _item_2);
+            _argIndex ++;
+        }
+        final Cursor _cursor = __db.query(_statement);
+        try {
+            final List<Integer> _result = new ArrayList<Integer>(_cursor.getCount());
+            while(_cursor.moveToNext()) {
+                final Integer _item_3;
+                if (_cursor.isNull(0)) {
+                    _item_3 = null;
+                } else {
+                    _item_3 = _cursor.getInt(0);
+                }
+                _result.add(_item_3);
+            }
+            return _result;
+        } finally {
+            _cursor.close();
+            _statement.release();
+        }
+    }
 }
diff --git a/foo/bar/ComplexDatabase.java b/foo/bar/ComplexDatabase.java
index f35e0b8..cfdc110 100644
--- a/foo/bar/ComplexDatabase.java
+++ b/foo/bar/ComplexDatabase.java
@@ -1,23 +1,99 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
 package foo.bar;
-import android.arch.persistence.room.*;
-import java.util.List;
-@Database(entities = {User.class}, version = 1923)
-abstract class ComplexDatabase extends RoomDatabase {
-    abstract ComplexDao getComplexDao();
+
+import android.arch.persistence.db.SupportSQLiteDatabase;
+import android.arch.persistence.db.SupportSQLiteOpenHelper;
+import android.arch.persistence.db.SupportSQLiteOpenHelper.Callback;
+import android.arch.persistence.db.SupportSQLiteOpenHelper.Configuration;
+import android.arch.persistence.room.DatabaseConfiguration;
+import android.arch.persistence.room.InvalidationTracker;
+import android.arch.persistence.room.RoomOpenHelper;
+import android.arch.persistence.room.RoomOpenHelper.Delegate;
+import android.arch.persistence.room.util.TableInfo;
+import android.arch.persistence.room.util.TableInfo.Column;
+import android.arch.persistence.room.util.TableInfo.ForeignKey;
+import android.arch.persistence.room.util.TableInfo.Index;
+import java.lang.IllegalStateException;
+import java.lang.Override;
+import java.lang.String;
+import java.util.HashMap;
+import java.util.HashSet;
+import javax.annotation.Generated;
+
+@Generated("android.arch.persistence.room.RoomProcessor")
+public class ComplexDatabase_Impl extends ComplexDatabase {
+    private volatile ComplexDao _complexDao;
+
+    protected SupportSQLiteOpenHelper createOpenHelper(DatabaseConfiguration configuration) {
+        final SupportSQLiteOpenHelper.Callback _openCallback = new RoomOpenHelper(configuration, new RoomOpenHelper.Delegate(1923) {
+            public void createAllTables(SupportSQLiteDatabase _db) {
+                _db.execSQL("CREATE TABLE IF NOT EXISTS `User` (`uid` INTEGER NOT NULL, `name` TEXT, `lastName` TEXT, `ageColumn` INTEGER NOT NULL, PRIMARY KEY(`uid`))");
+                _db.execSQL("CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)");
+                _db.execSQL("INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"6773601c5bcf94c71ee4eb0de04f21a4\")");
+            }
+
+            public void dropAllTables(SupportSQLiteDatabase _db) {
+                _db.execSQL("DROP TABLE IF EXISTS `User`");
+            }
+
+            protected void onCreate(SupportSQLiteDatabase _db) {
+                if (mCallbacks != null) {
+                    for (int _i = 0, _size = mCallbacks.size(); _i < _size; _i++) {
+                        mCallbacks.get(_i).onCreate(_db);
+                    }
+                }
+            }
+
+            public void onOpen(SupportSQLiteDatabase _db) {
+                mDatabase = _db;
+                internalInitInvalidationTracker(_db);
+                if (mCallbacks != null) {
+                    for (int _i = 0, _size = mCallbacks.size(); _i < _size; _i++) {
+                        mCallbacks.get(_i).onOpen(_db);
+                    }
+                }
+            }
+
+            protected void validateMigration(SupportSQLiteDatabase _db) {
+                final HashMap<String, TableInfo.Column> _columnsUser = new HashMap<String, TableInfo.Column>(4);
+                _columnsUser.put("uid", new TableInfo.Column("uid", "INTEGER", true, 1));
+                _columnsUser.put("name", new TableInfo.Column("name", "TEXT", false, 0));
+                _columnsUser.put("lastName", new TableInfo.Column("lastName", "TEXT", false, 0));
+                _columnsUser.put("ageColumn", new TableInfo.Column("ageColumn", "INTEGER", true, 0));
+                final HashSet<TableInfo.ForeignKey> _foreignKeysUser = new HashSet<TableInfo.ForeignKey>(0);
+                final HashSet<TableInfo.Index> _indicesUser = new HashSet<TableInfo.Index>(0);
+                final TableInfo _infoUser = new TableInfo("User", _columnsUser, _foreignKeysUser, _indicesUser);
+                final TableInfo _existingUser = TableInfo.read(_db, "User");
+                if (! _infoUser.equals(_existingUser)) {
+                    throw new IllegalStateException("Migration didn't properly handle User(foo.bar.User).\n"
+                            + " Expected:\n" + _infoUser + "\n"
+                            + " Found:\n" + _existingUser);
+                }
+            }
+        }, "6773601c5bcf94c71ee4eb0de04f21a4");
+        final SupportSQLiteOpenHelper.Configuration _sqliteConfig = SupportSQLiteOpenHelper.Configuration.builder(configuration.context)
+                .name(configuration.name)
+                .callback(_openCallback)
+                .build();
+        final SupportSQLiteOpenHelper _helper = configuration.sqliteOpenHelperFactory.create(_sqliteConfig);
+        return _helper;
+    }
+
+    @Override
+    protected InvalidationTracker createInvalidationTracker() {
+        return new InvalidationTracker(this, "User");
+    }
+
+    @Override
+    ComplexDao getComplexDao() {
+        if (_complexDao != null) {
+            return _complexDao;
+        } else {
+            synchronized(this) {
+                if(_complexDao == null) {
+                    _complexDao = new ComplexDao_Impl(this);
+                }
+                return _complexDao;
+            }
+        }
+    }
 }
diff --git a/foo/bar/DeletionDao.java b/foo/bar/DeletionDao.java
index 997f290..067bf67 100644
--- a/foo/bar/DeletionDao.java
+++ b/foo/bar/DeletionDao.java
@@ -1,51 +1,240 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
 package foo.bar;
-import android.arch.persistence.room.*;
+
+import android.arch.persistence.db.SupportSQLiteStatement;
+import android.arch.persistence.room.EntityDeletionOrUpdateAdapter;
+import android.arch.persistence.room.RoomDatabase;
+import android.arch.persistence.room.SharedSQLiteStatement;
+import android.arch.persistence.room.util.StringUtil;
+import java.lang.Override;
+import java.lang.String;
+import java.lang.StringBuilder;
 import java.util.List;
+import javax.annotation.Generated;
 
-@Dao
-abstract interface DeletionDao {
-    @Delete
-    void deleteUser(User user);
-    @Delete
-    void deleteUsers(User user1, List<User> others);
-    @Delete
-    void deleteArrayOfUsers(User[] users);
+@Generated("android.arch.persistence.room.RoomProcessor")
+public class DeletionDao_Impl implements DeletionDao {
+  private final RoomDatabase __db;
 
-    @Delete
-    int deleteUserAndReturnCount(User user);
-    @Delete
-    int deleteUserAndReturnCount(User user1, List<User> others);
-    @Delete
-    int deleteUserAndReturnCount(User[] users);
+  private final EntityDeletionOrUpdateAdapter __deletionAdapterOfUser;
 
-    @Delete
-    int multiPKey(MultiPKeyEntity entity);
+  private final EntityDeletionOrUpdateAdapter __deletionAdapterOfMultiPKeyEntity;
 
-    @Query("DELETE FROM user where uid = :uid")
-    int deleteByUid(int uid);
+  private final EntityDeletionOrUpdateAdapter __deletionAdapterOfBook;
 
-    @Query("DELETE FROM user where uid IN(:uid)")
-    int deleteByUidList(int... uid);
+  private final SharedSQLiteStatement __preparedStmtOfDeleteByUid;
 
-    @Delete
-    void deleteUserAndBook(User user, Book book);
+  private final SharedSQLiteStatement __preparedStmtOfDeleteEverything;
 
-    @Query("DELETE FROM user")
-    int deleteEverything();
+  public DeletionDao_Impl(RoomDatabase __db) {
+    this.__db = __db;
+    this.__deletionAdapterOfUser = new EntityDeletionOrUpdateAdapter<User>(__db) {
+      @Override
+      public String createQuery() {
+        return "DELETE FROM `User` WHERE `uid` = ?";
+      }
+
+      @Override
+      public void bind(SupportSQLiteStatement stmt, User value) {
+        stmt.bindLong(1, value.uid);
+      }
+    };
+    this.__deletionAdapterOfMultiPKeyEntity = new EntityDeletionOrUpdateAdapter<MultiPKeyEntity>(__db) {
+      @Override
+      public String createQuery() {
+        return "DELETE FROM `MultiPKeyEntity` WHERE `name` = ? AND `lastName` = ?";
+      }
+
+      @Override
+      public void bind(SupportSQLiteStatement stmt, MultiPKeyEntity value) {
+        if (value.name == null) {
+          stmt.bindNull(1);
+        } else {
+          stmt.bindString(1, value.name);
+        }
+        if (value.lastName == null) {
+          stmt.bindNull(2);
+        } else {
+          stmt.bindString(2, value.lastName);
+        }
+      }
+    };
+    this.__deletionAdapterOfBook = new EntityDeletionOrUpdateAdapter<Book>(__db) {
+      @Override
+      public String createQuery() {
+        return "DELETE FROM `Book` WHERE `bookId` = ?";
+      }
+
+      @Override
+      public void bind(SupportSQLiteStatement stmt, Book value) {
+        stmt.bindLong(1, value.bookId);
+      }
+    };
+    this.__preparedStmtOfDeleteByUid = new SharedSQLiteStatement(__db) {
+      @Override
+      public String createQuery() {
+        final String _query = "DELETE FROM user where uid = ?";
+        return _query;
+      }
+    };
+    this.__preparedStmtOfDeleteEverything = new SharedSQLiteStatement(__db) {
+      @Override
+      public String createQuery() {
+        final String _query = "DELETE FROM user";
+        return _query;
+      }
+    };
+  }
+
+  @Override
+  public void deleteUser(User user) {
+    __db.beginTransaction();
+    try {
+      __deletionAdapterOfUser.handle(user);
+      __db.setTransactionSuccessful();
+    } finally {
+      __db.endTransaction();
+    }
+  }
+
+  @Override
+  public void deleteUsers(User user1, List<User> others) {
+    __db.beginTransaction();
+    try {
+      __deletionAdapterOfUser.handle(user1);
+      __deletionAdapterOfUser.handleMultiple(others);
+      __db.setTransactionSuccessful();
+    } finally {
+      __db.endTransaction();
+    }
+  }
+
+  @Override
+  public void deleteArrayOfUsers(User[] users) {
+    __db.beginTransaction();
+    try {
+      __deletionAdapterOfUser.handleMultiple(users);
+      __db.setTransactionSuccessful();
+    } finally {
+      __db.endTransaction();
+    }
+  }
+
+  @Override
+  public int deleteUserAndReturnCount(User user) {
+    int _total = 0;
+    __db.beginTransaction();
+    try {
+      _total +=__deletionAdapterOfUser.handle(user);
+      __db.setTransactionSuccessful();
+      return _total;
+    } finally {
+      __db.endTransaction();
+    }
+  }
+
+  @Override
+  public int deleteUserAndReturnCount(User user1, List<User> others) {
+    int _total = 0;
+    __db.beginTransaction();
+    try {
+      _total +=__deletionAdapterOfUser.handle(user1);
+      _total +=__deletionAdapterOfUser.handleMultiple(others);
+      __db.setTransactionSuccessful();
+      return _total;
+    } finally {
+      __db.endTransaction();
+    }
+  }
+
+  @Override
+  public int deleteUserAndReturnCount(User[] users) {
+    int _total = 0;
+    __db.beginTransaction();
+    try {
+      _total +=__deletionAdapterOfUser.handleMultiple(users);
+      __db.setTransactionSuccessful();
+      return _total;
+    } finally {
+      __db.endTransaction();
+    }
+  }
+
+  @Override
+  public int multiPKey(MultiPKeyEntity entity) {
+    int _total = 0;
+    __db.beginTransaction();
+    try {
+      _total +=__deletionAdapterOfMultiPKeyEntity.handle(entity);
+      __db.setTransactionSuccessful();
+      return _total;
+    } finally {
+      __db.endTransaction();
+    }
+  }
+
+  @Override
+  public void deleteUserAndBook(User user, Book book) {
+    __db.beginTransaction();
+    try {
+      __deletionAdapterOfUser.handle(user);
+      __deletionAdapterOfBook.handle(book);
+      __db.setTransactionSuccessful();
+    } finally {
+      __db.endTransaction();
+    }
+  }
+
+  @Override
+  public int deleteByUid(int uid) {
+    final SupportSQLiteStatement _stmt = __preparedStmtOfDeleteByUid.acquire();
+    __db.beginTransaction();
+    try {
+      int _argIndex = 1;
+      _stmt.bindLong(_argIndex, uid);
+      final int _result = _stmt.executeUpdateDelete();
+      __db.setTransactionSuccessful();
+      return _result;
+    } finally {
+      __db.endTransaction();
+      __preparedStmtOfDeleteByUid.release(_stmt);
+    }
+  }
+
+  @Override
+  public int deleteEverything() {
+    final SupportSQLiteStatement _stmt = __preparedStmtOfDeleteEverything.acquire();
+    __db.beginTransaction();
+    try {
+      final int _result = _stmt.executeUpdateDelete();
+      __db.setTransactionSuccessful();
+      return _result;
+    } finally {
+      __db.endTransaction();
+      __preparedStmtOfDeleteEverything.release(_stmt);
+    }
+  }
+
+  @Override
+  public int deleteByUidList(int... uid) {
+    StringBuilder _stringBuilder = StringUtil.newStringBuilder();
+    _stringBuilder.append("DELETE FROM user where uid IN(");
+    final int _inputSize = uid.length;
+    StringUtil.appendPlaceholders(_stringBuilder, _inputSize);
+    _stringBuilder.append(")");
+    final String _sql = _stringBuilder.toString();
+    SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
+    int _argIndex = 1;
+    for (int _item : uid) {
+      _stmt.bindLong(_argIndex, _item);
+      _argIndex ++;
+    }
+    __db.beginTransaction();
+    try {
+      final int _result = _stmt.executeUpdateDelete();
+      __db.setTransactionSuccessful();
+      return _result;
+    } finally {
+      __db.endTransaction();
+    }
+  }
 }
diff --git a/foo/bar/UpdateDao.java b/foo/bar/UpdateDao.java
index 040b5c7..1190a0d 100644
--- a/foo/bar/UpdateDao.java
+++ b/foo/bar/UpdateDao.java
@@ -1,48 +1,240 @@
-/*
- * Copyright (C) 2017 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 foo.bar;
-import android.arch.persistence.room.*;
+
+import android.arch.persistence.db.SupportSQLiteStatement;
+import android.arch.persistence.room.EntityDeletionOrUpdateAdapter;
+import android.arch.persistence.room.RoomDatabase;
+import android.arch.persistence.room.SharedSQLiteStatement;
+import java.lang.Override;
+import java.lang.String;
 import java.util.List;
+import javax.annotation.Generated;
 
-@Dao
-abstract interface UpdateDao {
-    @Update
-    void updateUser(User user);
-    @Update
-    void updateUsers(User user1, List<User> others);
-    @Update
-    void updateArrayOfUsers(User[] users);
+@Generated("android.arch.persistence.room.RoomProcessor")
+public class UpdateDao_Impl implements UpdateDao {
+  private final RoomDatabase __db;
 
-    @Update
-    int updateUserAndReturnCount(User user);
-    @Update
-    int updateUserAndReturnCount(User user1, List<User> others);
-    @Update
-    int updateUserAndReturnCount(User[] users);
+  private final EntityDeletionOrUpdateAdapter __updateAdapterOfUser;
 
-    @Update
-    int multiPKey(MultiPKeyEntity entity);
+  private final EntityDeletionOrUpdateAdapter __updateAdapterOfMultiPKeyEntity;
 
-    @Update
-    void updateUserAndBook(User user, Book book);
+  private final EntityDeletionOrUpdateAdapter __updateAdapterOfBook;
 
-    @Query("UPDATE User SET ageColumn = ageColumn + 1 WHERE uid = :uid")
-    void ageUserByUid(String uid);
+  private final SharedSQLiteStatement __preparedStmtOfAgeUserByUid;
 
-    @Query("UPDATE User SET ageColumn = ageColumn + 1")
-    void ageUserAll();
+  private final SharedSQLiteStatement __preparedStmtOfAgeUserAll;
+
+  public UpdateDao_Impl(RoomDatabase __db) {
+    this.__db = __db;
+    this.__updateAdapterOfUser = new EntityDeletionOrUpdateAdapter<User>(__db) {
+      @Override
+      public String createQuery() {
+        return "UPDATE OR ABORT `User` SET `uid` = ?,`name` = ?,`lastName` = ?,`ageColumn` = ? WHERE `uid` = ?";
+      }
+
+      @Override
+      public void bind(SupportSQLiteStatement stmt, User value) {
+        stmt.bindLong(1, value.uid);
+        if (value.name == null) {
+          stmt.bindNull(2);
+        } else {
+          stmt.bindString(2, value.name);
+        }
+        if (value.getLastName() == null) {
+          stmt.bindNull(3);
+        } else {
+          stmt.bindString(3, value.getLastName());
+        }
+        stmt.bindLong(4, value.age);
+        stmt.bindLong(5, value.uid);
+      }
+    };
+    this.__updateAdapterOfMultiPKeyEntity = new EntityDeletionOrUpdateAdapter<MultiPKeyEntity>(__db) {
+      @Override
+      public String createQuery() {
+        return "UPDATE OR ABORT `MultiPKeyEntity` SET `name` = ?,`lastName` = ? WHERE `name` = ? AND `lastName` = ?";
+      }
+
+      @Override
+      public void bind(SupportSQLiteStatement stmt, MultiPKeyEntity value) {
+        if (value.name == null) {
+          stmt.bindNull(1);
+        } else {
+          stmt.bindString(1, value.name);
+        }
+        if (value.lastName == null) {
+          stmt.bindNull(2);
+        } else {
+          stmt.bindString(2, value.lastName);
+        }
+        if (value.name == null) {
+          stmt.bindNull(3);
+        } else {
+          stmt.bindString(3, value.name);
+        }
+        if (value.lastName == null) {
+          stmt.bindNull(4);
+        } else {
+          stmt.bindString(4, value.lastName);
+        }
+      }
+    };
+    this.__updateAdapterOfBook = new EntityDeletionOrUpdateAdapter<Book>(__db) {
+      @Override
+      public String createQuery() {
+        return "UPDATE OR ABORT `Book` SET `bookId` = ?,`uid` = ? WHERE `bookId` = ?";
+      }
+
+      @Override
+      public void bind(SupportSQLiteStatement stmt, Book value) {
+        stmt.bindLong(1, value.bookId);
+        stmt.bindLong(2, value.uid);
+        stmt.bindLong(3, value.bookId);
+      }
+    };
+    this.__preparedStmtOfAgeUserByUid = new SharedSQLiteStatement(__db) {
+      @Override
+      public String createQuery() {
+        final String _query = "UPDATE User SET ageColumn = ageColumn + 1 WHERE uid = ?";
+        return _query;
+      }
+    };
+    this.__preparedStmtOfAgeUserAll = new SharedSQLiteStatement(__db) {
+      @Override
+      public String createQuery() {
+        final String _query = "UPDATE User SET ageColumn = ageColumn + 1";
+        return _query;
+      }
+    };
+  }
+
+  @Override
+  public void updateUser(User user) {
+    __db.beginTransaction();
+    try {
+      __updateAdapterOfUser.handle(user);
+      __db.setTransactionSuccessful();
+    } finally {
+      __db.endTransaction();
+    }
+  }
+
+  @Override
+  public void updateUsers(User user1, List<User> others) {
+    __db.beginTransaction();
+    try {
+      __updateAdapterOfUser.handle(user1);
+      __updateAdapterOfUser.handleMultiple(others);
+      __db.setTransactionSuccessful();
+    } finally {
+      __db.endTransaction();
+    }
+  }
+
+  @Override
+  public void updateArrayOfUsers(User[] users) {
+    __db.beginTransaction();
+    try {
+      __updateAdapterOfUser.handleMultiple(users);
+      __db.setTransactionSuccessful();
+    } finally {
+      __db.endTransaction();
+    }
+  }
+
+  @Override
+  public int updateUserAndReturnCount(User user) {
+    int _total = 0;
+    __db.beginTransaction();
+    try {
+      _total +=__updateAdapterOfUser.handle(user);
+      __db.setTransactionSuccessful();
+      return _total;
+    } finally {
+      __db.endTransaction();
+    }
+  }
+
+  @Override
+  public int updateUserAndReturnCount(User user1, List<User> others) {
+    int _total = 0;
+    __db.beginTransaction();
+    try {
+      _total +=__updateAdapterOfUser.handle(user1);
+      _total +=__updateAdapterOfUser.handleMultiple(others);
+      __db.setTransactionSuccessful();
+      return _total;
+    } finally {
+      __db.endTransaction();
+    }
+  }
+
+  @Override
+  public int updateUserAndReturnCount(User[] users) {
+    int _total = 0;
+    __db.beginTransaction();
+    try {
+      _total +=__updateAdapterOfUser.handleMultiple(users);
+      __db.setTransactionSuccessful();
+      return _total;
+    } finally {
+      __db.endTransaction();
+    }
+  }
+
+  @Override
+  public int multiPKey(MultiPKeyEntity entity) {
+    int _total = 0;
+    __db.beginTransaction();
+    try {
+      _total +=__updateAdapterOfMultiPKeyEntity.handle(entity);
+      __db.setTransactionSuccessful();
+      return _total;
+    } finally {
+      __db.endTransaction();
+    }
+  }
+
+  @Override
+  public void updateUserAndBook(User user, Book book) {
+    __db.beginTransaction();
+    try {
+      __updateAdapterOfUser.handle(user);
+      __updateAdapterOfBook.handle(book);
+      __db.setTransactionSuccessful();
+    } finally {
+      __db.endTransaction();
+    }
+  }
+
+  @Override
+  public void ageUserByUid(String uid) {
+    final SupportSQLiteStatement _stmt = __preparedStmtOfAgeUserByUid.acquire();
+    __db.beginTransaction();
+    try {
+      int _argIndex = 1;
+      if (uid == null) {
+        _stmt.bindNull(_argIndex);
+      } else {
+        _stmt.bindString(_argIndex, uid);
+      }
+      _stmt.executeUpdateDelete();
+      __db.setTransactionSuccessful();
+    } finally {
+      __db.endTransaction();
+      __preparedStmtOfAgeUserByUid.release(_stmt);
+    }
+  }
+
+  @Override
+  public void ageUserAll() {
+    final SupportSQLiteStatement _stmt = __preparedStmtOfAgeUserAll.acquire();
+    __db.beginTransaction();
+    try {
+      _stmt.executeUpdateDelete();
+      __db.setTransactionSuccessful();
+    } finally {
+      __db.endTransaction();
+      __preparedStmtOfAgeUserAll.release(_stmt);
+    }
+  }
 }
diff --git a/foo/bar/WriterDao.java b/foo/bar/WriterDao.java
index e122479..cfad046 100644
--- a/foo/bar/WriterDao.java
+++ b/foo/bar/WriterDao.java
@@ -15,17 +15,131 @@
  */
 
 package foo.bar;
-import android.arch.persistence.room.*;
-import java.util.List;
 
-@Dao
-abstract interface WriterDao {
-    @Insert
-    void insertUser(User user);
-    @Insert
-    void insertUsers(User user1, List<User> others);
-    @Insert(onConflict=OnConflictStrategy.REPLACE)
-    void insertUsers(User[] users);
-    @Insert
-    void insertUserAndBook(User user, Book book);
+import android.arch.persistence.db.SupportSQLiteStatement;
+import android.arch.persistence.room.EntityInsertionAdapter;
+import android.arch.persistence.room.RoomDatabase;
+
+import java.lang.Override;
+import java.lang.String;
+import java.util.List;
+import javax.annotation.Generated;
+
+@Generated("android.arch.persistence.room.RoomProcessor")
+public class WriterDao_Impl implements WriterDao {
+    private final RoomDatabase __db;
+
+    private final EntityInsertionAdapter __insertionAdapterOfUser;
+
+    private final EntityInsertionAdapter __insertionAdapterOfUser_1;
+
+    private final EntityInsertionAdapter __insertionAdapterOfBook;
+
+    public WriterDao_Impl(RoomDatabase __db) {
+        this.__db = __db;
+        this.__insertionAdapterOfUser = new EntityInsertionAdapter<User>(__db) {
+            @Override
+            public String createQuery() {
+                return "INSERT OR ABORT INTO `User`(`uid`,`name`,`lastName`,`ageColumn`) VALUES"
+                        + " (?,?,?,?)";
+            }
+
+            @Override
+            public void bind(SupportSQLiteStatement stmt, User value) {
+                stmt.bindLong(1, value.uid);
+                if (value.name == null) {
+                    stmt.bindNull(2);
+                } else {
+                    stmt.bindString(2, value.name);
+                }
+                if (value.getLastName() == null) {
+                    stmt.bindNull(3);
+                } else {
+                    stmt.bindString(3, value.getLastName());
+                }
+                stmt.bindLong(4, value.age);
+            }
+        };
+        this.__insertionAdapterOfUser_1 = new EntityInsertionAdapter<User>(__db) {
+            @Override
+            public String createQuery() {
+                return "INSERT OR REPLACE INTO `User`(`uid`,`name`,`lastName`,`ageColumn`) VALUES"
+                        + " (?,?,?,?)";
+            }
+
+            @Override
+            public void bind(SupportSQLiteStatement stmt, User value) {
+                stmt.bindLong(1, value.uid);
+                if (value.name == null) {
+                    stmt.bindNull(2);
+                } else {
+                    stmt.bindString(2, value.name);
+                }
+                if (value.getLastName() == null) {
+                    stmt.bindNull(3);
+                } else {
+                    stmt.bindString(3, value.getLastName());
+                }
+                stmt.bindLong(4, value.age);
+            }
+        };
+        this.__insertionAdapterOfBook = new EntityInsertionAdapter<Book>(__db) {
+            @Override
+            public String createQuery() {
+                return "INSERT OR ABORT INTO `Book`(`bookId`,`uid`) VALUES (?,?)";
+            }
+
+            @Override
+            public void bind(SupportSQLiteStatement stmt, Book value) {
+                stmt.bindLong(1, value.bookId);
+                stmt.bindLong(2, value.uid);
+            }
+        };
+    }
+
+    @Override
+    public void insertUser(User user) {
+        __db.beginTransaction();
+        try {
+            __insertionAdapterOfUser.insert(user);
+            __db.setTransactionSuccessful();
+        } finally {
+            __db.endTransaction();
+        }
+    }
+
+    @Override
+    public void insertUsers(User user1, List<User> others) {
+        __db.beginTransaction();
+        try {
+            __insertionAdapterOfUser.insert(user1);
+            __insertionAdapterOfUser.insert(others);
+            __db.setTransactionSuccessful();
+        } finally {
+            __db.endTransaction();
+        }
+    }
+
+    @Override
+    public void insertUsers(User[] users) {
+        __db.beginTransaction();
+        try {
+            __insertionAdapterOfUser_1.insert(users);
+            __db.setTransactionSuccessful();
+        } finally {
+            __db.endTransaction();
+        }
+    }
+
+    @Override
+    public void insertUserAndBook(User user, Book book) {
+        __db.beginTransaction();
+        try {
+            __insertionAdapterOfUser.insert(user);
+            __insertionAdapterOfBook.insert(book);
+            __db.setTransactionSuccessful();
+        } finally {
+            __db.endTransaction();
+        }
+    }
 }
diff --git a/java/lang/Boolean.java b/java/lang/Boolean.java
index 802d910..397bf09 100644
--- a/java/lang/Boolean.java
+++ b/java/lang/Boolean.java
@@ -61,6 +61,8 @@
      * @since   JDK1.1
      */
     @SuppressWarnings("unchecked")
+    // Android-changed: Avoid use of removed Class.getPrimitiveClass method.
+    // public static final Class<Boolean> TYPE = (Class<Boolean>) Class.getPrimitiveClass("boolean");
     public static final Class<Boolean> TYPE = (Class<Boolean>) boolean[].class.getComponentType();
 
     /**
diff --git a/java/lang/Byte.java b/java/lang/Byte.java
index d0031d0..2333afd 100644
--- a/java/lang/Byte.java
+++ b/java/lang/Byte.java
@@ -60,6 +60,8 @@
      * {@code byte}.
      */
     @SuppressWarnings("unchecked")
+    // Android-changed: Avoid use of removed Class.getPrimitiveClass method.
+    // public static final Class<Byte>     TYPE = (Class<Byte>) Class.getPrimitiveClass("byte");
     public static final Class<Byte>     TYPE = (Class<Byte>) byte[].class.getComponentType();
 
     /**
diff --git a/java/lang/Character.java b/java/lang/Character.java
index fb0d576..7615dab 100644
--- a/java/lang/Character.java
+++ b/java/lang/Character.java
@@ -173,6 +173,8 @@
      * @since   1.1
      */
     @SuppressWarnings("unchecked")
+    // Android-changed: Avoid use of removed Class.getPrimitiveClass method.
+    // public static final Class<Character> TYPE = (Class<Character>) Class.getPrimitiveClass("char");
     public static final Class<Character> TYPE = (Class<Character>) char[].class.getComponentType();
 
     /*
diff --git a/java/lang/Double.java b/java/lang/Double.java
index 2bc2bf3..2721a84 100644
--- a/java/lang/Double.java
+++ b/java/lang/Double.java
@@ -137,6 +137,8 @@
      * @since JDK1.1
      */
     @SuppressWarnings("unchecked")
+    // Android-changed: Avoid use of removed Class.getPrimitiveClass method.
+    // public static final Class<Double>   TYPE = (Class<Double>) Class.getPrimitiveClass("double");
     public static final Class<Double>   TYPE = (Class<Double>) double[].class.getComponentType();
 
     /**
diff --git a/java/lang/Float.java b/java/lang/Float.java
index 32bd625..6a2b933 100644
--- a/java/lang/Float.java
+++ b/java/lang/Float.java
@@ -135,6 +135,8 @@
      * @since JDK1.1
      */
     @SuppressWarnings("unchecked")
+    // Android-changed: Avoid use of removed Class.getPrimitiveClass method.
+    // public static final Class<Float> TYPE = (Class<Float>) Class.getPrimitiveClass("float");
     public static final Class<Float> TYPE = (Class<Float>) float[].class.getComponentType();
 
     /**
diff --git a/java/lang/Integer.java b/java/lang/Integer.java
index f742505..c63b0d5 100644
--- a/java/lang/Integer.java
+++ b/java/lang/Integer.java
@@ -70,6 +70,8 @@
      * @since   JDK1.1
      */
     @SuppressWarnings("unchecked")
+    // Android-changed: Avoid use of removed Class.getPrimitiveClass method.
+    // public static final Class<Integer>  TYPE = (Class<Integer>) Class.getPrimitiveClass("int");
     public static final Class<Integer>  TYPE = (Class<Integer>) int[].class.getComponentType();
 
     /**
@@ -315,7 +317,8 @@
 
         formatUnsignedInt(val, shift, buf, 0, chars);
 
-        // Use special constructor which takes over "buf".
+        // Android-changed: Use regular constructor instead of one which takes over "buf".
+        // return new String(buf, true);
         return new String(buf);
     }
 
@@ -340,8 +343,10 @@
         return charPos;
     }
 
+    // BEGIN Android-changed: Cache the toString() result for small values.
     private static final String[] SMALL_NEG_VALUES  = new String[100];
     private static final String[] SMALL_NONNEG_VALUES = new String[100];
+    // END Android-changed: Cache the toString() result for small values.
 
     final static char [] DigitTens = {
         '0', '0', '0', '0', '0', '0', '0', '0', '0', '0',
@@ -402,7 +407,8 @@
         if (i == Integer.MIN_VALUE)
             return "-2147483648";
 
-        // Android-changed: cache the string literal for small values.
+        // BEGIN Android-changed: Cache the String for small values.
+        // int size = (i < 0) ? stringSize(-i) + 1 : stringSize(i);
         boolean negative = i < 0;
         boolean small = negative ? i > -100 : i < 100;
         if (small) {
@@ -424,10 +430,12 @@
             }
             return smallValues[i];
         }
-
         int size = negative ? stringSize(-i) + 1 : stringSize(i);
+        // END Android-changed: Cache the String for small values.
         char[] buf = new char[size];
         getChars(i, size, buf);
+        // Android-changed: Use regular constructor instead of one which takes over "buf".
+        // return new String(buf, true);
         return new String(buf);
     }
 
@@ -567,6 +575,7 @@
          */
 
         if (s == null) {
+            // Android-changed: Improve exception message for parseInt.
             throw new NumberFormatException("s == null");
         }
 
diff --git a/java/lang/Long.java b/java/lang/Long.java
index 3f383b4..c752957 100644
--- a/java/lang/Long.java
+++ b/java/lang/Long.java
@@ -72,6 +72,8 @@
      * @since   JDK1.1
      */
     @SuppressWarnings("unchecked")
+    // Android-changed: Avoid use of removed Class.getPrimitiveClass method.
+    // public static final Class<Long>     TYPE = (Class<Long>) Class.getPrimitiveClass("long");
     public static final Class<Long>     TYPE = (Class<Long>) long[].class.getComponentType();
 
     /**
@@ -357,6 +359,8 @@
         char[] buf = new char[chars];
 
         formatUnsignedLong(val, shift, buf, 0, chars);
+        // Android-changed: Use regular constructor instead of one which takes over "buf".
+        // return new String(buf, true);
         return new String(buf);
     }
 
@@ -397,6 +401,8 @@
         int size = (i < 0) ? stringSize(-i) + 1 : stringSize(i);
         char[] buf = new char[size];
         getChars(i, size, buf);
+        // Android-changed: Use regular constructor instead of one which takes over "buf".
+        // return new String(buf, true);
         return new String(buf);
     }
 
diff --git a/java/lang/Short.java b/java/lang/Short.java
index 0031711..0f600a8 100644
--- a/java/lang/Short.java
+++ b/java/lang/Short.java
@@ -60,6 +60,8 @@
      * {@code short}.
      */
     @SuppressWarnings("unchecked")
+    // Android-changed: Avoid use of removed Class.getPrimitiveClass method.
+    // public static final Class<Short>    TYPE = (Class<Short>) Class.getPrimitiveClass("short");
     public static final Class<Short>    TYPE = (Class<Short>) short[].class.getComponentType();
 
     /**
diff --git a/java/lang/String.java b/java/lang/String.java
index 4cefed8..7e82347 100644
--- a/java/lang/String.java
+++ b/java/lang/String.java
@@ -543,8 +543,10 @@
         throw new UnsupportedOperationException("Use StringFactory instead.");
     }
 
+    // Android-removed: Unused package-private constructor String(char[] value, boolean share).
 
-    // BEGIN Android-changed: Deprecated & unsupported as all calls are intercepted by the runtime.
+    // BEGIN Android-added: Constructor for internal use.
+    // Not implemented in java as all calls are intercepted by the runtime.
     /**
      * Package private constructor
      *
@@ -554,7 +556,7 @@
     String(int offset, int count, char[] value) {
         throw new UnsupportedOperationException("Use StringFactory instead.");
     }
-    // END Android-changed: Deprecated & unsupported as all calls are intercepted by the runtime.
+    // END Android-added: Constructor for internal use.
 
     /**
      * Returns the length of this string.
@@ -1559,12 +1561,6 @@
         }
     }
 
-    // BEGIN Android-added: Native method to access char storage managed by runtime.
-    // TODO(b/67411061): This seems to be unused, see whether we can remove it.
-    @FastNative
-    private native int fastIndexOf(int c, int start);
-    // END Android-added: Native method to access char storage managed by runtime.
-
     /**
      * Handles (rare) calls of indexOf with a supplementary character.
      */
diff --git a/java/lang/Void.java b/java/lang/Void.java
index 8311ea8..14268c7 100644
--- a/java/lang/Void.java
+++ b/java/lang/Void.java
@@ -44,24 +44,13 @@
      * The {@code Class} object representing the pseudo-type corresponding to
      * the keyword {@code void}.
      */
+    // BEGIN Android-changed: Avoid use of removed Class.getPrimitiveClass method.
+    // public static final Class<Void> TYPE = (Class<Void>) Class.getPrimitiveClass("void");
     public static final Class<Void> TYPE = lookupType();
 
-    // Android-changed: Upstream code would use reflection to establish the value of "void.class".
-    // ART makes a native call instead because the reflection approach could lead to initialization
-    // of TYPE with the current, i.e. uninitialized, value of TYPE due to other Android changes.
     @dalvik.annotation.optimization.FastNative
     private static native Class<Void> lookupType();
-    /*
-    @SuppressWarnings("unchecked")
-    private static Class<Void> lookupType() {
-        try {
-            Method method = Runnable.class.getMethod("run", EmptyArray.CLASS);
-            return (Class<Void>) method.getReturnType();
-        } catch (Exception e) {
-            throw new AssertionError(e);
-        }
-    }
-    */
+    // END Android-changed: Avoid use of removed Class.getPrimitiveClass method.
 
     /*
      * The Void class cannot be instantiated.
diff --git a/java/lang/invoke/ArrayElementVarHandle.java b/java/lang/invoke/ArrayElementVarHandle.java
new file mode 100644
index 0000000..e315ba7
--- /dev/null
+++ b/java/lang/invoke/ArrayElementVarHandle.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2017 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 java.lang.invoke;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+
+/**
+ * A VarHandle to access array elements.
+ * @hide
+ */
+final class ArrayElementVarHandle extends VarHandle {
+    private ArrayElementVarHandle(Class<?> arrayClass) {
+        super(arrayClass.getComponentType(), arrayClass, false /* isFinal */,
+              arrayClass, int.class);
+    }
+
+    static ArrayElementVarHandle create(Class<?> arrayClass) {
+        return new ArrayElementVarHandle(arrayClass);
+    }
+}
diff --git a/java/lang/invoke/ByteArrayVarHandle.java b/java/lang/invoke/ByteArrayVarHandle.java
new file mode 100644
index 0000000..4237058
--- /dev/null
+++ b/java/lang/invoke/ByteArrayVarHandle.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2017 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 java.lang.invoke;
+
+import java.nio.ByteOrder;
+
+/**
+ * A VarHandle to access byte array elements as an array of primitive types.
+ * @hide
+ */
+final class ByteArrayVarHandle extends VarHandle {
+    private ByteOrder byteOrder;
+
+    private ByteArrayVarHandle(Class<?> arrayClass, ByteOrder byteOrder) {
+        super(arrayClass.getComponentType(), byte[].class, false /* isFinal */,
+              byte[].class, int.class);
+        this.byteOrder = byteOrder;
+    }
+
+    static ByteArrayVarHandle create(Class<?> arrayClass, ByteOrder byteOrder) {
+        return new ByteArrayVarHandle(arrayClass, byteOrder);
+    }
+}
diff --git a/java/lang/invoke/ByteBufferViewVarHandle.java b/java/lang/invoke/ByteBufferViewVarHandle.java
new file mode 100644
index 0000000..74f3b0c
--- /dev/null
+++ b/java/lang/invoke/ByteBufferViewVarHandle.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2017 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 java.lang.invoke;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/**
+ * A VarHandle to access byte array elements as an array of primitive types.
+ * @hide
+ */
+final class ByteBufferViewVarHandle extends VarHandle {
+    private ByteOrder byteOrder;
+
+    private ByteBufferViewVarHandle(Class<?> arrayClass, ByteOrder byteOrder) {
+        super(arrayClass.getComponentType(), byte[].class, false /* isFinal */,
+              ByteBuffer.class, int.class);
+        this.byteOrder = byteOrder;
+    }
+
+    static ByteBufferViewVarHandle create(Class<?> arrayClass, ByteOrder byteOrder) {
+        return new ByteBufferViewVarHandle(arrayClass, byteOrder);
+    }
+}
diff --git a/java/lang/invoke/CallSite.java b/java/lang/invoke/CallSite.java
index 85b4bb9..1ff1eb8 100644
--- a/java/lang/invoke/CallSite.java
+++ b/java/lang/invoke/CallSite.java
@@ -25,363 +25,15 @@
 
 package java.lang.invoke;
 
-// Android-changed: Not using Empty
-//import sun.invoke.empty.Empty;
-import static java.lang.invoke.MethodHandleStatics.*;
-import static java.lang.invoke.MethodHandles.Lookup.IMPL_LOOKUP;
-
-/**
- * A {@code CallSite} is a holder for a variable {@link MethodHandle},
- * which is called its {@code target}.
- * An {@code invokedynamic} instruction linked to a {@code CallSite} delegates
- * all calls to the site's current target.
- * A {@code CallSite} may be associated with several {@code invokedynamic}
- * instructions, or it may be "free floating", associated with none.
- * In any case, it may be invoked through an associated method handle
- * called its {@linkplain #dynamicInvoker dynamic invoker}.
- * <p>
- * {@code CallSite} is an abstract class which does not allow
- * direct subclassing by users.  It has three immediate,
- * concrete subclasses that may be either instantiated or subclassed.
- * <ul>
- * <li>If a mutable target is not required, an {@code invokedynamic} instruction
- * may be permanently bound by means of a {@linkplain ConstantCallSite constant call site}.
- * <li>If a mutable target is required which has volatile variable semantics,
- * because updates to the target must be immediately and reliably witnessed by other threads,
- * a {@linkplain VolatileCallSite volatile call site} may be used.
- * <li>Otherwise, if a mutable target is required,
- * a {@linkplain MutableCallSite mutable call site} may be used.
- * </ul>
- * <p>
- * A non-constant call site may be <em>relinked</em> by changing its target.
- * The new target must have the same {@linkplain MethodHandle#type() type}
- * as the previous target.
- * Thus, though a call site can be relinked to a series of
- * successive targets, it cannot change its type.
- * <p>
- * Here is a sample use of call sites and bootstrap methods which links every
- * dynamic call site to print its arguments:
-<blockquote><pre>{@code
-static void test() throws Throwable {
-    // THE FOLLOWING LINE IS PSEUDOCODE FOR A JVM INSTRUCTION
-    InvokeDynamic[#bootstrapDynamic].baz("baz arg", 2, 3.14);
-}
-private static void printArgs(Object... args) {
-  System.out.println(java.util.Arrays.deepToString(args));
-}
-private static final MethodHandle printArgs;
-static {
-  MethodHandles.Lookup lookup = MethodHandles.lookup();
-  Class thisClass = lookup.lookupClass();  // (who am I?)
-  printArgs = lookup.findStatic(thisClass,
-      "printArgs", MethodType.methodType(void.class, Object[].class));
-}
-private static CallSite bootstrapDynamic(MethodHandles.Lookup caller, String name, MethodType type) {
-  // ignore caller and name, but match the type:
-  return new ConstantCallSite(printArgs.asType(type));
-}
-}</pre></blockquote>
- * @author John Rose, JSR 292 EG
- */
 abstract
 public class CallSite {
-    // Android-changed: not used.
-    // static { MethodHandleImpl.initStatics(); }
 
-    // The actual payload of this call site:
-    /*package-private*/
-    MethodHandle target;    // Note: This field is known to the JVM.  Do not change.
+    public MethodType type() { return null; }
 
-    /**
-     * Make a blank call site object with the given method type.
-     * An initial target method is supplied which will throw
-     * an {@link IllegalStateException} if called.
-     * <p>
-     * Before this {@code CallSite} object is returned from a bootstrap method,
-     * it is usually provided with a more useful target method,
-     * via a call to {@link CallSite#setTarget(MethodHandle) setTarget}.
-     * @throws NullPointerException if the proposed type is null
-     */
-    /*package-private*/
-    CallSite(MethodType type) {
-        // Android-changed: No cache for these so create uninitializedCallSite target here using
-        // method handle transformations to create a method handle that has the expected method
-        // type but throws an IllegalStateException.
-        // target = makeUninitializedCallSite(type);
-        this.target = MethodHandles.throwException(type.returnType(), IllegalStateException.class);
-        this.target = MethodHandles.insertArguments(
-            this.target, 0, new IllegalStateException("uninitialized call site"));
-        if (type.parameterCount() > 0) {
-            this.target = MethodHandles.dropArguments(this.target, 0, type.ptypes());
-        }
-
-        // Android-changed: Using initializer method for GET_TARGET
-        // rather than complex static initializer.
-        initializeGetTarget();
-    }
-
-    /**
-     * Make a call site object equipped with an initial target method handle.
-     * @param target the method handle which will be the initial target of the call site
-     * @throws NullPointerException if the proposed target is null
-     */
-    /*package-private*/
-    CallSite(MethodHandle target) {
-        target.type();  // null check
-        this.target = target;
-
-        // Android-changed: Using initializer method for GET_TARGET
-        // rather than complex static initializer.
-        initializeGetTarget();
-    }
-
-    /**
-     * Make a call site object equipped with an initial target method handle.
-     * @param targetType the desired type of the call site
-     * @param createTargetHook a hook which will bind the call site to the target method handle
-     * @throws WrongMethodTypeException if the hook cannot be invoked on the required arguments,
-     *         or if the target returned by the hook is not of the given {@code targetType}
-     * @throws NullPointerException if the hook returns a null value
-     * @throws ClassCastException if the hook returns something other than a {@code MethodHandle}
-     * @throws Throwable anything else thrown by the hook function
-     */
-    /*package-private*/
-    CallSite(MethodType targetType, MethodHandle createTargetHook) throws Throwable {
-        this(targetType);
-        ConstantCallSite selfCCS = (ConstantCallSite) this;
-        MethodHandle boundTarget = (MethodHandle) createTargetHook.invokeWithArguments(selfCCS);
-        checkTargetChange(this.target, boundTarget);
-        this.target = boundTarget;
-
-        // Android-changed: Using initializer method for GET_TARGET
-        // rather than complex static initializer.
-        initializeGetTarget();
-    }
-
-    /**
-     * Returns the type of this call site's target.
-     * Although targets may change, any call site's type is permanent, and can never change to an unequal type.
-     * The {@code setTarget} method enforces this invariant by refusing any new target that does
-     * not have the previous target's type.
-     * @return the type of the current target, which is also the type of any future target
-     */
-    public MethodType type() {
-        // warning:  do not call getTarget here, because CCS.getTarget can throw IllegalStateException
-        return target.type();
-    }
-
-    /**
-     * Returns the target method of the call site, according to the
-     * behavior defined by this call site's specific class.
-     * The immediate subclasses of {@code CallSite} document the
-     * class-specific behaviors of this method.
-     *
-     * @return the current linkage state of the call site, its target method handle
-     * @see ConstantCallSite
-     * @see VolatileCallSite
-     * @see #setTarget
-     * @see ConstantCallSite#getTarget
-     * @see MutableCallSite#getTarget
-     * @see VolatileCallSite#getTarget
-     */
     public abstract MethodHandle getTarget();
 
-    /**
-     * Updates the target method of this call site, according to the
-     * behavior defined by this call site's specific class.
-     * The immediate subclasses of {@code CallSite} document the
-     * class-specific behaviors of this method.
-     * <p>
-     * The type of the new target must be {@linkplain MethodType#equals equal to}
-     * the type of the old target.
-     *
-     * @param newTarget the new target
-     * @throws NullPointerException if the proposed new target is null
-     * @throws WrongMethodTypeException if the proposed new target
-     *         has a method type that differs from the previous target
-     * @see CallSite#getTarget
-     * @see ConstantCallSite#setTarget
-     * @see MutableCallSite#setTarget
-     * @see VolatileCallSite#setTarget
-     */
     public abstract void setTarget(MethodHandle newTarget);
 
-    void checkTargetChange(MethodHandle oldTarget, MethodHandle newTarget) {
-        MethodType oldType = oldTarget.type();
-        MethodType newType = newTarget.type();  // null check!
-        if (!newType.equals(oldType))
-            throw wrongTargetType(newTarget, oldType);
-    }
-
-    private static WrongMethodTypeException wrongTargetType(MethodHandle target, MethodType type) {
-        return new WrongMethodTypeException(String.valueOf(target)+" should be of type "+type);
-    }
-
-    /**
-     * Produces a method handle equivalent to an invokedynamic instruction
-     * which has been linked to this call site.
-     * <p>
-     * This method is equivalent to the following code:
-     * <blockquote><pre>{@code
-     * MethodHandle getTarget, invoker, result;
-     * getTarget = MethodHandles.publicLookup().bind(this, "getTarget", MethodType.methodType(MethodHandle.class));
-     * invoker = MethodHandles.exactInvoker(this.type());
-     * result = MethodHandles.foldArguments(invoker, getTarget)
-     * }</pre></blockquote>
-     *
-     * @return a method handle which always invokes this call site's current target
-     */
     public abstract MethodHandle dynamicInvoker();
 
-    /*non-public*/ MethodHandle makeDynamicInvoker() {
-        // Android-changed: Use bindTo() rather than bindArgumentL() (not implemented).
-        MethodHandle getTarget = GET_TARGET.bindTo(this);
-        MethodHandle invoker = MethodHandles.exactInvoker(this.type());
-        return MethodHandles.foldArguments(invoker, getTarget);
-    }
-
-    // Android-changed: no longer final. GET_TARGET assigned in initializeGetTarget().
-    private static MethodHandle GET_TARGET = null;
-
-    private void initializeGetTarget() {
-        // Android-changed: moved from static initializer for
-        // GET_TARGET to avoid issues with running early. Called from
-        // constructors. CallSite creation is not performance critical.
-        synchronized (CallSite.class) {
-            if (GET_TARGET == null) {
-                try {
-                    GET_TARGET = IMPL_LOOKUP.
-                            findVirtual(CallSite.class, "getTarget",
-                                        MethodType.methodType(MethodHandle.class));
-                } catch (ReflectiveOperationException e) {
-                    throw new InternalError(e);
-                }
-            }
-        }
-    }
-
-    // Android-changed: not used.
-    // /** This guy is rolled into the default target if a MethodType is supplied to the constructor. */
-    // /*package-private*/
-    // static Empty uninitializedCallSite() {
-    //     throw new IllegalStateException("uninitialized call site");
-    // }
-
-    // unsafe stuff:
-    private static final long TARGET_OFFSET;
-    static {
-        try {
-            TARGET_OFFSET = UNSAFE.objectFieldOffset(CallSite.class.getDeclaredField("target"));
-        } catch (Exception ex) { throw new Error(ex); }
-    }
-
-    /*package-private*/
-    void setTargetNormal(MethodHandle newTarget) {
-        // Android-changed: Set value directly.
-        // MethodHandleNatives.setCallSiteTargetNormal(this, newTarget);
-        target = newTarget;
-    }
-    /*package-private*/
-    MethodHandle getTargetVolatile() {
-        return (MethodHandle) UNSAFE.getObjectVolatile(this, TARGET_OFFSET);
-    }
-    /*package-private*/
-    void setTargetVolatile(MethodHandle newTarget) {
-        // Android-changed: Set value directly.
-        // MethodHandleNatives.setCallSiteTargetVolatile(this, newTarget);
-        UNSAFE.putObjectVolatile(this, TARGET_OFFSET, newTarget);
-    }
-
-    // Android-changed: not used.
-    // this implements the upcall from the JVM, MethodHandleNatives.makeDynamicCallSite:
-    // static CallSite makeSite(MethodHandle bootstrapMethod,
-    //                          // Callee information:
-    //                          String name, MethodType type,
-    //                          // Extra arguments for BSM, if any:
-    //                          Object info,
-    //                          // Caller information:
-    //                          Class<?> callerClass) {
-    //     MethodHandles.Lookup caller = IMPL_LOOKUP.in(callerClass);
-    //     CallSite site;
-    //     try {
-    //         Object binding;
-    //         info = maybeReBox(info);
-    //         if (info == null) {
-    //             binding = bootstrapMethod.invoke(caller, name, type);
-    //         } else if (!info.getClass().isArray()) {
-    //             binding = bootstrapMethod.invoke(caller, name, type, info);
-    //         } else {
-    //             Object[] argv = (Object[]) info;
-    //             maybeReBoxElements(argv);
-    //             switch (argv.length) {
-    //             case 0:
-    //                 binding = bootstrapMethod.invoke(caller, name, type);
-    //                 break;
-    //             case 1:
-    //                 binding = bootstrapMethod.invoke(caller, name, type,
-    //                                                  argv[0]);
-    //                 break;
-    //             case 2:
-    //                 binding = bootstrapMethod.invoke(caller, name, type,
-    //                                                  argv[0], argv[1]);
-    //                 break;
-    //             case 3:
-    //                 binding = bootstrapMethod.invoke(caller, name, type,
-    //                                                  argv[0], argv[1], argv[2]);
-    //                 break;
-    //             case 4:
-    //                 binding = bootstrapMethod.invoke(caller, name, type,
-    //                                                  argv[0], argv[1], argv[2], argv[3]);
-    //                 break;
-    //             case 5:
-    //                 binding = bootstrapMethod.invoke(caller, name, type,
-    //                                                  argv[0], argv[1], argv[2], argv[3], argv[4]);
-    //                 break;
-    //             case 6:
-    //                 binding = bootstrapMethod.invoke(caller, name, type,
-    //                                                  argv[0], argv[1], argv[2], argv[3], argv[4], argv[5]);
-    //                 break;
-    //             default:
-    //                 final int NON_SPREAD_ARG_COUNT = 3;  // (caller, name, type)
-    //                 if (NON_SPREAD_ARG_COUNT + argv.length > MethodType.MAX_MH_ARITY)
-    //                     throw new BootstrapMethodError("too many bootstrap method arguments");
-    //                 MethodType bsmType = bootstrapMethod.type();
-    //                 MethodType invocationType = MethodType.genericMethodType(NON_SPREAD_ARG_COUNT + argv.length);
-    //                 MethodHandle typedBSM = bootstrapMethod.asType(invocationType);
-    //                 MethodHandle spreader = invocationType.invokers().spreadInvoker(NON_SPREAD_ARG_COUNT);
-    //                 binding = spreader.invokeExact(typedBSM, (Object)caller, (Object)name, (Object)type, argv);
-    //             }
-    //         }
-    //         //System.out.println("BSM for "+name+type+" => "+binding);
-    //         if (binding instanceof CallSite) {
-    //             site = (CallSite) binding;
-    //         }  else {
-    //             throw new ClassCastException("bootstrap method failed to produce a CallSite");
-    //         }
-    //         if (!site.getTarget().type().equals(type))
-    //             throw wrongTargetType(site.getTarget(), type);
-    //     } catch (Throwable ex) {
-    //         BootstrapMethodError bex;
-    //         if (ex instanceof BootstrapMethodError)
-    //             bex = (BootstrapMethodError) ex;
-    //         else
-    //             bex = new BootstrapMethodError("call site initialization exception", ex);
-    //         throw bex;
-    //     }
-    //     return site;
-    // }
-
-    // private static Object maybeReBox(Object x) {
-    //     if (x instanceof Integer) {
-    //         int xi = (int) x;
-    //         if (xi == (byte) xi)
-    //             x = xi;  // must rebox; see JLS 5.1.7
-    //     }
-    //     return x;
-    // }
-    // private static void maybeReBoxElements(Object[] xa) {
-    //     for (int i = 0; i < xa.length; i++) {
-    //         xa[i] = maybeReBox(xa[i]);
-    //     }
-    // }
 }
diff --git a/java/lang/invoke/FieldVarHandle.java b/java/lang/invoke/FieldVarHandle.java
new file mode 100644
index 0000000..0921040
--- /dev/null
+++ b/java/lang/invoke/FieldVarHandle.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2017 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 java.lang.invoke;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+
+/**
+ * A VarHandle that's associated with an ArtField.
+ * @hide
+ */
+final class FieldVarHandle extends VarHandle {
+    private final long artField;
+
+    private FieldVarHandle(Field field, Class<?> declaringClass) {
+        super(field.getType(), Modifier.isFinal(field.getModifiers()), declaringClass);
+        artField = field.getArtField();
+    }
+
+    private FieldVarHandle(Field field) {
+        super(field.getType(), Modifier.isFinal(field.getModifiers()));
+        artField = field.getArtField();
+    }
+
+    static FieldVarHandle create(Field field) {
+        if (Modifier.isStatic(field.getModifiers())) {
+            return new FieldVarHandle(field);
+        } else {
+            return new FieldVarHandle(field, field.getDeclaringClass());
+        }
+    }
+}
diff --git a/java/lang/invoke/MethodHandle.java b/java/lang/invoke/MethodHandle.java
index af3db10..159f9dd 100644
--- a/java/lang/invoke/MethodHandle.java
+++ b/java/lang/invoke/MethodHandle.java
@@ -25,1353 +25,28 @@
 
 package java.lang.invoke;
 
-
-import dalvik.system.EmulatedStackFrame;
-
-import static java.lang.invoke.MethodHandleStatics.*;
-
-/**
- * A method handle is a typed, directly executable reference to an underlying method,
- * constructor, field, or similar low-level operation, with optional
- * transformations of arguments or return values.
- * These transformations are quite general, and include such patterns as
- * {@linkplain #asType conversion},
- * {@linkplain #bindTo insertion},
- * {@linkplain java.lang.invoke.MethodHandles#dropArguments deletion},
- * and {@linkplain java.lang.invoke.MethodHandles#filterArguments substitution}.
- *
- * <h1>Method handle contents</h1>
- * Method handles are dynamically and strongly typed according to their parameter and return types.
- * They are not distinguished by the name or the defining class of their underlying methods.
- * A method handle must be invoked using a symbolic type descriptor which matches
- * the method handle's own {@linkplain #type type descriptor}.
- * <p>
- * Every method handle reports its type descriptor via the {@link #type type} accessor.
- * This type descriptor is a {@link java.lang.invoke.MethodType MethodType} object,
- * whose structure is a series of classes, one of which is
- * the return type of the method (or {@code void.class} if none).
- * <p>
- * A method handle's type controls the types of invocations it accepts,
- * and the kinds of transformations that apply to it.
- * <p>
- * A method handle contains a pair of special invoker methods
- * called {@link #invokeExact invokeExact} and {@link #invoke invoke}.
- * Both invoker methods provide direct access to the method handle's
- * underlying method, constructor, field, or other operation,
- * as modified by transformations of arguments and return values.
- * Both invokers accept calls which exactly match the method handle's own type.
- * The plain, inexact invoker also accepts a range of other call types.
- * <p>
- * Method handles are immutable and have no visible state.
- * Of course, they can be bound to underlying methods or data which exhibit state.
- * With respect to the Java Memory Model, any method handle will behave
- * as if all of its (internal) fields are final variables.  This means that any method
- * handle made visible to the application will always be fully formed.
- * This is true even if the method handle is published through a shared
- * variable in a data race.
- * <p>
- * Method handles cannot be subclassed by the user.
- * Implementations may (or may not) create internal subclasses of {@code MethodHandle}
- * which may be visible via the {@link java.lang.Object#getClass Object.getClass}
- * operation.  The programmer should not draw conclusions about a method handle
- * from its specific class, as the method handle class hierarchy (if any)
- * may change from time to time or across implementations from different vendors.
- *
- * <h1>Method handle compilation</h1>
- * A Java method call expression naming {@code invokeExact} or {@code invoke}
- * can invoke a method handle from Java source code.
- * From the viewpoint of source code, these methods can take any arguments
- * and their result can be cast to any return type.
- * Formally this is accomplished by giving the invoker methods
- * {@code Object} return types and variable arity {@code Object} arguments,
- * but they have an additional quality called <em>signature polymorphism</em>
- * which connects this freedom of invocation directly to the JVM execution stack.
- * <p>
- * As is usual with virtual methods, source-level calls to {@code invokeExact}
- * and {@code invoke} compile to an {@code invokevirtual} instruction.
- * More unusually, the compiler must record the actual argument types,
- * and may not perform method invocation conversions on the arguments.
- * Instead, it must push them on the stack according to their own unconverted types.
- * The method handle object itself is pushed on the stack before the arguments.
- * The compiler then calls the method handle with a symbolic type descriptor which
- * describes the argument and return types.
- * <p>
- * To issue a complete symbolic type descriptor, the compiler must also determine
- * the return type.  This is based on a cast on the method invocation expression,
- * if there is one, or else {@code Object} if the invocation is an expression
- * or else {@code void} if the invocation is a statement.
- * The cast may be to a primitive type (but not {@code void}).
- * <p>
- * As a corner case, an uncasted {@code null} argument is given
- * a symbolic type descriptor of {@code java.lang.Void}.
- * The ambiguity with the type {@code Void} is harmless, since there are no references of type
- * {@code Void} except the null reference.
- *
- * <h1>Method handle invocation</h1>
- * The first time a {@code invokevirtual} instruction is executed
- * it is linked, by symbolically resolving the names in the instruction
- * and verifying that the method call is statically legal.
- * This is true of calls to {@code invokeExact} and {@code invoke}.
- * In this case, the symbolic type descriptor emitted by the compiler is checked for
- * correct syntax and names it contains are resolved.
- * Thus, an {@code invokevirtual} instruction which invokes
- * a method handle will always link, as long
- * as the symbolic type descriptor is syntactically well-formed
- * and the types exist.
- * <p>
- * When the {@code invokevirtual} is executed after linking,
- * the receiving method handle's type is first checked by the JVM
- * to ensure that it matches the symbolic type descriptor.
- * If the type match fails, it means that the method which the
- * caller is invoking is not present on the individual
- * method handle being invoked.
- * <p>
- * In the case of {@code invokeExact}, the type descriptor of the invocation
- * (after resolving symbolic type names) must exactly match the method type
- * of the receiving method handle.
- * In the case of plain, inexact {@code invoke}, the resolved type descriptor
- * must be a valid argument to the receiver's {@link #asType asType} method.
- * Thus, plain {@code invoke} is more permissive than {@code invokeExact}.
- * <p>
- * After type matching, a call to {@code invokeExact} directly
- * and immediately invoke the method handle's underlying method
- * (or other behavior, as the case may be).
- * <p>
- * A call to plain {@code invoke} works the same as a call to
- * {@code invokeExact}, if the symbolic type descriptor specified by the caller
- * exactly matches the method handle's own type.
- * If there is a type mismatch, {@code invoke} attempts
- * to adjust the type of the receiving method handle,
- * as if by a call to {@link #asType asType},
- * to obtain an exactly invokable method handle {@code M2}.
- * This allows a more powerful negotiation of method type
- * between caller and callee.
- * <p>
- * (<em>Note:</em> The adjusted method handle {@code M2} is not directly observable,
- * and implementations are therefore not required to materialize it.)
- *
- * <h1>Invocation checking</h1>
- * In typical programs, method handle type matching will usually succeed.
- * But if a match fails, the JVM will throw a {@link WrongMethodTypeException},
- * either directly (in the case of {@code invokeExact}) or indirectly as if
- * by a failed call to {@code asType} (in the case of {@code invoke}).
- * <p>
- * Thus, a method type mismatch which might show up as a linkage error
- * in a statically typed program can show up as
- * a dynamic {@code WrongMethodTypeException}
- * in a program which uses method handles.
- * <p>
- * Because method types contain "live" {@code Class} objects,
- * method type matching takes into account both types names and class loaders.
- * Thus, even if a method handle {@code M} is created in one
- * class loader {@code L1} and used in another {@code L2},
- * method handle calls are type-safe, because the caller's symbolic type
- * descriptor, as resolved in {@code L2},
- * is matched against the original callee method's symbolic type descriptor,
- * as resolved in {@code L1}.
- * The resolution in {@code L1} happens when {@code M} is created
- * and its type is assigned, while the resolution in {@code L2} happens
- * when the {@code invokevirtual} instruction is linked.
- * <p>
- * Apart from the checking of type descriptors,
- * a method handle's capability to call its underlying method is unrestricted.
- * If a method handle is formed on a non-public method by a class
- * that has access to that method, the resulting handle can be used
- * in any place by any caller who receives a reference to it.
- * <p>
- * Unlike with the Core Reflection API, where access is checked every time
- * a reflective method is invoked,
- * method handle access checking is performed
- * <a href="MethodHandles.Lookup.html#access">when the method handle is created</a>.
- * In the case of {@code ldc} (see below), access checking is performed as part of linking
- * the constant pool entry underlying the constant method handle.
- * <p>
- * Thus, handles to non-public methods, or to methods in non-public classes,
- * should generally be kept secret.
- * They should not be passed to untrusted code unless their use from
- * the untrusted code would be harmless.
- *
- * <h1>Method handle creation</h1>
- * Java code can create a method handle that directly accesses
- * any method, constructor, or field that is accessible to that code.
- * This is done via a reflective, capability-based API called
- * {@link java.lang.invoke.MethodHandles.Lookup MethodHandles.Lookup}
- * For example, a static method handle can be obtained
- * from {@link java.lang.invoke.MethodHandles.Lookup#findStatic Lookup.findStatic}.
- * There are also conversion methods from Core Reflection API objects,
- * such as {@link java.lang.invoke.MethodHandles.Lookup#unreflect Lookup.unreflect}.
- * <p>
- * Like classes and strings, method handles that correspond to accessible
- * fields, methods, and constructors can also be represented directly
- * in a class file's constant pool as constants to be loaded by {@code ldc} bytecodes.
- * A new type of constant pool entry, {@code CONSTANT_MethodHandle},
- * refers directly to an associated {@code CONSTANT_Methodref},
- * {@code CONSTANT_InterfaceMethodref}, or {@code CONSTANT_Fieldref}
- * constant pool entry.
- * (For full details on method handle constants,
- * see sections 4.4.8 and 5.4.3.5 of the Java Virtual Machine Specification.)
- * <p>
- * Method handles produced by lookups or constant loads from methods or
- * constructors with the variable arity modifier bit ({@code 0x0080})
- * have a corresponding variable arity, as if they were defined with
- * the help of {@link #asVarargsCollector asVarargsCollector}.
- * <p>
- * A method reference may refer either to a static or non-static method.
- * In the non-static case, the method handle type includes an explicit
- * receiver argument, prepended before any other arguments.
- * In the method handle's type, the initial receiver argument is typed
- * according to the class under which the method was initially requested.
- * (E.g., if a non-static method handle is obtained via {@code ldc},
- * the type of the receiver is the class named in the constant pool entry.)
- * <p>
- * Method handle constants are subject to the same link-time access checks
- * their corresponding bytecode instructions, and the {@code ldc} instruction
- * will throw corresponding linkage errors if the bytecode behaviors would
- * throw such errors.
- * <p>
- * As a corollary of this, access to protected members is restricted
- * to receivers only of the accessing class, or one of its subclasses,
- * and the accessing class must in turn be a subclass (or package sibling)
- * of the protected member's defining class.
- * If a method reference refers to a protected non-static method or field
- * of a class outside the current package, the receiver argument will
- * be narrowed to the type of the accessing class.
- * <p>
- * When a method handle to a virtual method is invoked, the method is
- * always looked up in the receiver (that is, the first argument).
- * <p>
- * A non-virtual method handle to a specific virtual method implementation
- * can also be created.  These do not perform virtual lookup based on
- * receiver type.  Such a method handle simulates the effect of
- * an {@code invokespecial} instruction to the same method.
- *
- * <h1>Usage examples</h1>
- * Here are some examples of usage:
- * <blockquote><pre>{@code
-Object x, y; String s; int i;
-MethodType mt; MethodHandle mh;
-MethodHandles.Lookup lookup = MethodHandles.lookup();
-// mt is (char,char)String
-mt = MethodType.methodType(String.class, char.class, char.class);
-mh = lookup.findVirtual(String.class, "replace", mt);
-s = (String) mh.invokeExact("daddy",'d','n');
-// invokeExact(Ljava/lang/String;CC)Ljava/lang/String;
-assertEquals(s, "nanny");
-// weakly typed invocation (using MHs.invoke)
-s = (String) mh.invokeWithArguments("sappy", 'p', 'v');
-assertEquals(s, "savvy");
-// mt is (Object[])List
-mt = MethodType.methodType(java.util.List.class, Object[].class);
-mh = lookup.findStatic(java.util.Arrays.class, "asList", mt);
-assert(mh.isVarargsCollector());
-x = mh.invoke("one", "two");
-// invoke(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object;
-assertEquals(x, java.util.Arrays.asList("one","two"));
-// mt is (Object,Object,Object)Object
-mt = MethodType.genericMethodType(3);
-mh = mh.asType(mt);
-x = mh.invokeExact((Object)1, (Object)2, (Object)3);
-// invokeExact(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
-assertEquals(x, java.util.Arrays.asList(1,2,3));
-// mt is ()int
-mt = MethodType.methodType(int.class);
-mh = lookup.findVirtual(java.util.List.class, "size", mt);
-i = (int) mh.invokeExact(java.util.Arrays.asList(1,2,3));
-// invokeExact(Ljava/util/List;)I
-assert(i == 3);
-mt = MethodType.methodType(void.class, String.class);
-mh = lookup.findVirtual(java.io.PrintStream.class, "println", mt);
-mh.invokeExact(System.out, "Hello, world.");
-// invokeExact(Ljava/io/PrintStream;Ljava/lang/String;)V
- * }</pre></blockquote>
- * Each of the above calls to {@code invokeExact} or plain {@code invoke}
- * generates a single invokevirtual instruction with
- * the symbolic type descriptor indicated in the following comment.
- * In these examples, the helper method {@code assertEquals} is assumed to
- * be a method which calls {@link java.util.Objects#equals(Object,Object) Objects.equals}
- * on its arguments, and asserts that the result is true.
- *
- * <h1>Exceptions</h1>
- * The methods {@code invokeExact} and {@code invoke} are declared
- * to throw {@link java.lang.Throwable Throwable},
- * which is to say that there is no static restriction on what a method handle
- * can throw.  Since the JVM does not distinguish between checked
- * and unchecked exceptions (other than by their class, of course),
- * there is no particular effect on bytecode shape from ascribing
- * checked exceptions to method handle invocations.  But in Java source
- * code, methods which perform method handle calls must either explicitly
- * throw {@code Throwable}, or else must catch all
- * throwables locally, rethrowing only those which are legal in the context,
- * and wrapping ones which are illegal.
- *
- * <h1><a name="sigpoly"></a>Signature polymorphism</h1>
- * The unusual compilation and linkage behavior of
- * {@code invokeExact} and plain {@code invoke}
- * is referenced by the term <em>signature polymorphism</em>.
- * As defined in the Java Language Specification,
- * a signature polymorphic method is one which can operate with
- * any of a wide range of call signatures and return types.
- * <p>
- * In source code, a call to a signature polymorphic method will
- * compile, regardless of the requested symbolic type descriptor.
- * As usual, the Java compiler emits an {@code invokevirtual}
- * instruction with the given symbolic type descriptor against the named method.
- * The unusual part is that the symbolic type descriptor is derived from
- * the actual argument and return types, not from the method declaration.
- * <p>
- * When the JVM processes bytecode containing signature polymorphic calls,
- * it will successfully link any such call, regardless of its symbolic type descriptor.
- * (In order to retain type safety, the JVM will guard such calls with suitable
- * dynamic type checks, as described elsewhere.)
- * <p>
- * Bytecode generators, including the compiler back end, are required to emit
- * untransformed symbolic type descriptors for these methods.
- * Tools which determine symbolic linkage are required to accept such
- * untransformed descriptors, without reporting linkage errors.
- *
- * <h1>Interoperation between method handles and the Core Reflection API</h1>
- * Using factory methods in the {@link java.lang.invoke.MethodHandles.Lookup Lookup} API,
- * any class member represented by a Core Reflection API object
- * can be converted to a behaviorally equivalent method handle.
- * For example, a reflective {@link java.lang.reflect.Method Method} can
- * be converted to a method handle using
- * {@link java.lang.invoke.MethodHandles.Lookup#unreflect Lookup.unreflect}.
- * The resulting method handles generally provide more direct and efficient
- * access to the underlying class members.
- * <p>
- * As a special case,
- * when the Core Reflection API is used to view the signature polymorphic
- * methods {@code invokeExact} or plain {@code invoke} in this class,
- * they appear as ordinary non-polymorphic methods.
- * Their reflective appearance, as viewed by
- * {@link java.lang.Class#getDeclaredMethod Class.getDeclaredMethod},
- * is unaffected by their special status in this API.
- * For example, {@link java.lang.reflect.Method#getModifiers Method.getModifiers}
- * will report exactly those modifier bits required for any similarly
- * declared method, including in this case {@code native} and {@code varargs} bits.
- * <p>
- * As with any reflected method, these methods (when reflected) may be
- * invoked via {@link java.lang.reflect.Method#invoke java.lang.reflect.Method.invoke}.
- * However, such reflective calls do not result in method handle invocations.
- * Such a call, if passed the required argument
- * (a single one, of type {@code Object[]}), will ignore the argument and
- * will throw an {@code UnsupportedOperationException}.
- * <p>
- * Since {@code invokevirtual} instructions can natively
- * invoke method handles under any symbolic type descriptor, this reflective view conflicts
- * with the normal presentation of these methods via bytecodes.
- * Thus, these two native methods, when reflectively viewed by
- * {@code Class.getDeclaredMethod}, may be regarded as placeholders only.
- * <p>
- * In order to obtain an invoker method for a particular type descriptor,
- * use {@link java.lang.invoke.MethodHandles#exactInvoker MethodHandles.exactInvoker},
- * or {@link java.lang.invoke.MethodHandles#invoker MethodHandles.invoker}.
- * The {@link java.lang.invoke.MethodHandles.Lookup#findVirtual Lookup.findVirtual}
- * API is also able to return a method handle
- * to call {@code invokeExact} or plain {@code invoke},
- * for any specified type descriptor .
- *
- * <h1>Interoperation between method handles and Java generics</h1>
- * A method handle can be obtained on a method, constructor, or field
- * which is declared with Java generic types.
- * As with the Core Reflection API, the type of the method handle
- * will constructed from the erasure of the source-level type.
- * When a method handle is invoked, the types of its arguments
- * or the return value cast type may be generic types or type instances.
- * If this occurs, the compiler will replace those
- * types by their erasures when it constructs the symbolic type descriptor
- * for the {@code invokevirtual} instruction.
- * <p>
- * Method handles do not represent
- * their function-like types in terms of Java parameterized (generic) types,
- * because there are three mismatches between function-like types and parameterized
- * Java types.
- * <ul>
- * <li>Method types range over all possible arities,
- * from no arguments to up to the  <a href="MethodHandle.html#maxarity">maximum number</a> of allowed arguments.
- * Generics are not variadic, and so cannot represent this.</li>
- * <li>Method types can specify arguments of primitive types,
- * which Java generic types cannot range over.</li>
- * <li>Higher order functions over method handles (combinators) are
- * often generic across a wide range of function types, including
- * those of multiple arities.  It is impossible to represent such
- * genericity with a Java type parameter.</li>
- * </ul>
- *
- * <h1><a name="maxarity"></a>Arity limits</h1>
- * The JVM imposes on all methods and constructors of any kind an absolute
- * limit of 255 stacked arguments.  This limit can appear more restrictive
- * in certain cases:
- * <ul>
- * <li>A {@code long} or {@code double} argument counts (for purposes of arity limits) as two argument slots.
- * <li>A non-static method consumes an extra argument for the object on which the method is called.
- * <li>A constructor consumes an extra argument for the object which is being constructed.
- * <li>Since a method handle&rsquo;s {@code invoke} method (or other signature-polymorphic method) is non-virtual,
- *     it consumes an extra argument for the method handle itself, in addition to any non-virtual receiver object.
- * </ul>
- * These limits imply that certain method handles cannot be created, solely because of the JVM limit on stacked arguments.
- * For example, if a static JVM method accepts exactly 255 arguments, a method handle cannot be created for it.
- * Attempts to create method handles with impossible method types lead to an {@link IllegalArgumentException}.
- * In particular, a method handle&rsquo;s type must not have an arity of the exact maximum 255.
- *
- * @see MethodType
- * @see MethodHandles
- * @author John Rose, JSR 292 EG
- */
 public abstract class MethodHandle {
-    // Android-changed:
-    //
-    // static { MethodHandleImpl.initStatics(); }
-    //
-    // LambdaForm and customizationCount are currently unused in our implementation
-    // and will be substituted with appropriate implementation / delegate classes.
-    //
-    // /*private*/ final LambdaForm form;
-    // form is not private so that invokers can easily fetch it
-    // /*non-public*/ byte customizationCount;
-    // customizationCount should be accessible from invokers
 
+    public MethodType type() { return null; }
 
-    /**
-     * Internal marker interface which distinguishes (to the Java compiler)
-     * those methods which are <a href="MethodHandle.html#sigpoly">signature polymorphic</a>.
-     *
-     * @hide
-     */
-    @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD})
-    @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
-    public @interface PolymorphicSignature { }
+    public final Object invokeExact(Object... args) throws Throwable { return null; }
 
-    /**
-     * The type of this method handle, this corresponds to the exact type of the method
-     * being invoked.
-     */
-    private final MethodType type;
+    public final Object invoke(Object... args) throws Throwable { return null; }
 
-    /**
-     * The nominal type of this method handle, will be non-null if a method handle declares
-     * a different type from its "real" type, which is either the type of the method being invoked
-     * or the type of the emulated stackframe expected by an underyling adapter.
-     */
-    private MethodType nominalType;
+    public Object invokeWithArguments(Object... arguments) throws Throwable { return null; }
 
-    /**
-     * The spread invoker associated with this type with zero trailing arguments.
-     * This is used to speed up invokeWithArguments.
-     */
-    private MethodHandle cachedSpreadInvoker;
+    public Object invokeWithArguments(java.util.List<?> arguments) throws Throwable { return null; }
 
-    /**
-     * The INVOKE* constants and SGET/SPUT and IGET/IPUT constants specify the behaviour of this
-     * method handle with respect to the ArtField* or the ArtMethod* that it operates on. These
-     * behaviours are equivalent to the dex bytecode behaviour on the respective method_id or
-     * field_id in the equivalent instruction.
-     *
-     * INVOKE_TRANSFORM is a special type of handle which doesn't encode any dex bytecode behaviour,
-     * instead it transforms the list of input arguments or performs other higher order operations
-     * before (optionally) delegating to another method handle.
-     *
-     * INVOKE_CALLSITE_TRANSFORM is a variation on INVOKE_TRANSFORM where the method type of
-     * a MethodHandle dynamically varies based on the callsite. This is used by
-     * the VarargsCollector implementation which places any number of trailing arguments
-     * into an array before invoking an arity method. The "any number of trailing arguments" means
-     * it would otherwise generate WrongMethodTypeExceptions as the callsite method type and
-     * VarargsCollector method type appear incompatible.
-     */
+    public MethodHandle asType(MethodType newType) { return null; }
 
-    /** @hide */ public static final int INVOKE_VIRTUAL = 0;
-    /** @hide */ public static final int INVOKE_SUPER = 1;
-    /** @hide */ public static final int INVOKE_DIRECT = 2;
-    /** @hide */ public static final int INVOKE_STATIC = 3;
-    /** @hide */ public static final int INVOKE_INTERFACE = 4;
-    /** @hide */ public static final int INVOKE_TRANSFORM = 5;
-    /** @hide */ public static final int INVOKE_CALLSITE_TRANSFORM = 6;
-    /** @hide */ public static final int IGET = 7;
-    /** @hide */ public static final int IPUT = 8;
-    /** @hide */ public static final int SGET = 9;
-    /** @hide */ public static final int SPUT = 10;
+    public MethodHandle asCollector(Class<?> arrayType, int arrayLength) { return null; }
 
-    // The kind of this method handle (used by the runtime). This is one of the INVOKE_*
-    // constants or SGET/SPUT, IGET/IPUT.
-    /** @hide */ protected final int handleKind;
+    public MethodHandle asVarargsCollector(Class<?> arrayType) { return null; }
 
-    // The ArtMethod* or ArtField* associated with this method handle (used by the runtime).
-    /** @hide */ protected final long artFieldOrMethod;
+    public boolean isVarargsCollector() { return false; }
 
-    /** @hide */
-    protected MethodHandle(long artFieldOrMethod, int handleKind, MethodType type) {
-        this.artFieldOrMethod = artFieldOrMethod;
-        this.handleKind = handleKind;
-        this.type = type;
-    }
+    public MethodHandle asFixedArity() { return null; }
 
-    /**
-     * Reports the type of this method handle.
-     * Every invocation of this method handle via {@code invokeExact} must exactly match this type.
-     * @return the method handle type
-     */
-    public MethodType type() {
-        if (nominalType != null) {
-            return nominalType;
-        }
+    public MethodHandle bindTo(Object x) { return null; }
 
-        return type;
-    }
-
-    /**
-     * Invokes the method handle, allowing any caller type descriptor, but requiring an exact type match.
-     * The symbolic type descriptor at the call site of {@code invokeExact} must
-     * exactly match this method handle's {@link #type type}.
-     * No conversions are allowed on arguments or return values.
-     * <p>
-     * When this method is observed via the Core Reflection API,
-     * it will appear as a single native method, taking an object array and returning an object.
-     * If this native method is invoked directly via
-     * {@link java.lang.reflect.Method#invoke java.lang.reflect.Method.invoke}, via JNI,
-     * or indirectly via {@link java.lang.invoke.MethodHandles.Lookup#unreflect Lookup.unreflect},
-     * it will throw an {@code UnsupportedOperationException}.
-     * @param args the signature-polymorphic parameter list, statically represented using varargs
-     * @return the signature-polymorphic result, statically represented using {@code Object}
-     * @throws WrongMethodTypeException if the target's type is not identical with the caller's symbolic type descriptor
-     * @throws Throwable anything thrown by the underlying method propagates unchanged through the method handle call
-     */
-    public final native @PolymorphicSignature Object invokeExact(Object... args) throws Throwable;
-
-    /**
-     * Invokes the method handle, allowing any caller type descriptor,
-     * and optionally performing conversions on arguments and return values.
-     * <p>
-     * If the call site's symbolic type descriptor exactly matches this method handle's {@link #type type},
-     * the call proceeds as if by {@link #invokeExact invokeExact}.
-     * <p>
-     * Otherwise, the call proceeds as if this method handle were first
-     * adjusted by calling {@link #asType asType} to adjust this method handle
-     * to the required type, and then the call proceeds as if by
-     * {@link #invokeExact invokeExact} on the adjusted method handle.
-     * <p>
-     * There is no guarantee that the {@code asType} call is actually made.
-     * If the JVM can predict the results of making the call, it may perform
-     * adaptations directly on the caller's arguments,
-     * and call the target method handle according to its own exact type.
-     * <p>
-     * The resolved type descriptor at the call site of {@code invoke} must
-     * be a valid argument to the receivers {@code asType} method.
-     * In particular, the caller must specify the same argument arity
-     * as the callee's type,
-     * if the callee is not a {@linkplain #asVarargsCollector variable arity collector}.
-     * <p>
-     * When this method is observed via the Core Reflection API,
-     * it will appear as a single native method, taking an object array and returning an object.
-     * If this native method is invoked directly via
-     * {@link java.lang.reflect.Method#invoke java.lang.reflect.Method.invoke}, via JNI,
-     * or indirectly via {@link java.lang.invoke.MethodHandles.Lookup#unreflect Lookup.unreflect},
-     * it will throw an {@code UnsupportedOperationException}.
-     * @param args the signature-polymorphic parameter list, statically represented using varargs
-     * @return the signature-polymorphic result, statically represented using {@code Object}
-     * @throws WrongMethodTypeException if the target's type cannot be adjusted to the caller's symbolic type descriptor
-     * @throws ClassCastException if the target's type can be adjusted to the caller, but a reference cast fails
-     * @throws Throwable anything thrown by the underlying method propagates unchanged through the method handle call
-     */
-    public final native @PolymorphicSignature Object invoke(Object... args) throws Throwable;
-
-    // Android-changed: Removed implementation details.
-    //
-    // /*non-public*/ final native @PolymorphicSignature Object invokeBasic(Object... args)
-    // /*non-public*/ static native @PolymorphicSignature Object linkToVirtual(Object... args)
-    // /*non-public*/ static native @PolymorphicSignature Object linkToStatic(Object... args)
-    // /*non-public*/ static native @PolymorphicSignature Object linkToSpecial(Object... args)
-    // /*non-public*/ static native @PolymorphicSignature Object linkToInterface(Object... args)
-
-    /**
-     * Performs a variable arity invocation, passing the arguments in the given list
-     * to the method handle, as if via an inexact {@link #invoke invoke} from a call site
-     * which mentions only the type {@code Object}, and whose arity is the length
-     * of the argument list.
-     * <p>
-     * Specifically, execution proceeds as if by the following steps,
-     * although the methods are not guaranteed to be called if the JVM
-     * can predict their effects.
-     * <ul>
-     * <li>Determine the length of the argument array as {@code N}.
-     *     For a null reference, {@code N=0}. </li>
-     * <li>Determine the general type {@code TN} of {@code N} arguments as
-     *     as {@code TN=MethodType.genericMethodType(N)}.</li>
-     * <li>Force the original target method handle {@code MH0} to the
-     *     required type, as {@code MH1 = MH0.asType(TN)}. </li>
-     * <li>Spread the array into {@code N} separate arguments {@code A0, ...}. </li>
-     * <li>Invoke the type-adjusted method handle on the unpacked arguments:
-     *     MH1.invokeExact(A0, ...). </li>
-     * <li>Take the return value as an {@code Object} reference. </li>
-     * </ul>
-     * <p>
-     * Because of the action of the {@code asType} step, the following argument
-     * conversions are applied as necessary:
-     * <ul>
-     * <li>reference casting
-     * <li>unboxing
-     * <li>widening primitive conversions
-     * </ul>
-     * <p>
-     * The result returned by the call is boxed if it is a primitive,
-     * or forced to null if the return type is void.
-     * <p>
-     * This call is equivalent to the following code:
-     * <blockquote><pre>{@code
-     * MethodHandle invoker = MethodHandles.spreadInvoker(this.type(), 0);
-     * Object result = invoker.invokeExact(this, arguments);
-     * }</pre></blockquote>
-     * <p>
-     * Unlike the signature polymorphic methods {@code invokeExact} and {@code invoke},
-     * {@code invokeWithArguments} can be accessed normally via the Core Reflection API and JNI.
-     * It can therefore be used as a bridge between native or reflective code and method handles.
-     *
-     * @param arguments the arguments to pass to the target
-     * @return the result returned by the target
-     * @throws ClassCastException if an argument cannot be converted by reference casting
-     * @throws WrongMethodTypeException if the target's type cannot be adjusted to take the given number of {@code Object} arguments
-     * @throws Throwable anything thrown by the target method invocation
-     * @see MethodHandles#spreadInvoker
-     */
-    public Object invokeWithArguments(Object... arguments) throws Throwable {
-        MethodHandle invoker = null;
-        synchronized (this) {
-            if (cachedSpreadInvoker == null) {
-                cachedSpreadInvoker = MethodHandles.spreadInvoker(this.type(), 0);
-            }
-
-            invoker = cachedSpreadInvoker;
-        }
-
-        return invoker.invoke(this, arguments);
-    }
-
-    /**
-     * Performs a variable arity invocation, passing the arguments in the given array
-     * to the method handle, as if via an inexact {@link #invoke invoke} from a call site
-     * which mentions only the type {@code Object}, and whose arity is the length
-     * of the argument array.
-     * <p>
-     * This method is also equivalent to the following code:
-     * <blockquote><pre>{@code
-     *   invokeWithArguments(arguments.toArray()
-     * }</pre></blockquote>
-     *
-     * @param arguments the arguments to pass to the target
-     * @return the result returned by the target
-     * @throws NullPointerException if {@code arguments} is a null reference
-     * @throws ClassCastException if an argument cannot be converted by reference casting
-     * @throws WrongMethodTypeException if the target's type cannot be adjusted to take the given number of {@code Object} arguments
-     * @throws Throwable anything thrown by the target method invocation
-     */
-    public Object invokeWithArguments(java.util.List<?> arguments) throws Throwable {
-        return invokeWithArguments(arguments.toArray());
-    }
-
-    /**
-     * Produces an adapter method handle which adapts the type of the
-     * current method handle to a new type.
-     * The resulting method handle is guaranteed to report a type
-     * which is equal to the desired new type.
-     * <p>
-     * If the original type and new type are equal, returns {@code this}.
-     * <p>
-     * The new method handle, when invoked, will perform the following
-     * steps:
-     * <ul>
-     * <li>Convert the incoming argument list to match the original
-     *     method handle's argument list.
-     * <li>Invoke the original method handle on the converted argument list.
-     * <li>Convert any result returned by the original method handle
-     *     to the return type of new method handle.
-     * </ul>
-     * <p>
-     * This method provides the crucial behavioral difference between
-     * {@link #invokeExact invokeExact} and plain, inexact {@link #invoke invoke}.
-     * The two methods
-     * perform the same steps when the caller's type descriptor exactly m atches
-     * the callee's, but when the types differ, plain {@link #invoke invoke}
-     * also calls {@code asType} (or some internal equivalent) in order
-     * to match up the caller's and callee's types.
-     * <p>
-     * If the current method is a variable arity method handle
-     * argument list conversion may involve the conversion and collection
-     * of several arguments into an array, as
-     * {@linkplain #asVarargsCollector described elsewhere}.
-     * In every other case, all conversions are applied <em>pairwise</em>,
-     * which means that each argument or return value is converted to
-     * exactly one argument or return value (or no return value).
-     * The applied conversions are defined by consulting the
-     * the corresponding component types of the old and new
-     * method handle types.
-     * <p>
-     * Let <em>T0</em> and <em>T1</em> be corresponding new and old parameter types,
-     * or old and new return types.  Specifically, for some valid index {@code i}, let
-     * <em>T0</em>{@code =newType.parameterType(i)} and <em>T1</em>{@code =this.type().parameterType(i)}.
-     * Or else, going the other way for return values, let
-     * <em>T0</em>{@code =this.type().returnType()} and <em>T1</em>{@code =newType.returnType()}.
-     * If the types are the same, the new method handle makes no change
-     * to the corresponding argument or return value (if any).
-     * Otherwise, one of the following conversions is applied
-     * if possible:
-     * <ul>
-     * <li>If <em>T0</em> and <em>T1</em> are references, then a cast to <em>T1</em> is applied.
-     *     (The types do not need to be related in any particular way.
-     *     This is because a dynamic value of null can convert to any reference type.)
-     * <li>If <em>T0</em> and <em>T1</em> are primitives, then a Java method invocation
-     *     conversion (JLS 5.3) is applied, if one exists.
-     *     (Specifically, <em>T0</em> must convert to <em>T1</em> by a widening primitive conversion.)
-     * <li>If <em>T0</em> is a primitive and <em>T1</em> a reference,
-     *     a Java casting conversion (JLS 5.5) is applied if one exists.
-     *     (Specifically, the value is boxed from <em>T0</em> to its wrapper class,
-     *     which is then widened as needed to <em>T1</em>.)
-     * <li>If <em>T0</em> is a reference and <em>T1</em> a primitive, an unboxing
-     *     conversion will be applied at runtime, possibly followed
-     *     by a Java method invocation conversion (JLS 5.3)
-     *     on the primitive value.  (These are the primitive widening conversions.)
-     *     <em>T0</em> must be a wrapper class or a supertype of one.
-     *     (In the case where <em>T0</em> is Object, these are the conversions
-     *     allowed by {@link java.lang.reflect.Method#invoke java.lang.reflect.Method.invoke}.)
-     *     The unboxing conversion must have a possibility of success, which means that
-     *     if <em>T0</em> is not itself a wrapper class, there must exist at least one
-     *     wrapper class <em>TW</em> which is a subtype of <em>T0</em> and whose unboxed
-     *     primitive value can be widened to <em>T1</em>.
-     * <li>If the return type <em>T1</em> is marked as void, any returned value is discarded
-     * <li>If the return type <em>T0</em> is void and <em>T1</em> a reference, a null value is introduced.
-     * <li>If the return type <em>T0</em> is void and <em>T1</em> a primitive,
-     *     a zero value is introduced.
-     * </ul>
-     * (<em>Note:</em> Both <em>T0</em> and <em>T1</em> may be regarded as static types,
-     * because neither corresponds specifically to the <em>dynamic type</em> of any
-     * actual argument or return value.)
-     * <p>
-     * The method handle conversion cannot be made if any one of the required
-     * pairwise conversions cannot be made.
-     * <p>
-     * At runtime, the conversions applied to reference arguments
-     * or return values may require additional runtime checks which can fail.
-     * An unboxing operation may fail because the original reference is null,
-     * causing a {@link java.lang.NullPointerException NullPointerException}.
-     * An unboxing operation or a reference cast may also fail on a reference
-     * to an object of the wrong type,
-     * causing a {@link java.lang.ClassCastException ClassCastException}.
-     * Although an unboxing operation may accept several kinds of wrappers,
-     * if none are available, a {@code ClassCastException} will be thrown.
-     *
-     * @param newType the expected type of the new method handle
-     * @return a method handle which delegates to {@code this} after performing
-     *           any necessary argument conversions, and arranges for any
-     *           necessary return value conversions
-     * @throws NullPointerException if {@code newType} is a null reference
-     * @throws WrongMethodTypeException if the conversion cannot be made
-     * @see MethodHandles#explicitCastArguments
-     */
-    public MethodHandle asType(MethodType newType) {
-        // Fast path alternative to a heavyweight {@code asType} call.
-        // Return 'this' if the conversion will be a no-op.
-        if (newType == type) {
-            return this;
-        }
-
-        if (!type.isConvertibleTo(newType)) {
-            throw new WrongMethodTypeException("cannot convert " + this + " to " + newType);
-        }
-
-        MethodHandle mh = duplicate();
-        mh.nominalType = newType;
-        return mh;
-    }
-
-    /**
-     * Makes an <em>array-spreading</em> method handle, which accepts a trailing array argument
-     * and spreads its elements as positional arguments.
-     * The new method handle adapts, as its <i>target</i>,
-     * the current method handle.  The type of the adapter will be
-     * the same as the type of the target, except that the final
-     * {@code arrayLength} parameters of the target's type are replaced
-     * by a single array parameter of type {@code arrayType}.
-     * <p>
-     * If the array element type differs from any of the corresponding
-     * argument types on the original target,
-     * the original target is adapted to take the array elements directly,
-     * as if by a call to {@link #asType asType}.
-     * <p>
-     * When called, the adapter replaces a trailing array argument
-     * by the array's elements, each as its own argument to the target.
-     * (The order of the arguments is preserved.)
-     * They are converted pairwise by casting and/or unboxing
-     * to the types of the trailing parameters of the target.
-     * Finally the target is called.
-     * What the target eventually returns is returned unchanged by the adapter.
-     * <p>
-     * Before calling the target, the adapter verifies that the array
-     * contains exactly enough elements to provide a correct argument count
-     * to the target method handle.
-     * (The array may also be null when zero elements are required.)
-     * <p>
-     * If, when the adapter is called, the supplied array argument does
-     * not have the correct number of elements, the adapter will throw
-     * an {@link IllegalArgumentException} instead of invoking the target.
-     * <p>
-     * Here are some simple examples of array-spreading method handles:
-     * <blockquote><pre>{@code
-MethodHandle equals = publicLookup()
-  .findVirtual(String.class, "equals", methodType(boolean.class, Object.class));
-assert( (boolean) equals.invokeExact("me", (Object)"me"));
-assert(!(boolean) equals.invokeExact("me", (Object)"thee"));
-// spread both arguments from a 2-array:
-MethodHandle eq2 = equals.asSpreader(Object[].class, 2);
-assert( (boolean) eq2.invokeExact(new Object[]{ "me", "me" }));
-assert(!(boolean) eq2.invokeExact(new Object[]{ "me", "thee" }));
-// try to spread from anything but a 2-array:
-for (int n = 0; n <= 10; n++) {
-  Object[] badArityArgs = (n == 2 ? null : new Object[n]);
-  try { assert((boolean) eq2.invokeExact(badArityArgs) && false); }
-  catch (IllegalArgumentException ex) { } // OK
-}
-// spread both arguments from a String array:
-MethodHandle eq2s = equals.asSpreader(String[].class, 2);
-assert( (boolean) eq2s.invokeExact(new String[]{ "me", "me" }));
-assert(!(boolean) eq2s.invokeExact(new String[]{ "me", "thee" }));
-// spread second arguments from a 1-array:
-MethodHandle eq1 = equals.asSpreader(Object[].class, 1);
-assert( (boolean) eq1.invokeExact("me", new Object[]{ "me" }));
-assert(!(boolean) eq1.invokeExact("me", new Object[]{ "thee" }));
-// spread no arguments from a 0-array or null:
-MethodHandle eq0 = equals.asSpreader(Object[].class, 0);
-assert( (boolean) eq0.invokeExact("me", (Object)"me", new Object[0]));
-assert(!(boolean) eq0.invokeExact("me", (Object)"thee", (Object[])null));
-// asSpreader and asCollector are approximate inverses:
-for (int n = 0; n <= 2; n++) {
-    for (Class<?> a : new Class<?>[]{Object[].class, String[].class, CharSequence[].class}) {
-        MethodHandle equals2 = equals.asSpreader(a, n).asCollector(a, n);
-        assert( (boolean) equals2.invokeWithArguments("me", "me"));
-        assert(!(boolean) equals2.invokeWithArguments("me", "thee"));
-    }
-}
-MethodHandle caToString = publicLookup()
-  .findStatic(Arrays.class, "toString", methodType(String.class, char[].class));
-assertEquals("[A, B, C]", (String) caToString.invokeExact("ABC".toCharArray()));
-MethodHandle caString3 = caToString.asCollector(char[].class, 3);
-assertEquals("[A, B, C]", (String) caString3.invokeExact('A', 'B', 'C'));
-MethodHandle caToString2 = caString3.asSpreader(char[].class, 2);
-assertEquals("[A, B, C]", (String) caToString2.invokeExact('A', "BC".toCharArray()));
-     * }</pre></blockquote>
-     * @param arrayType usually {@code Object[]}, the type of the array argument from which to extract the spread arguments
-     * @param arrayLength the number of arguments to spread from an incoming array argument
-     * @return a new method handle which spreads its final array argument,
-     *         before calling the original method handle
-     * @throws NullPointerException if {@code arrayType} is a null reference
-     * @throws IllegalArgumentException if {@code arrayType} is not an array type,
-     *         or if target does not have at least
-     *         {@code arrayLength} parameter types,
-     *         or if {@code arrayLength} is negative,
-     *         or if the resulting method handle's type would have
-     *         <a href="MethodHandle.html#maxarity">too many parameters</a>
-     * @throws WrongMethodTypeException if the implied {@code asType} call fails
-     * @see #asCollector
-     */
-    public MethodHandle asSpreader(Class<?> arrayType, int arrayLength) {
-        MethodType postSpreadType = asSpreaderChecks(arrayType, arrayLength);
-
-        final int targetParamCount = postSpreadType.parameterCount();
-        MethodType dropArrayArgs = postSpreadType.dropParameterTypes(
-                (targetParamCount - arrayLength), targetParamCount);
-        MethodType adapterType = dropArrayArgs.appendParameterTypes(arrayType);
-
-        return new Transformers.Spreader(this, adapterType, arrayLength);
-    }
-
-    /**
-     * See if {@code asSpreader} can be validly called with the given arguments.
-     * Return the type of the method handle call after spreading but before conversions.
-     */
-    private MethodType asSpreaderChecks(Class<?> arrayType, int arrayLength) {
-        spreadArrayChecks(arrayType, arrayLength);
-        int nargs = type().parameterCount();
-        if (nargs < arrayLength || arrayLength < 0)
-            throw newIllegalArgumentException("bad spread array length");
-        Class<?> arrayElement = arrayType.getComponentType();
-        MethodType mtype = type();
-        boolean match = true, fail = false;
-        for (int i = nargs - arrayLength; i < nargs; i++) {
-            Class<?> ptype = mtype.parameterType(i);
-            if (ptype != arrayElement) {
-                match = false;
-                if (!MethodType.canConvert(arrayElement, ptype)) {
-                    fail = true;
-                    break;
-                }
-            }
-        }
-        if (match)  return mtype;
-        MethodType needType = mtype.asSpreaderType(arrayType, arrayLength);
-        if (!fail)  return needType;
-        // elicit an error:
-        this.asType(needType);
-        throw newInternalError("should not return", null);
-    }
-
-    private void spreadArrayChecks(Class<?> arrayType, int arrayLength) {
-        Class<?> arrayElement = arrayType.getComponentType();
-        if (arrayElement == null)
-            throw newIllegalArgumentException("not an array type", arrayType);
-        if ((arrayLength & 0x7F) != arrayLength) {
-            if ((arrayLength & 0xFF) != arrayLength)
-                throw newIllegalArgumentException("array length is not legal", arrayLength);
-            assert(arrayLength >= 128);
-            if (arrayElement == long.class ||
-                arrayElement == double.class)
-                throw newIllegalArgumentException("array length is not legal for long[] or double[]", arrayLength);
-        }
-    }
-
-    /**
-     * Makes an <em>array-collecting</em> method handle, which accepts a given number of trailing
-     * positional arguments and collects them into an array argument.
-     * The new method handle adapts, as its <i>target</i>,
-     * the current method handle.  The type of the adapter will be
-     * the same as the type of the target, except that a single trailing
-     * parameter (usually of type {@code arrayType}) is replaced by
-     * {@code arrayLength} parameters whose type is element type of {@code arrayType}.
-     * <p>
-     * If the array type differs from the final argument type on the original target,
-     * the original target is adapted to take the array type directly,
-     * as if by a call to {@link #asType asType}.
-     * <p>
-     * When called, the adapter replaces its trailing {@code arrayLength}
-     * arguments by a single new array of type {@code arrayType}, whose elements
-     * comprise (in order) the replaced arguments.
-     * Finally the target is called.
-     * What the target eventually returns is returned unchanged by the adapter.
-     * <p>
-     * (The array may also be a shared constant when {@code arrayLength} is zero.)
-     * <p>
-     * (<em>Note:</em> The {@code arrayType} is often identical to the last
-     * parameter type of the original target.
-     * It is an explicit argument for symmetry with {@code asSpreader}, and also
-     * to allow the target to use a simple {@code Object} as its last parameter type.)
-     * <p>
-     * In order to create a collecting adapter which is not restricted to a particular
-     * number of collected arguments, use {@link #asVarargsCollector asVarargsCollector} instead.
-     * <p>
-     * Here are some examples of array-collecting method handles:
-     * <blockquote><pre>{@code
-MethodHandle deepToString = publicLookup()
-  .findStatic(Arrays.class, "deepToString", methodType(String.class, Object[].class));
-assertEquals("[won]",   (String) deepToString.invokeExact(new Object[]{"won"}));
-MethodHandle ts1 = deepToString.asCollector(Object[].class, 1);
-assertEquals(methodType(String.class, Object.class), ts1.type());
-//assertEquals("[won]", (String) ts1.invokeExact(         new Object[]{"won"})); //FAIL
-assertEquals("[[won]]", (String) ts1.invokeExact((Object) new Object[]{"won"}));
-// arrayType can be a subtype of Object[]
-MethodHandle ts2 = deepToString.asCollector(String[].class, 2);
-assertEquals(methodType(String.class, String.class, String.class), ts2.type());
-assertEquals("[two, too]", (String) ts2.invokeExact("two", "too"));
-MethodHandle ts0 = deepToString.asCollector(Object[].class, 0);
-assertEquals("[]", (String) ts0.invokeExact());
-// collectors can be nested, Lisp-style
-MethodHandle ts22 = deepToString.asCollector(Object[].class, 3).asCollector(String[].class, 2);
-assertEquals("[A, B, [C, D]]", ((String) ts22.invokeExact((Object)'A', (Object)"B", "C", "D")));
-// arrayType can be any primitive array type
-MethodHandle bytesToString = publicLookup()
-  .findStatic(Arrays.class, "toString", methodType(String.class, byte[].class))
-  .asCollector(byte[].class, 3);
-assertEquals("[1, 2, 3]", (String) bytesToString.invokeExact((byte)1, (byte)2, (byte)3));
-MethodHandle longsToString = publicLookup()
-  .findStatic(Arrays.class, "toString", methodType(String.class, long[].class))
-  .asCollector(long[].class, 1);
-assertEquals("[123]", (String) longsToString.invokeExact((long)123));
-     * }</pre></blockquote>
-     * @param arrayType often {@code Object[]}, the type of the array argument which will collect the arguments
-     * @param arrayLength the number of arguments to collect into a new array argument
-     * @return a new method handle which collects some trailing argument
-     *         into an array, before calling the original method handle
-     * @throws NullPointerException if {@code arrayType} is a null reference
-     * @throws IllegalArgumentException if {@code arrayType} is not an array type
-     *         or {@code arrayType} is not assignable to this method handle's trailing parameter type,
-     *         or {@code arrayLength} is not a legal array size,
-     *         or the resulting method handle's type would have
-     *         <a href="MethodHandle.html#maxarity">too many parameters</a>
-     * @throws WrongMethodTypeException if the implied {@code asType} call fails
-     * @see #asSpreader
-     * @see #asVarargsCollector
-     */
-    public MethodHandle asCollector(Class<?> arrayType, int arrayLength) {
-        asCollectorChecks(arrayType, arrayLength);
-
-        return new Transformers.Collector(this, arrayType, arrayLength);
-    }
-
-    /**
-     * See if {@code asCollector} can be validly called with the given arguments.
-     * Return false if the last parameter is not an exact match to arrayType.
-     */
-    /*non-public*/ boolean asCollectorChecks(Class<?> arrayType, int arrayLength) {
-        spreadArrayChecks(arrayType, arrayLength);
-        int nargs = type().parameterCount();
-        if (nargs != 0) {
-            Class<?> lastParam = type().parameterType(nargs-1);
-            if (lastParam == arrayType)  return true;
-            if (lastParam.isAssignableFrom(arrayType))  return false;
-        }
-        throw newIllegalArgumentException("array type not assignable to trailing argument", this, arrayType);
-    }
-
-    /**
-     * Makes a <em>variable arity</em> adapter which is able to accept
-     * any number of trailing positional arguments and collect them
-     * into an array argument.
-     * <p>
-     * The type and behavior of the adapter will be the same as
-     * the type and behavior of the target, except that certain
-     * {@code invoke} and {@code asType} requests can lead to
-     * trailing positional arguments being collected into target's
-     * trailing parameter.
-     * Also, the last parameter type of the adapter will be
-     * {@code arrayType}, even if the target has a different
-     * last parameter type.
-     * <p>
-     * This transformation may return {@code this} if the method handle is
-     * already of variable arity and its trailing parameter type
-     * is identical to {@code arrayType}.
-     * <p>
-     * When called with {@link #invokeExact invokeExact}, the adapter invokes
-     * the target with no argument changes.
-     * (<em>Note:</em> This behavior is different from a
-     * {@linkplain #asCollector fixed arity collector},
-     * since it accepts a whole array of indeterminate length,
-     * rather than a fixed number of arguments.)
-     * <p>
-     * When called with plain, inexact {@link #invoke invoke}, if the caller
-     * type is the same as the adapter, the adapter invokes the target as with
-     * {@code invokeExact}.
-     * (This is the normal behavior for {@code invoke} when types match.)
-     * <p>
-     * Otherwise, if the caller and adapter arity are the same, and the
-     * trailing parameter type of the caller is a reference type identical to
-     * or assignable to the trailing parameter type of the adapter,
-     * the arguments and return values are converted pairwise,
-     * as if by {@link #asType asType} on a fixed arity
-     * method handle.
-     * <p>
-     * Otherwise, the arities differ, or the adapter's trailing parameter
-     * type is not assignable from the corresponding caller type.
-     * In this case, the adapter replaces all trailing arguments from
-     * the original trailing argument position onward, by
-     * a new array of type {@code arrayType}, whose elements
-     * comprise (in order) the replaced arguments.
-     * <p>
-     * The caller type must provides as least enough arguments,
-     * and of the correct type, to satisfy the target's requirement for
-     * positional arguments before the trailing array argument.
-     * Thus, the caller must supply, at a minimum, {@code N-1} arguments,
-     * where {@code N} is the arity of the target.
-     * Also, there must exist conversions from the incoming arguments
-     * to the target's arguments.
-     * As with other uses of plain {@code invoke}, if these basic
-     * requirements are not fulfilled, a {@code WrongMethodTypeException}
-     * may be thrown.
-     * <p>
-     * In all cases, what the target eventually returns is returned unchanged by the adapter.
-     * <p>
-     * In the final case, it is exactly as if the target method handle were
-     * temporarily adapted with a {@linkplain #asCollector fixed arity collector}
-     * to the arity required by the caller type.
-     * (As with {@code asCollector}, if the array length is zero,
-     * a shared constant may be used instead of a new array.
-     * If the implied call to {@code asCollector} would throw
-     * an {@code IllegalArgumentException} or {@code WrongMethodTypeException},
-     * the call to the variable arity adapter must throw
-     * {@code WrongMethodTypeException}.)
-     * <p>
-     * The behavior of {@link #asType asType} is also specialized for
-     * variable arity adapters, to maintain the invariant that
-     * plain, inexact {@code invoke} is always equivalent to an {@code asType}
-     * call to adjust the target type, followed by {@code invokeExact}.
-     * Therefore, a variable arity adapter responds
-     * to an {@code asType} request by building a fixed arity collector,
-     * if and only if the adapter and requested type differ either
-     * in arity or trailing argument type.
-     * The resulting fixed arity collector has its type further adjusted
-     * (if necessary) to the requested type by pairwise conversion,
-     * as if by another application of {@code asType}.
-     * <p>
-     * When a method handle is obtained by executing an {@code ldc} instruction
-     * of a {@code CONSTANT_MethodHandle} constant, and the target method is marked
-     * as a variable arity method (with the modifier bit {@code 0x0080}),
-     * the method handle will accept multiple arities, as if the method handle
-     * constant were created by means of a call to {@code asVarargsCollector}.
-     * <p>
-     * In order to create a collecting adapter which collects a predetermined
-     * number of arguments, and whose type reflects this predetermined number,
-     * use {@link #asCollector asCollector} instead.
-     * <p>
-     * No method handle transformations produce new method handles with
-     * variable arity, unless they are documented as doing so.
-     * Therefore, besides {@code asVarargsCollector},
-     * all methods in {@code MethodHandle} and {@code MethodHandles}
-     * will return a method handle with fixed arity,
-     * except in the cases where they are specified to return their original
-     * operand (e.g., {@code asType} of the method handle's own type).
-     * <p>
-     * Calling {@code asVarargsCollector} on a method handle which is already
-     * of variable arity will produce a method handle with the same type and behavior.
-     * It may (or may not) return the original variable arity method handle.
-     * <p>
-     * Here is an example, of a list-making variable arity method handle:
-     * <blockquote><pre>{@code
-MethodHandle deepToString = publicLookup()
-  .findStatic(Arrays.class, "deepToString", methodType(String.class, Object[].class));
-MethodHandle ts1 = deepToString.asVarargsCollector(Object[].class);
-assertEquals("[won]",   (String) ts1.invokeExact(    new Object[]{"won"}));
-assertEquals("[won]",   (String) ts1.invoke(         new Object[]{"won"}));
-assertEquals("[won]",   (String) ts1.invoke(                      "won" ));
-assertEquals("[[won]]", (String) ts1.invoke((Object) new Object[]{"won"}));
-// findStatic of Arrays.asList(...) produces a variable arity method handle:
-MethodHandle asList = publicLookup()
-  .findStatic(Arrays.class, "asList", methodType(List.class, Object[].class));
-assertEquals(methodType(List.class, Object[].class), asList.type());
-assert(asList.isVarargsCollector());
-assertEquals("[]", asList.invoke().toString());
-assertEquals("[1]", asList.invoke(1).toString());
-assertEquals("[two, too]", asList.invoke("two", "too").toString());
-String[] argv = { "three", "thee", "tee" };
-assertEquals("[three, thee, tee]", asList.invoke(argv).toString());
-assertEquals("[three, thee, tee]", asList.invoke((Object[])argv).toString());
-List ls = (List) asList.invoke((Object)argv);
-assertEquals(1, ls.size());
-assertEquals("[three, thee, tee]", Arrays.toString((Object[])ls.get(0)));
-     * }</pre></blockquote>
-     * <p style="font-size:smaller;">
-     * <em>Discussion:</em>
-     * These rules are designed as a dynamically-typed variation
-     * of the Java rules for variable arity methods.
-     * In both cases, callers to a variable arity method or method handle
-     * can either pass zero or more positional arguments, or else pass
-     * pre-collected arrays of any length.  Users should be aware of the
-     * special role of the final argument, and of the effect of a
-     * type match on that final argument, which determines whether
-     * or not a single trailing argument is interpreted as a whole
-     * array or a single element of an array to be collected.
-     * Note that the dynamic type of the trailing argument has no
-     * effect on this decision, only a comparison between the symbolic
-     * type descriptor of the call site and the type descriptor of the method handle.)
-     *
-     * @param arrayType often {@code Object[]}, the type of the array argument which will collect the arguments
-     * @return a new method handle which can collect any number of trailing arguments
-     *         into an array, before calling the original method handle
-     * @throws NullPointerException if {@code arrayType} is a null reference
-     * @throws IllegalArgumentException if {@code arrayType} is not an array type
-     *         or {@code arrayType} is not assignable to this method handle's trailing parameter type
-     * @see #asCollector
-     * @see #isVarargsCollector
-     * @see #asFixedArity
-     */
-    public MethodHandle asVarargsCollector(Class<?> arrayType) {
-        arrayType.getClass(); // explicit NPE
-        boolean lastMatch = asCollectorChecks(arrayType, 0);
-        if (isVarargsCollector() && lastMatch)
-            return this;
-
-        return new Transformers.VarargsCollector(this);
-    }
-
-    /**
-     * Determines if this method handle
-     * supports {@linkplain #asVarargsCollector variable arity} calls.
-     * Such method handles arise from the following sources:
-     * <ul>
-     * <li>a call to {@linkplain #asVarargsCollector asVarargsCollector}
-     * <li>a call to a {@linkplain java.lang.invoke.MethodHandles.Lookup lookup method}
-     *     which resolves to a variable arity Java method or constructor
-     * <li>an {@code ldc} instruction of a {@code CONSTANT_MethodHandle}
-     *     which resolves to a variable arity Java method or constructor
-     * </ul>
-     * @return true if this method handle accepts more than one arity of plain, inexact {@code invoke} calls
-     * @see #asVarargsCollector
-     * @see #asFixedArity
-     */
-    public boolean isVarargsCollector() {
-        return false;
-    }
-
-    /**
-     * Makes a <em>fixed arity</em> method handle which is otherwise
-     * equivalent to the current method handle.
-     * <p>
-     * If the current method handle is not of
-     * {@linkplain #asVarargsCollector variable arity},
-     * the current method handle is returned.
-     * This is true even if the current method handle
-     * could not be a valid input to {@code asVarargsCollector}.
-     * <p>
-     * Otherwise, the resulting fixed-arity method handle has the same
-     * type and behavior of the current method handle,
-     * except that {@link #isVarargsCollector isVarargsCollector}
-     * will be false.
-     * The fixed-arity method handle may (or may not) be the
-     * a previous argument to {@code asVarargsCollector}.
-     * <p>
-     * Here is an example, of a list-making variable arity method handle:
-     * <blockquote><pre>{@code
-MethodHandle asListVar = publicLookup()
-  .findStatic(Arrays.class, "asList", methodType(List.class, Object[].class))
-  .asVarargsCollector(Object[].class);
-MethodHandle asListFix = asListVar.asFixedArity();
-assertEquals("[1]", asListVar.invoke(1).toString());
-Exception caught = null;
-try { asListFix.invoke((Object)1); }
-catch (Exception ex) { caught = ex; }
-assert(caught instanceof ClassCastException);
-assertEquals("[two, too]", asListVar.invoke("two", "too").toString());
-try { asListFix.invoke("two", "too"); }
-catch (Exception ex) { caught = ex; }
-assert(caught instanceof WrongMethodTypeException);
-Object[] argv = { "three", "thee", "tee" };
-assertEquals("[three, thee, tee]", asListVar.invoke(argv).toString());
-assertEquals("[three, thee, tee]", asListFix.invoke(argv).toString());
-assertEquals(1, ((List) asListVar.invoke((Object)argv)).size());
-assertEquals("[three, thee, tee]", asListFix.invoke((Object)argv).toString());
-     * }</pre></blockquote>
-     *
-     * @return a new method handle which accepts only a fixed number of arguments
-     * @see #asVarargsCollector
-     * @see #isVarargsCollector
-     */
-    public MethodHandle asFixedArity() {
-        // Android-changed: implementation specific.
-        MethodHandle mh = this;
-        if (mh.isVarargsCollector()) {
-            mh = ((Transformers.VarargsCollector) mh).asFixedArity();
-        }
-        assert(!mh.isVarargsCollector());
-        return mh;
-    }
-
-    /**
-     * Binds a value {@code x} to the first argument of a method handle, without invoking it.
-     * The new method handle adapts, as its <i>target</i>,
-     * the current method handle by binding it to the given argument.
-     * The type of the bound handle will be
-     * the same as the type of the target, except that a single leading
-     * reference parameter will be omitted.
-     * <p>
-     * When called, the bound handle inserts the given value {@code x}
-     * as a new leading argument to the target.  The other arguments are
-     * also passed unchanged.
-     * What the target eventually returns is returned unchanged by the bound handle.
-     * <p>
-     * The reference {@code x} must be convertible to the first parameter
-     * type of the target.
-     * <p>
-     * (<em>Note:</em>  Because method handles are immutable, the target method handle
-     * retains its original type and behavior.)
-     * @param x  the value to bind to the first argument of the target
-     * @return a new method handle which prepends the given value to the incoming
-     *         argument list, before calling the original method handle
-     * @throws IllegalArgumentException if the target does not have a
-     *         leading parameter type that is a reference type
-     * @throws ClassCastException if {@code x} cannot be converted
-     *         to the leading parameter type of the target
-     * @see MethodHandles#insertArguments
-     */
-    public MethodHandle bindTo(Object x) {
-        x = type.leadingReferenceParameter().cast(x);  // throw CCE if needed
-
-        return new Transformers.BindTo(this, x);
-    }
-
-    /**
-     * Returns a string representation of the method handle,
-     * starting with the string {@code "MethodHandle"} and
-     * ending with the string representation of the method handle's type.
-     * In other words, this method returns a string equal to the value of:
-     * <blockquote><pre>{@code
-     * "MethodHandle" + type().toString()
-     * }</pre></blockquote>
-     * <p>
-     * (<em>Note:</em>  Future releases of this API may add further information
-     * to the string representation.
-     * Therefore, the present syntax should not be parsed by applications.)
-     *
-     * @return a string representation of the method handle
-     */
-    @Override
-    public String toString() {
-        // Android-changed: Removed debugging support.
-        return "MethodHandle"+type;
-    }
-
-    /** @hide */
-    public int getHandleKind() {
-        return handleKind;
-    }
-
-    /** @hide */
-    protected void transform(EmulatedStackFrame arguments) throws Throwable {
-        throw new AssertionError("MethodHandle.transform should never be called.");
-    }
-
-    /**
-     * Creates a copy of this method handle, copying all relevant data.
-     *
-     * @hide
-     */
-    protected MethodHandle duplicate() {
-        try {
-            return (MethodHandle) this.clone();
-        } catch (CloneNotSupportedException cnse) {
-            throw new AssertionError("Subclass of Transformer is not cloneable");
-        }
-    }
-
-
-    /**
-     * This is the entry point for all transform calls, and dispatches to the protected
-     * transform method. This layer of indirection exists purely for convenience, because
-     * we can invoke-direct on a fixed ArtMethod for all transform variants.
-     *
-     * NOTE: If this extra layer of indirection proves to be a problem, we can get rid
-     * of this layer of indirection at the cost of some additional ugliness.
-     */
-    private void transformInternal(EmulatedStackFrame arguments) throws Throwable {
-        transform(arguments);
-    }
-
-    // Android-changed: Removed implementation details :
-    //
-    // String standardString();
-    // String debugString();
-    //
-    //// Implementation methods.
-    //// Sub-classes can override these default implementations.
-    //// All these methods assume arguments are already validated.
-    //
-    // Other transforms to do:  convert, explicitCast, permute, drop, filter, fold, GWT, catch
-    //
-    // BoundMethodHandle bindArgumentL(int pos, Object value);
-    // /*non-public*/ MethodHandle setVarargs(MemberName member);
-    // /*non-public*/ MethodHandle viewAsType(MethodType newType, boolean strict);
-    // /*non-public*/ boolean viewAsTypeChecks(MethodType newType, boolean strict);
-    //
-    // Decoding
-    //
-    // /*non-public*/ LambdaForm internalForm();
-    // /*non-public*/ MemberName internalMemberName();
-    // /*non-public*/ Class<?> internalCallerClass();
-    // /*non-public*/ MethodHandleImpl.Intrinsic intrinsicName();
-    // /*non-public*/ MethodHandle withInternalMemberName(MemberName member, boolean isInvokeSpecial);
-    // /*non-public*/ boolean isInvokeSpecial();
-    // /*non-public*/ Object internalValues();
-    // /*non-public*/ Object internalProperties();
-    //
-    //// Method handle implementation methods.
-    //// Sub-classes can override these default implementations.
-    //// All these methods assume arguments are already validated.
-    //
-    // /*non-public*/ abstract MethodHandle copyWith(MethodType mt, LambdaForm lf);
-    // abstract BoundMethodHandle rebind();
-    // /*non-public*/ void updateForm(LambdaForm newForm);
-    // /*non-public*/ void customize();
-    // private static final long FORM_OFFSET;
 }
diff --git a/java/lang/invoke/MethodHandles.java b/java/lang/invoke/MethodHandles.java
index 88ce6e0..f27ad98 100644
--- a/java/lang/invoke/MethodHandles.java
+++ b/java/lang/invoke/MethodHandles.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2008, 2013, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -25,3410 +25,129 @@
 
 package java.lang.invoke;
 
-import java.lang.reflect.*;
-import java.nio.ByteOrder;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Member;
+import java.lang.reflect.Method;
 import java.util.List;
-import java.util.Arrays;
-import java.util.ArrayList;
-import java.util.NoSuchElementException;
 
-import dalvik.system.VMStack;
-import sun.invoke.util.VerifyAccess;
-import sun.invoke.util.Wrapper;
-import static java.lang.invoke.MethodHandleStatics.*;
-
-/**
- * This class consists exclusively of static methods that operate on or return
- * method handles. They fall into several categories:
- * <ul>
- * <li>Lookup methods which help create method handles for methods and fields.
- * <li>Combinator methods, which combine or transform pre-existing method handles into new ones.
- * <li>Other factory methods to create method handles that emulate other common JVM operations or control flow patterns.
- * </ul>
- * <p>
- * @author John Rose, JSR 292 EG
- * @since 1.7
- */
 public class MethodHandles {
 
-    private MethodHandles() { }  // do not instantiate
+    public static Lookup lookup() { return null; }
 
-    // BEGIN Android-added: unsupported() helper function.
-    // TODO(b/65872996): Remove when complete.
-    private static void unsupported(String msg) throws UnsupportedOperationException {
-        throw new UnsupportedOperationException(msg);
-    }
-    // END Android-added: unsupported() helper function.
+    public static Lookup publicLookup() { return null; }
 
-    // Android-changed: We do not use MemberName / MethodHandleImpl.
-    //
-    // private static final MemberName.Factory IMPL_NAMES = MemberName.getFactory();
-    // static { MethodHandleImpl.initStatics(); }
-    // See IMPL_LOOKUP below.
-
-    //// Method handle creation from ordinary methods.
-
-    /**
-     * Returns a {@link Lookup lookup object} with
-     * full capabilities to emulate all supported bytecode behaviors of the caller.
-     * These capabilities include <a href="MethodHandles.Lookup.html#privacc">private access</a> to the caller.
-     * Factory methods on the lookup object can create
-     * <a href="MethodHandleInfo.html#directmh">direct method handles</a>
-     * for any member that the caller has access to via bytecodes,
-     * including protected and private fields and methods.
-     * This lookup object is a <em>capability</em> which may be delegated to trusted agents.
-     * Do not store it in place where untrusted code can access it.
-     * <p>
-     * This method is caller sensitive, which means that it may return different
-     * values to different callers.
-     * <p>
-     * For any given caller class {@code C}, the lookup object returned by this call
-     * has equivalent capabilities to any lookup object
-     * supplied by the JVM to the bootstrap method of an
-     * <a href="package-summary.html#indyinsn">invokedynamic instruction</a>
-     * executing in the same caller class {@code C}.
-     * @return a lookup object for the caller of this method, with private access
-     */
-    // Android-changed: Remove caller sensitive.
-    // @CallerSensitive
-    public static Lookup lookup() {
-        // Android-changed: Do not use Reflection.getCallerClass().
-        return new Lookup(VMStack.getStackClass1());
-    }
-
-    /**
-     * Returns a {@link Lookup lookup object} which is trusted minimally.
-     * It can only be used to create method handles to
-     * publicly accessible fields and methods.
-     * <p>
-     * As a matter of pure convention, the {@linkplain Lookup#lookupClass lookup class}
-     * of this lookup object will be {@link java.lang.Object}.
-     *
-     * <p style="font-size:smaller;">
-     * <em>Discussion:</em>
-     * The lookup class can be changed to any other class {@code C} using an expression of the form
-     * {@link Lookup#in publicLookup().in(C.class)}.
-     * Since all classes have equal access to public names,
-     * such a change would confer no new access rights.
-     * A public lookup object is always subject to
-     * <a href="MethodHandles.Lookup.html#secmgr">security manager checks</a>.
-     * Also, it cannot access
-     * <a href="MethodHandles.Lookup.html#callsens">caller sensitive methods</a>.
-     * @return a lookup object which is trusted minimally
-     */
-    public static Lookup publicLookup() {
-        return Lookup.PUBLIC_LOOKUP;
-    }
-
-    /**
-     * Performs an unchecked "crack" of a
-     * <a href="MethodHandleInfo.html#directmh">direct method handle</a>.
-     * The result is as if the user had obtained a lookup object capable enough
-     * to crack the target method handle, called
-     * {@link java.lang.invoke.MethodHandles.Lookup#revealDirect Lookup.revealDirect}
-     * on the target to obtain its symbolic reference, and then called
-     * {@link java.lang.invoke.MethodHandleInfo#reflectAs MethodHandleInfo.reflectAs}
-     * to resolve the symbolic reference to a member.
-     * <p>
-     * If there is a security manager, its {@code checkPermission} method
-     * is called with a {@code ReflectPermission("suppressAccessChecks")} permission.
-     * @param <T> the desired type of the result, either {@link Member} or a subtype
-     * @param target a direct method handle to crack into symbolic reference components
-     * @param expected a class object representing the desired result type {@code T}
-     * @return a reference to the method, constructor, or field object
-     * @exception SecurityException if the caller is not privileged to call {@code setAccessible}
-     * @exception NullPointerException if either argument is {@code null}
-     * @exception IllegalArgumentException if the target is not a direct method handle
-     * @exception ClassCastException if the member is not of the expected type
-     * @since 1.8
-     */
     public static <T extends Member> T
-    reflectAs(Class<T> expected, MethodHandle target) {
-        MethodHandleImpl directTarget = getMethodHandleImpl(target);
-        // Given that this is specified to be an "unchecked" crack, we can directly allocate
-        // a member from the underlying ArtField / Method and bypass all associated access checks.
-        return expected.cast(directTarget.getMemberInternal());
-    }
+    reflectAs(Class<T> expected, MethodHandle target) { return null; }
 
-    /**
-     * A <em>lookup object</em> is a factory for creating method handles,
-     * when the creation requires access checking.
-     * Method handles do not perform
-     * access checks when they are called, but rather when they are created.
-     * Therefore, method handle access
-     * restrictions must be enforced when a method handle is created.
-     * The caller class against which those restrictions are enforced
-     * is known as the {@linkplain #lookupClass lookup class}.
-     * <p>
-     * A lookup class which needs to create method handles will call
-     * {@link #lookup MethodHandles.lookup} to create a factory for itself.
-     * When the {@code Lookup} factory object is created, the identity of the lookup class is
-     * determined, and securely stored in the {@code Lookup} object.
-     * The lookup class (or its delegates) may then use factory methods
-     * on the {@code Lookup} object to create method handles for access-checked members.
-     * This includes all methods, constructors, and fields which are allowed to the lookup class,
-     * even private ones.
-     *
-     * <h1><a name="lookups"></a>Lookup Factory Methods</h1>
-     * The factory methods on a {@code Lookup} object correspond to all major
-     * use cases for methods, constructors, and fields.
-     * Each method handle created by a factory method is the functional
-     * equivalent of a particular <em>bytecode behavior</em>.
-     * (Bytecode behaviors are described in section 5.4.3.5 of the Java Virtual Machine Specification.)
-     * Here is a summary of the correspondence between these factory methods and
-     * the behavior the resulting method handles:
-     * <table border=1 cellpadding=5 summary="lookup method behaviors">
-     * <tr>
-     *     <th><a name="equiv"></a>lookup expression</th>
-     *     <th>member</th>
-     *     <th>bytecode behavior</th>
-     * </tr>
-     * <tr>
-     *     <td>{@link java.lang.invoke.MethodHandles.Lookup#findGetter lookup.findGetter(C.class,"f",FT.class)}</td>
-     *     <td>{@code FT f;}</td><td>{@code (T) this.f;}</td>
-     * </tr>
-     * <tr>
-     *     <td>{@link java.lang.invoke.MethodHandles.Lookup#findStaticGetter lookup.findStaticGetter(C.class,"f",FT.class)}</td>
-     *     <td>{@code static}<br>{@code FT f;}</td><td>{@code (T) C.f;}</td>
-     * </tr>
-     * <tr>
-     *     <td>{@link java.lang.invoke.MethodHandles.Lookup#findSetter lookup.findSetter(C.class,"f",FT.class)}</td>
-     *     <td>{@code FT f;}</td><td>{@code this.f = x;}</td>
-     * </tr>
-     * <tr>
-     *     <td>{@link java.lang.invoke.MethodHandles.Lookup#findStaticSetter lookup.findStaticSetter(C.class,"f",FT.class)}</td>
-     *     <td>{@code static}<br>{@code FT f;}</td><td>{@code C.f = arg;}</td>
-     * </tr>
-     * <tr>
-     *     <td>{@link java.lang.invoke.MethodHandles.Lookup#findVirtual lookup.findVirtual(C.class,"m",MT)}</td>
-     *     <td>{@code T m(A*);}</td><td>{@code (T) this.m(arg*);}</td>
-     * </tr>
-     * <tr>
-     *     <td>{@link java.lang.invoke.MethodHandles.Lookup#findStatic lookup.findStatic(C.class,"m",MT)}</td>
-     *     <td>{@code static}<br>{@code T m(A*);}</td><td>{@code (T) C.m(arg*);}</td>
-     * </tr>
-     * <tr>
-     *     <td>{@link java.lang.invoke.MethodHandles.Lookup#findSpecial lookup.findSpecial(C.class,"m",MT,this.class)}</td>
-     *     <td>{@code T m(A*);}</td><td>{@code (T) super.m(arg*);}</td>
-     * </tr>
-     * <tr>
-     *     <td>{@link java.lang.invoke.MethodHandles.Lookup#findConstructor lookup.findConstructor(C.class,MT)}</td>
-     *     <td>{@code C(A*);}</td><td>{@code new C(arg*);}</td>
-     * </tr>
-     * <tr>
-     *     <td>{@link java.lang.invoke.MethodHandles.Lookup#unreflectGetter lookup.unreflectGetter(aField)}</td>
-     *     <td>({@code static})?<br>{@code FT f;}</td><td>{@code (FT) aField.get(thisOrNull);}</td>
-     * </tr>
-     * <tr>
-     *     <td>{@link java.lang.invoke.MethodHandles.Lookup#unreflectSetter lookup.unreflectSetter(aField)}</td>
-     *     <td>({@code static})?<br>{@code FT f;}</td><td>{@code aField.set(thisOrNull, arg);}</td>
-     * </tr>
-     * <tr>
-     *     <td>{@link java.lang.invoke.MethodHandles.Lookup#unreflect lookup.unreflect(aMethod)}</td>
-     *     <td>({@code static})?<br>{@code T m(A*);}</td><td>{@code (T) aMethod.invoke(thisOrNull, arg*);}</td>
-     * </tr>
-     * <tr>
-     *     <td>{@link java.lang.invoke.MethodHandles.Lookup#unreflectConstructor lookup.unreflectConstructor(aConstructor)}</td>
-     *     <td>{@code C(A*);}</td><td>{@code (C) aConstructor.newInstance(arg*);}</td>
-     * </tr>
-     * <tr>
-     *     <td>{@link java.lang.invoke.MethodHandles.Lookup#unreflect lookup.unreflect(aMethod)}</td>
-     *     <td>({@code static})?<br>{@code T m(A*);}</td><td>{@code (T) aMethod.invoke(thisOrNull, arg*);}</td>
-     * </tr>
-     * </table>
-     *
-     * Here, the type {@code C} is the class or interface being searched for a member,
-     * documented as a parameter named {@code refc} in the lookup methods.
-     * The method type {@code MT} is composed from the return type {@code T}
-     * and the sequence of argument types {@code A*}.
-     * The constructor also has a sequence of argument types {@code A*} and
-     * is deemed to return the newly-created object of type {@code C}.
-     * Both {@code MT} and the field type {@code FT} are documented as a parameter named {@code type}.
-     * The formal parameter {@code this} stands for the self-reference of type {@code C};
-     * if it is present, it is always the leading argument to the method handle invocation.
-     * (In the case of some {@code protected} members, {@code this} may be
-     * restricted in type to the lookup class; see below.)
-     * The name {@code arg} stands for all the other method handle arguments.
-     * In the code examples for the Core Reflection API, the name {@code thisOrNull}
-     * stands for a null reference if the accessed method or field is static,
-     * and {@code this} otherwise.
-     * The names {@code aMethod}, {@code aField}, and {@code aConstructor} stand
-     * for reflective objects corresponding to the given members.
-     * <p>
-     * In cases where the given member is of variable arity (i.e., a method or constructor)
-     * the returned method handle will also be of {@linkplain MethodHandle#asVarargsCollector variable arity}.
-     * In all other cases, the returned method handle will be of fixed arity.
-     * <p style="font-size:smaller;">
-     * <em>Discussion:</em>
-     * The equivalence between looked-up method handles and underlying
-     * class members and bytecode behaviors
-     * can break down in a few ways:
-     * <ul style="font-size:smaller;">
-     * <li>If {@code C} is not symbolically accessible from the lookup class's loader,
-     * the lookup can still succeed, even when there is no equivalent
-     * Java expression or bytecoded constant.
-     * <li>Likewise, if {@code T} or {@code MT}
-     * is not symbolically accessible from the lookup class's loader,
-     * the lookup can still succeed.
-     * For example, lookups for {@code MethodHandle.invokeExact} and
-     * {@code MethodHandle.invoke} will always succeed, regardless of requested type.
-     * <li>If there is a security manager installed, it can forbid the lookup
-     * on various grounds (<a href="MethodHandles.Lookup.html#secmgr">see below</a>).
-     * By contrast, the {@code ldc} instruction on a {@code CONSTANT_MethodHandle}
-     * constant is not subject to security manager checks.
-     * <li>If the looked-up method has a
-     * <a href="MethodHandle.html#maxarity">very large arity</a>,
-     * the method handle creation may fail, due to the method handle
-     * type having too many parameters.
-     * </ul>
-     *
-     * <h1><a name="access"></a>Access checking</h1>
-     * Access checks are applied in the factory methods of {@code Lookup},
-     * when a method handle is created.
-     * This is a key difference from the Core Reflection API, since
-     * {@link java.lang.reflect.Method#invoke java.lang.reflect.Method.invoke}
-     * performs access checking against every caller, on every call.
-     * <p>
-     * All access checks start from a {@code Lookup} object, which
-     * compares its recorded lookup class against all requests to
-     * create method handles.
-     * A single {@code Lookup} object can be used to create any number
-     * of access-checked method handles, all checked against a single
-     * lookup class.
-     * <p>
-     * A {@code Lookup} object can be shared with other trusted code,
-     * such as a metaobject protocol.
-     * A shared {@code Lookup} object delegates the capability
-     * to create method handles on private members of the lookup class.
-     * Even if privileged code uses the {@code Lookup} object,
-     * the access checking is confined to the privileges of the
-     * original lookup class.
-     * <p>
-     * A lookup can fail, because
-     * the containing class is not accessible to the lookup class, or
-     * because the desired class member is missing, or because the
-     * desired class member is not accessible to the lookup class, or
-     * because the lookup object is not trusted enough to access the member.
-     * In any of these cases, a {@code ReflectiveOperationException} will be
-     * thrown from the attempted lookup.  The exact class will be one of
-     * the following:
-     * <ul>
-     * <li>NoSuchMethodException &mdash; if a method is requested but does not exist
-     * <li>NoSuchFieldException &mdash; if a field is requested but does not exist
-     * <li>IllegalAccessException &mdash; if the member exists but an access check fails
-     * </ul>
-     * <p>
-     * In general, the conditions under which a method handle may be
-     * looked up for a method {@code M} are no more restrictive than the conditions
-     * under which the lookup class could have compiled, verified, and resolved a call to {@code M}.
-     * Where the JVM would raise exceptions like {@code NoSuchMethodError},
-     * a method handle lookup will generally raise a corresponding
-     * checked exception, such as {@code NoSuchMethodException}.
-     * And the effect of invoking the method handle resulting from the lookup
-     * is <a href="MethodHandles.Lookup.html#equiv">exactly equivalent</a>
-     * to executing the compiled, verified, and resolved call to {@code M}.
-     * The same point is true of fields and constructors.
-     * <p style="font-size:smaller;">
-     * <em>Discussion:</em>
-     * Access checks only apply to named and reflected methods,
-     * constructors, and fields.
-     * Other method handle creation methods, such as
-     * {@link MethodHandle#asType MethodHandle.asType},
-     * do not require any access checks, and are used
-     * independently of any {@code Lookup} object.
-     * <p>
-     * If the desired member is {@code protected}, the usual JVM rules apply,
-     * including the requirement that the lookup class must be either be in the
-     * same package as the desired member, or must inherit that member.
-     * (See the Java Virtual Machine Specification, sections 4.9.2, 5.4.3.5, and 6.4.)
-     * In addition, if the desired member is a non-static field or method
-     * in a different package, the resulting method handle may only be applied
-     * to objects of the lookup class or one of its subclasses.
-     * This requirement is enforced by narrowing the type of the leading
-     * {@code this} parameter from {@code C}
-     * (which will necessarily be a superclass of the lookup class)
-     * to the lookup class itself.
-     * <p>
-     * The JVM imposes a similar requirement on {@code invokespecial} instruction,
-     * that the receiver argument must match both the resolved method <em>and</em>
-     * the current class.  Again, this requirement is enforced by narrowing the
-     * type of the leading parameter to the resulting method handle.
-     * (See the Java Virtual Machine Specification, section 4.10.1.9.)
-     * <p>
-     * The JVM represents constructors and static initializer blocks as internal methods
-     * with special names ({@code "<init>"} and {@code "<clinit>"}).
-     * The internal syntax of invocation instructions allows them to refer to such internal
-     * methods as if they were normal methods, but the JVM bytecode verifier rejects them.
-     * A lookup of such an internal method will produce a {@code NoSuchMethodException}.
-     * <p>
-     * In some cases, access between nested classes is obtained by the Java compiler by creating
-     * an wrapper method to access a private method of another class
-     * in the same top-level declaration.
-     * For example, a nested class {@code C.D}
-     * can access private members within other related classes such as
-     * {@code C}, {@code C.D.E}, or {@code C.B},
-     * but the Java compiler may need to generate wrapper methods in
-     * those related classes.  In such cases, a {@code Lookup} object on
-     * {@code C.E} would be unable to those private members.
-     * A workaround for this limitation is the {@link Lookup#in Lookup.in} method,
-     * which can transform a lookup on {@code C.E} into one on any of those other
-     * classes, without special elevation of privilege.
-     * <p>
-     * The accesses permitted to a given lookup object may be limited,
-     * according to its set of {@link #lookupModes lookupModes},
-     * to a subset of members normally accessible to the lookup class.
-     * For example, the {@link #publicLookup publicLookup}
-     * method produces a lookup object which is only allowed to access
-     * public members in public classes.
-     * The caller sensitive method {@link #lookup lookup}
-     * produces a lookup object with full capabilities relative to
-     * its caller class, to emulate all supported bytecode behaviors.
-     * Also, the {@link Lookup#in Lookup.in} method may produce a lookup object
-     * with fewer access modes than the original lookup object.
-     *
-     * <p style="font-size:smaller;">
-     * <a name="privacc"></a>
-     * <em>Discussion of private access:</em>
-     * We say that a lookup has <em>private access</em>
-     * if its {@linkplain #lookupModes lookup modes}
-     * include the possibility of accessing {@code private} members.
-     * As documented in the relevant methods elsewhere,
-     * only lookups with private access possess the following capabilities:
-     * <ul style="font-size:smaller;">
-     * <li>access private fields, methods, and constructors of the lookup class
-     * <li>create method handles which invoke <a href="MethodHandles.Lookup.html#callsens">caller sensitive</a> methods,
-     *     such as {@code Class.forName}
-     * <li>create method handles which {@link Lookup#findSpecial emulate invokespecial} instructions
-     * <li>avoid <a href="MethodHandles.Lookup.html#secmgr">package access checks</a>
-     *     for classes accessible to the lookup class
-     * <li>create {@link Lookup#in delegated lookup objects} which have private access to other classes
-     *     within the same package member
-     * </ul>
-     * <p style="font-size:smaller;">
-     * Each of these permissions is a consequence of the fact that a lookup object
-     * with private access can be securely traced back to an originating class,
-     * whose <a href="MethodHandles.Lookup.html#equiv">bytecode behaviors</a> and Java language access permissions
-     * can be reliably determined and emulated by method handles.
-     *
-     * <h1><a name="secmgr"></a>Security manager interactions</h1>
-     * Although bytecode instructions can only refer to classes in
-     * a related class loader, this API can search for methods in any
-     * class, as long as a reference to its {@code Class} object is
-     * available.  Such cross-loader references are also possible with the
-     * Core Reflection API, and are impossible to bytecode instructions
-     * such as {@code invokestatic} or {@code getfield}.
-     * There is a {@linkplain java.lang.SecurityManager security manager API}
-     * to allow applications to check such cross-loader references.
-     * These checks apply to both the {@code MethodHandles.Lookup} API
-     * and the Core Reflection API
-     * (as found on {@link java.lang.Class Class}).
-     * <p>
-     * If a security manager is present, member lookups are subject to
-     * additional checks.
-     * From one to three calls are made to the security manager.
-     * Any of these calls can refuse access by throwing a
-     * {@link java.lang.SecurityException SecurityException}.
-     * Define {@code smgr} as the security manager,
-     * {@code lookc} as the lookup class of the current lookup object,
-     * {@code refc} as the containing class in which the member
-     * is being sought, and {@code defc} as the class in which the
-     * member is actually defined.
-     * The value {@code lookc} is defined as <em>not present</em>
-     * if the current lookup object does not have
-     * <a href="MethodHandles.Lookup.html#privacc">private access</a>.
-     * The calls are made according to the following rules:
-     * <ul>
-     * <li><b>Step 1:</b>
-     *     If {@code lookc} is not present, or if its class loader is not
-     *     the same as or an ancestor of the class loader of {@code refc},
-     *     then {@link SecurityManager#checkPackageAccess
-     *     smgr.checkPackageAccess(refcPkg)} is called,
-     *     where {@code refcPkg} is the package of {@code refc}.
-     * <li><b>Step 2:</b>
-     *     If the retrieved member is not public and
-     *     {@code lookc} is not present, then
-     *     {@link SecurityManager#checkPermission smgr.checkPermission}
-     *     with {@code RuntimePermission("accessDeclaredMembers")} is called.
-     * <li><b>Step 3:</b>
-     *     If the retrieved member is not public,
-     *     and if {@code lookc} is not present,
-     *     and if {@code defc} and {@code refc} are different,
-     *     then {@link SecurityManager#checkPackageAccess
-     *     smgr.checkPackageAccess(defcPkg)} is called,
-     *     where {@code defcPkg} is the package of {@code defc}.
-     * </ul>
-     * Security checks are performed after other access checks have passed.
-     * Therefore, the above rules presuppose a member that is public,
-     * or else that is being accessed from a lookup class that has
-     * rights to access the member.
-     *
-     * <h1><a name="callsens"></a>Caller sensitive methods</h1>
-     * A small number of Java methods have a special property called caller sensitivity.
-     * A <em>caller-sensitive</em> method can behave differently depending on the
-     * identity of its immediate caller.
-     * <p>
-     * If a method handle for a caller-sensitive method is requested,
-     * the general rules for <a href="MethodHandles.Lookup.html#equiv">bytecode behaviors</a> apply,
-     * but they take account of the lookup class in a special way.
-     * The resulting method handle behaves as if it were called
-     * from an instruction contained in the lookup class,
-     * so that the caller-sensitive method detects the lookup class.
-     * (By contrast, the invoker of the method handle is disregarded.)
-     * Thus, in the case of caller-sensitive methods,
-     * different lookup classes may give rise to
-     * differently behaving method handles.
-     * <p>
-     * In cases where the lookup object is
-     * {@link #publicLookup publicLookup()},
-     * or some other lookup object without
-     * <a href="MethodHandles.Lookup.html#privacc">private access</a>,
-     * the lookup class is disregarded.
-     * In such cases, no caller-sensitive method handle can be created,
-     * access is forbidden, and the lookup fails with an
-     * {@code IllegalAccessException}.
-     * <p style="font-size:smaller;">
-     * <em>Discussion:</em>
-     * For example, the caller-sensitive method
-     * {@link java.lang.Class#forName(String) Class.forName(x)}
-     * can return varying classes or throw varying exceptions,
-     * depending on the class loader of the class that calls it.
-     * A public lookup of {@code Class.forName} will fail, because
-     * there is no reasonable way to determine its bytecode behavior.
-     * <p style="font-size:smaller;">
-     * If an application caches method handles for broad sharing,
-     * it should use {@code publicLookup()} to create them.
-     * If there is a lookup of {@code Class.forName}, it will fail,
-     * and the application must take appropriate action in that case.
-     * It may be that a later lookup, perhaps during the invocation of a
-     * bootstrap method, can incorporate the specific identity
-     * of the caller, making the method accessible.
-     * <p style="font-size:smaller;">
-     * The function {@code MethodHandles.lookup} is caller sensitive
-     * so that there can be a secure foundation for lookups.
-     * Nearly all other methods in the JSR 292 API rely on lookup
-     * objects to check access requests.
-     */
-    // Android-changed: Change link targets from MethodHandles#[public]Lookup to
-    // #[public]Lookup to work around complaints from javadoc.
     public static final
     class Lookup {
-        /** The class on behalf of whom the lookup is being performed. */
-        /* @NonNull */ private final Class<?> lookupClass;
+        public static final int PUBLIC = 0;
 
-        /** The allowed sorts of members which may be looked up (PUBLIC, etc.). */
-        private final int allowedModes;
+        public static final int PRIVATE = 0;
 
-        /** A single-bit mask representing {@code public} access,
-         *  which may contribute to the result of {@link #lookupModes lookupModes}.
-         *  The value, {@code 0x01}, happens to be the same as the value of the
-         *  {@code public} {@linkplain java.lang.reflect.Modifier#PUBLIC modifier bit}.
-         */
-        public static final int PUBLIC = Modifier.PUBLIC;
+        public static final int PROTECTED = 0;
 
-        /** A single-bit mask representing {@code private} access,
-         *  which may contribute to the result of {@link #lookupModes lookupModes}.
-         *  The value, {@code 0x02}, happens to be the same as the value of the
-         *  {@code private} {@linkplain java.lang.reflect.Modifier#PRIVATE modifier bit}.
-         */
-        public static final int PRIVATE = Modifier.PRIVATE;
+        public static final int PACKAGE =  0;
 
-        /** A single-bit mask representing {@code protected} access,
-         *  which may contribute to the result of {@link #lookupModes lookupModes}.
-         *  The value, {@code 0x04}, happens to be the same as the value of the
-         *  {@code protected} {@linkplain java.lang.reflect.Modifier#PROTECTED modifier bit}.
-         */
-        public static final int PROTECTED = Modifier.PROTECTED;
+        public Class<?> lookupClass() { return null; }
 
-        /** A single-bit mask representing {@code package} access (default access),
-         *  which may contribute to the result of {@link #lookupModes lookupModes}.
-         *  The value is {@code 0x08}, which does not correspond meaningfully to
-         *  any particular {@linkplain java.lang.reflect.Modifier modifier bit}.
-         */
-        public static final int PACKAGE = Modifier.STATIC;
+        public int lookupModes() { return 0; }
 
-        private static final int ALL_MODES = (PUBLIC | PRIVATE | PROTECTED | PACKAGE);
+        public Lookup in(Class<?> requestedLookupClass) { return null; }
 
-        // Android-note: Android has no notion of a trusted lookup. If required, such lookups
-        // are performed by the runtime. As a result, we always use lookupClass, which will always
-        // be non-null in our implementation.
-        //
-        // private static final int TRUSTED   = -1;
-
-        private static int fixmods(int mods) {
-            mods &= (ALL_MODES - PACKAGE);
-            return (mods != 0) ? mods : PACKAGE;
-        }
-
-        /** Tells which class is performing the lookup.  It is this class against
-         *  which checks are performed for visibility and access permissions.
-         *  <p>
-         *  The class implies a maximum level of access permission,
-         *  but the permissions may be additionally limited by the bitmask
-         *  {@link #lookupModes lookupModes}, which controls whether non-public members
-         *  can be accessed.
-         *  @return the lookup class, on behalf of which this lookup object finds members
-         */
-        public Class<?> lookupClass() {
-            return lookupClass;
-        }
-
-        /** Tells which access-protection classes of members this lookup object can produce.
-         *  The result is a bit-mask of the bits
-         *  {@linkplain #PUBLIC PUBLIC (0x01)},
-         *  {@linkplain #PRIVATE PRIVATE (0x02)},
-         *  {@linkplain #PROTECTED PROTECTED (0x04)},
-         *  and {@linkplain #PACKAGE PACKAGE (0x08)}.
-         *  <p>
-         *  A freshly-created lookup object
-         *  on the {@linkplain java.lang.invoke.MethodHandles#lookup() caller's class}
-         *  has all possible bits set, since the caller class can access all its own members.
-         *  A lookup object on a new lookup class
-         *  {@linkplain java.lang.invoke.MethodHandles.Lookup#in created from a previous lookup object}
-         *  may have some mode bits set to zero.
-         *  The purpose of this is to restrict access via the new lookup object,
-         *  so that it can access only names which can be reached by the original
-         *  lookup object, and also by the new lookup class.
-         *  @return the lookup modes, which limit the kinds of access performed by this lookup object
-         */
-        public int lookupModes() {
-            return allowedModes & ALL_MODES;
-        }
-
-        /** Embody the current class (the lookupClass) as a lookup class
-         * for method handle creation.
-         * Must be called by from a method in this package,
-         * which in turn is called by a method not in this package.
-         */
-        Lookup(Class<?> lookupClass) {
-            this(lookupClass, ALL_MODES);
-            // make sure we haven't accidentally picked up a privileged class:
-            checkUnprivilegedlookupClass(lookupClass, ALL_MODES);
-        }
-
-        private Lookup(Class<?> lookupClass, int allowedModes) {
-            this.lookupClass = lookupClass;
-            this.allowedModes = allowedModes;
-        }
-
-        /**
-         * Creates a lookup on the specified new lookup class.
-         * The resulting object will report the specified
-         * class as its own {@link #lookupClass lookupClass}.
-         * <p>
-         * However, the resulting {@code Lookup} object is guaranteed
-         * to have no more access capabilities than the original.
-         * In particular, access capabilities can be lost as follows:<ul>
-         * <li>If the new lookup class differs from the old one,
-         * protected members will not be accessible by virtue of inheritance.
-         * (Protected members may continue to be accessible because of package sharing.)
-         * <li>If the new lookup class is in a different package
-         * than the old one, protected and default (package) members will not be accessible.
-         * <li>If the new lookup class is not within the same package member
-         * as the old one, private members will not be accessible.
-         * <li>If the new lookup class is not accessible to the old lookup class,
-         * then no members, not even public members, will be accessible.
-         * (In all other cases, public members will continue to be accessible.)
-         * </ul>
-         *
-         * @param requestedLookupClass the desired lookup class for the new lookup object
-         * @return a lookup object which reports the desired lookup class
-         * @throws NullPointerException if the argument is null
-         */
-        public Lookup in(Class<?> requestedLookupClass) {
-            requestedLookupClass.getClass();  // null check
-            // Android-changed: There's no notion of a trusted lookup.
-            // if (allowedModes == TRUSTED)  // IMPL_LOOKUP can make any lookup at all
-            //    return new Lookup(requestedLookupClass, ALL_MODES);
-
-            if (requestedLookupClass == this.lookupClass)
-                return this;  // keep same capabilities
-            int newModes = (allowedModes & (ALL_MODES & ~PROTECTED));
-            if ((newModes & PACKAGE) != 0
-                && !VerifyAccess.isSamePackage(this.lookupClass, requestedLookupClass)) {
-                newModes &= ~(PACKAGE|PRIVATE);
-            }
-            // Allow nestmate lookups to be created without special privilege:
-            if ((newModes & PRIVATE) != 0
-                && !VerifyAccess.isSamePackageMember(this.lookupClass, requestedLookupClass)) {
-                newModes &= ~PRIVATE;
-            }
-            if ((newModes & PUBLIC) != 0
-                && !VerifyAccess.isClassAccessible(requestedLookupClass, this.lookupClass, allowedModes)) {
-                // The requested class it not accessible from the lookup class.
-                // No permissions.
-                newModes = 0;
-            }
-            checkUnprivilegedlookupClass(requestedLookupClass, newModes);
-            return new Lookup(requestedLookupClass, newModes);
-        }
-
-        // Make sure outer class is initialized first.
-        //
-        // Android-changed: Removed unnecessary reference to IMPL_NAMES.
-        // static { IMPL_NAMES.getClass(); }
-
-        /** Version of lookup which is trusted minimally.
-         *  It can only be used to create method handles to
-         *  publicly accessible members.
-         */
-        static final Lookup PUBLIC_LOOKUP = new Lookup(Object.class, PUBLIC);
-
-        /** Package-private version of lookup which is trusted. */
-        static final Lookup IMPL_LOOKUP = new Lookup(Object.class, ALL_MODES);
-
-        private static void checkUnprivilegedlookupClass(Class<?> lookupClass, int allowedModes) {
-            String name = lookupClass.getName();
-            if (name.startsWith("java.lang.invoke."))
-                throw newIllegalArgumentException("illegal lookupClass: "+lookupClass);
-
-            // For caller-sensitive MethodHandles.lookup()
-            // disallow lookup more restricted packages
-            //
-            // Android-changed: The bootstrap classloader isn't null.
-            if (allowedModes == ALL_MODES &&
-                    lookupClass.getClassLoader() == Object.class.getClassLoader()) {
-                if (name.startsWith("java.") ||
-                        (name.startsWith("sun.")
-                                && !name.startsWith("sun.invoke.")
-                                && !name.equals("sun.reflect.ReflectionFactory"))) {
-                    throw newIllegalArgumentException("illegal lookupClass: " + lookupClass);
-                }
-            }
-        }
-
-        /**
-         * Displays the name of the class from which lookups are to be made.
-         * (The name is the one reported by {@link java.lang.Class#getName() Class.getName}.)
-         * If there are restrictions on the access permitted to this lookup,
-         * this is indicated by adding a suffix to the class name, consisting
-         * of a slash and a keyword.  The keyword represents the strongest
-         * allowed access, and is chosen as follows:
-         * <ul>
-         * <li>If no access is allowed, the suffix is "/noaccess".
-         * <li>If only public access is allowed, the suffix is "/public".
-         * <li>If only public and package access are allowed, the suffix is "/package".
-         * <li>If only public, package, and private access are allowed, the suffix is "/private".
-         * </ul>
-         * If none of the above cases apply, it is the case that full
-         * access (public, package, private, and protected) is allowed.
-         * In this case, no suffix is added.
-         * This is true only of an object obtained originally from
-         * {@link java.lang.invoke.MethodHandles#lookup MethodHandles.lookup}.
-         * Objects created by {@link java.lang.invoke.MethodHandles.Lookup#in Lookup.in}
-         * always have restricted access, and will display a suffix.
-         * <p>
-         * (It may seem strange that protected access should be
-         * stronger than private access.  Viewed independently from
-         * package access, protected access is the first to be lost,
-         * because it requires a direct subclass relationship between
-         * caller and callee.)
-         * @see #in
-         */
-        @Override
-        public String toString() {
-            String cname = lookupClass.getName();
-            switch (allowedModes) {
-            case 0:  // no privileges
-                return cname + "/noaccess";
-            case PUBLIC:
-                return cname + "/public";
-            case PUBLIC|PACKAGE:
-                return cname + "/package";
-            case ALL_MODES & ~PROTECTED:
-                return cname + "/private";
-            case ALL_MODES:
-                return cname;
-            // Android-changed: No support for TRUSTED callers.
-            // case TRUSTED:
-            //    return "/trusted";  // internal only; not exported
-            default:  // Should not happen, but it's a bitfield...
-                cname = cname + "/" + Integer.toHexString(allowedModes);
-                assert(false) : cname;
-                return cname;
-            }
-        }
-
-        /**
-         * Produces a method handle for a static method.
-         * The type of the method handle will be that of the method.
-         * (Since static methods do not take receivers, there is no
-         * additional receiver argument inserted into the method handle type,
-         * as there would be with {@link #findVirtual findVirtual} or {@link #findSpecial findSpecial}.)
-         * The method and all its argument types must be accessible to the lookup object.
-         * <p>
-         * The returned method handle will have
-         * {@linkplain MethodHandle#asVarargsCollector variable arity} if and only if
-         * the method's variable arity modifier bit ({@code 0x0080}) is set.
-         * <p>
-         * If the returned method handle is invoked, the method's class will
-         * be initialized, if it has not already been initialized.
-         * <p><b>Example:</b>
-         * <blockquote><pre>{@code
-import static java.lang.invoke.MethodHandles.*;
-import static java.lang.invoke.MethodType.*;
-...
-MethodHandle MH_asList = publicLookup().findStatic(Arrays.class,
-  "asList", methodType(List.class, Object[].class));
-assertEquals("[x, y]", MH_asList.invoke("x", "y").toString());
-         * }</pre></blockquote>
-         * @param refc the class from which the method is accessed
-         * @param name the name of the method
-         * @param type the type of the method
-         * @return the desired method handle
-         * @throws NoSuchMethodException if the method does not exist
-         * @throws IllegalAccessException if access checking fails,
-         *                                or if the method is not {@code static},
-         *                                or if the method's variable arity modifier bit
-         *                                is set and {@code asVarargsCollector} fails
-         * @exception SecurityException if a security manager is present and it
-         *                              <a href="MethodHandles.Lookup.html#secmgr">refuses access</a>
-         * @throws NullPointerException if any argument is null
-         */
         public
-        MethodHandle findStatic(Class<?> refc, String name, MethodType type) throws NoSuchMethodException, IllegalAccessException {
-            Method method = refc.getDeclaredMethod(name, type.ptypes());
-            final int modifiers = method.getModifiers();
-            if (!Modifier.isStatic(modifiers)) {
-                throw new IllegalAccessException("Method" + method + " is not static");
-            }
-            checkReturnType(method, type);
-            checkAccess(refc, method.getDeclaringClass(), modifiers, method.getName());
-            return createMethodHandle(method, MethodHandle.INVOKE_STATIC, type);
-        }
+        MethodHandle findStatic(Class<?> refc, String name, MethodType type) throws NoSuchMethodException, IllegalAccessException { return null; }
 
-        private MethodHandle findVirtualForMH(String name, MethodType type) {
-            // these names require special lookups because of the implicit MethodType argument
-            if ("invoke".equals(name))
-                return invoker(type);
-            if ("invokeExact".equals(name))
-                return exactInvoker(type);
-            return null;
-        }
+        public MethodHandle findVirtual(Class<?> refc, String name, MethodType type) throws NoSuchMethodException, IllegalAccessException { return null; }
 
-        private static MethodHandle createMethodHandle(Method method, int handleKind,
-                                                       MethodType methodType) {
-            MethodHandle mh = new MethodHandleImpl(method.getArtMethod(), handleKind, methodType);
-            if (method.isVarArgs()) {
-                return new Transformers.VarargsCollector(mh);
-            } else {
-                return mh;
-            }
-        }
+        public MethodHandle findConstructor(Class<?> refc, MethodType type) throws NoSuchMethodException, IllegalAccessException { return null; }
 
-        /**
-         * Produces a method handle for a virtual method.
-         * The type of the method handle will be that of the method,
-         * with the receiver type (usually {@code refc}) prepended.
-         * The method and all its argument types must be accessible to the lookup object.
-         * <p>
-         * When called, the handle will treat the first argument as a receiver
-         * and dispatch on the receiver's type to determine which method
-         * implementation to enter.
-         * (The dispatching action is identical with that performed by an
-         * {@code invokevirtual} or {@code invokeinterface} instruction.)
-         * <p>
-         * The first argument will be of type {@code refc} if the lookup
-         * class has full privileges to access the member.  Otherwise
-         * the member must be {@code protected} and the first argument
-         * will be restricted in type to the lookup class.
-         * <p>
-         * The returned method handle will have
-         * {@linkplain MethodHandle#asVarargsCollector variable arity} if and only if
-         * the method's variable arity modifier bit ({@code 0x0080}) is set.
-         * <p>
-         * Because of the general <a href="MethodHandles.Lookup.html#equiv">equivalence</a> between {@code invokevirtual}
-         * instructions and method handles produced by {@code findVirtual},
-         * if the class is {@code MethodHandle} and the name string is
-         * {@code invokeExact} or {@code invoke}, the resulting
-         * method handle is equivalent to one produced by
-         * {@link java.lang.invoke.MethodHandles#exactInvoker MethodHandles.exactInvoker} or
-         * {@link java.lang.invoke.MethodHandles#invoker MethodHandles.invoker}
-         * with the same {@code type} argument.
-         *
-         * <b>Example:</b>
-         * <blockquote><pre>{@code
-import static java.lang.invoke.MethodHandles.*;
-import static java.lang.invoke.MethodType.*;
-...
-MethodHandle MH_concat = publicLookup().findVirtual(String.class,
-  "concat", methodType(String.class, String.class));
-MethodHandle MH_hashCode = publicLookup().findVirtual(Object.class,
-  "hashCode", methodType(int.class));
-MethodHandle MH_hashCode_String = publicLookup().findVirtual(String.class,
-  "hashCode", methodType(int.class));
-assertEquals("xy", (String) MH_concat.invokeExact("x", "y"));
-assertEquals("xy".hashCode(), (int) MH_hashCode.invokeExact((Object)"xy"));
-assertEquals("xy".hashCode(), (int) MH_hashCode_String.invokeExact("xy"));
-// interface method:
-MethodHandle MH_subSequence = publicLookup().findVirtual(CharSequence.class,
-  "subSequence", methodType(CharSequence.class, int.class, int.class));
-assertEquals("def", MH_subSequence.invoke("abcdefghi", 3, 6).toString());
-// constructor "internal method" must be accessed differently:
-MethodType MT_newString = methodType(void.class); //()V for new String()
-try { assertEquals("impossible", lookup()
-        .findVirtual(String.class, "<init>", MT_newString));
- } catch (NoSuchMethodException ex) { } // OK
-MethodHandle MH_newString = publicLookup()
-  .findConstructor(String.class, MT_newString);
-assertEquals("", (String) MH_newString.invokeExact());
-         * }</pre></blockquote>
-         *
-         * @param refc the class or interface from which the method is accessed
-         * @param name the name of the method
-         * @param type the type of the method, with the receiver argument omitted
-         * @return the desired method handle
-         * @throws NoSuchMethodException if the method does not exist
-         * @throws IllegalAccessException if access checking fails,
-         *                                or if the method is {@code static}
-         *                                or if the method's variable arity modifier bit
-         *                                is set and {@code asVarargsCollector} fails
-         * @exception SecurityException if a security manager is present and it
-         *                              <a href="MethodHandles.Lookup.html#secmgr">refuses access</a>
-         * @throws NullPointerException if any argument is null
-         */
-        public MethodHandle findVirtual(Class<?> refc, String name, MethodType type) throws NoSuchMethodException, IllegalAccessException {
-            // Special case : when we're looking up a virtual method on the MethodHandles class
-            // itself, we can return one of our specialized invokers.
-            if (refc == MethodHandle.class) {
-                MethodHandle mh = findVirtualForMH(name, type);
-                if (mh != null) {
-                    return mh;
-                }
-            }
-            // BEGIN Android-changed: Added VarHandle case here.
-            // Implementation to follow. TODO(b/65872996)
-            if (refc == VarHandle.class) {
-                unsupported("MethodHandles.findVirtual with refc == VarHandle.class");
-                return null;
-            }
-            // END Android-changed: Added VarHandle handling here.
-
-            Method method = refc.getInstanceMethod(name, type.ptypes());
-            if (method == null) {
-                // This is pretty ugly and a consequence of the MethodHandles API. We have to throw
-                // an IAE and not an NSME if the method exists but is static (even though the RI's
-                // IAE has a message that says "no such method"). We confine the ugliness and
-                // slowness to the failure case, and allow getInstanceMethod to remain fairly
-                // general.
-                try {
-                    Method m = refc.getDeclaredMethod(name, type.ptypes());
-                    if (Modifier.isStatic(m.getModifiers())) {
-                        throw new IllegalAccessException("Method" + m + " is static");
-                    }
-                } catch (NoSuchMethodException ignored) {
-                }
-
-                throw new NoSuchMethodException(name + " "  + Arrays.toString(type.ptypes()));
-            }
-            checkReturnType(method, type);
-
-            // We have a valid method, perform access checks.
-            checkAccess(refc, method.getDeclaringClass(), method.getModifiers(), method.getName());
-
-            // Insert the leading reference parameter.
-            MethodType handleType = type.insertParameterTypes(0, refc);
-            return createMethodHandle(method, MethodHandle.INVOKE_VIRTUAL, handleType);
-        }
-
-        /**
-         * Produces a method handle which creates an object and initializes it, using
-         * the constructor of the specified type.
-         * The parameter types of the method handle will be those of the constructor,
-         * while the return type will be a reference to the constructor's class.
-         * The constructor and all its argument types must be accessible to the lookup object.
-         * <p>
-         * The requested type must have a return type of {@code void}.
-         * (This is consistent with the JVM's treatment of constructor type descriptors.)
-         * <p>
-         * The returned method handle will have
-         * {@linkplain MethodHandle#asVarargsCollector variable arity} if and only if
-         * the constructor's variable arity modifier bit ({@code 0x0080}) is set.
-         * <p>
-         * If the returned method handle is invoked, the constructor's class will
-         * be initialized, if it has not already been initialized.
-         * <p><b>Example:</b>
-         * <blockquote><pre>{@code
-import static java.lang.invoke.MethodHandles.*;
-import static java.lang.invoke.MethodType.*;
-...
-MethodHandle MH_newArrayList = publicLookup().findConstructor(
-  ArrayList.class, methodType(void.class, Collection.class));
-Collection orig = Arrays.asList("x", "y");
-Collection copy = (ArrayList) MH_newArrayList.invokeExact(orig);
-assert(orig != copy);
-assertEquals(orig, copy);
-// a variable-arity constructor:
-MethodHandle MH_newProcessBuilder = publicLookup().findConstructor(
-  ProcessBuilder.class, methodType(void.class, String[].class));
-ProcessBuilder pb = (ProcessBuilder)
-  MH_newProcessBuilder.invoke("x", "y", "z");
-assertEquals("[x, y, z]", pb.command().toString());
-         * }</pre></blockquote>
-         * @param refc the class or interface from which the method is accessed
-         * @param type the type of the method, with the receiver argument omitted, and a void return type
-         * @return the desired method handle
-         * @throws NoSuchMethodException if the constructor does not exist
-         * @throws IllegalAccessException if access checking fails
-         *                                or if the method's variable arity modifier bit
-         *                                is set and {@code asVarargsCollector} fails
-         * @exception SecurityException if a security manager is present and it
-         *                              <a href="MethodHandles.Lookup.html#secmgr">refuses access</a>
-         * @throws NullPointerException if any argument is null
-         */
-        public MethodHandle findConstructor(Class<?> refc, MethodType type) throws NoSuchMethodException, IllegalAccessException {
-            if (refc.isArray()) {
-                throw new NoSuchMethodException("no constructor for array class: " + refc.getName());
-            }
-            // The queried |type| is (PT1,PT2,..)V
-            Constructor constructor = refc.getDeclaredConstructor(type.ptypes());
-            if (constructor == null) {
-                throw new NoSuchMethodException(
-                    "No constructor for " + constructor.getDeclaringClass() + " matching " + type);
-            }
-            checkAccess(refc, constructor.getDeclaringClass(), constructor.getModifiers(),
-                    constructor.getName());
-
-            return createMethodHandleForConstructor(constructor);
-        }
-
-        private MethodHandle createMethodHandleForConstructor(Constructor constructor) {
-            Class<?> refc = constructor.getDeclaringClass();
-            MethodType constructorType =
-                    MethodType.methodType(refc, constructor.getParameterTypes());
-            MethodHandle mh;
-            if (refc == String.class) {
-                // String constructors have optimized StringFactory methods
-                // that matches returned type. These factory methods combine the
-                // memory allocation and initialization calls for String objects.
-                mh = new MethodHandleImpl(constructor.getArtMethod(), MethodHandle.INVOKE_DIRECT,
-                                          constructorType);
-            } else {
-                // Constructors for all other classes use a Construct transformer to perform
-                // their memory allocation and call to <init>.
-                MethodType initType = initMethodType(constructorType);
-                MethodHandle initHandle = new MethodHandleImpl(
-                    constructor.getArtMethod(), MethodHandle.INVOKE_DIRECT, initType);
-                mh = new Transformers.Construct(initHandle, constructorType);
-            }
-
-            if (constructor.isVarArgs()) {
-                mh = new Transformers.VarargsCollector(mh);
-            }
-            return mh;
-        }
-
-        private static MethodType initMethodType(MethodType constructorType) {
-            // Returns a MethodType appropriate for class <init>
-            // methods. Constructor MethodTypes have the form
-            // (PT1,PT2,...)C and class <init> MethodTypes have the
-            // form (C,PT1,PT2,...)V.
-            assert constructorType.rtype() != void.class;
-
-            // Insert constructorType C as the first parameter type in
-            // the MethodType for <init>.
-            Class<?> [] initPtypes = new Class<?> [constructorType.ptypes().length + 1];
-            initPtypes[0] = constructorType.rtype();
-            System.arraycopy(constructorType.ptypes(), 0, initPtypes, 1,
-                             constructorType.ptypes().length);
-
-            // Set the return type for the <init> MethodType to be void.
-            return MethodType.methodType(void.class, initPtypes);
-        }
-
-        /**
-         * Produces an early-bound method handle for a virtual method.
-         * It will bypass checks for overriding methods on the receiver,
-         * <a href="MethodHandles.Lookup.html#equiv">as if called</a> from an {@code invokespecial}
-         * instruction from within the explicitly specified {@code specialCaller}.
-         * The type of the method handle will be that of the method,
-         * with a suitably restricted receiver type prepended.
-         * (The receiver type will be {@code specialCaller} or a subtype.)
-         * The method and all its argument types must be accessible
-         * to the lookup object.
-         * <p>
-         * Before method resolution,
-         * if the explicitly specified caller class is not identical with the
-         * lookup class, or if this lookup object does not have
-         * <a href="MethodHandles.Lookup.html#privacc">private access</a>
-         * privileges, the access fails.
-         * <p>
-         * The returned method handle will have
-         * {@linkplain MethodHandle#asVarargsCollector variable arity} if and only if
-         * the method's variable arity modifier bit ({@code 0x0080}) is set.
-         * <p style="font-size:smaller;">
-         * <em>(Note:  JVM internal methods named {@code "<init>"} are not visible to this API,
-         * even though the {@code invokespecial} instruction can refer to them
-         * in special circumstances.  Use {@link #findConstructor findConstructor}
-         * to access instance initialization methods in a safe manner.)</em>
-         * <p><b>Example:</b>
-         * <blockquote><pre>{@code
-import static java.lang.invoke.MethodHandles.*;
-import static java.lang.invoke.MethodType.*;
-...
-static class Listie extends ArrayList {
-  public String toString() { return "[wee Listie]"; }
-  static Lookup lookup() { return MethodHandles.lookup(); }
-}
-...
-// no access to constructor via invokeSpecial:
-MethodHandle MH_newListie = Listie.lookup()
-  .findConstructor(Listie.class, methodType(void.class));
-Listie l = (Listie) MH_newListie.invokeExact();
-try { assertEquals("impossible", Listie.lookup().findSpecial(
-        Listie.class, "<init>", methodType(void.class), Listie.class));
- } catch (NoSuchMethodException ex) { } // OK
-// access to super and self methods via invokeSpecial:
-MethodHandle MH_super = Listie.lookup().findSpecial(
-  ArrayList.class, "toString" , methodType(String.class), Listie.class);
-MethodHandle MH_this = Listie.lookup().findSpecial(
-  Listie.class, "toString" , methodType(String.class), Listie.class);
-MethodHandle MH_duper = Listie.lookup().findSpecial(
-  Object.class, "toString" , methodType(String.class), Listie.class);
-assertEquals("[]", (String) MH_super.invokeExact(l));
-assertEquals(""+l, (String) MH_this.invokeExact(l));
-assertEquals("[]", (String) MH_duper.invokeExact(l)); // ArrayList method
-try { assertEquals("inaccessible", Listie.lookup().findSpecial(
-        String.class, "toString", methodType(String.class), Listie.class));
- } catch (IllegalAccessException ex) { } // OK
-Listie subl = new Listie() { public String toString() { return "[subclass]"; } };
-assertEquals(""+l, (String) MH_this.invokeExact(subl)); // Listie method
-         * }</pre></blockquote>
-         *
-         * @param refc the class or interface from which the method is accessed
-         * @param name the name of the method (which must not be "&lt;init&gt;")
-         * @param type the type of the method, with the receiver argument omitted
-         * @param specialCaller the proposed calling class to perform the {@code invokespecial}
-         * @return the desired method handle
-         * @throws NoSuchMethodException if the method does not exist
-         * @throws IllegalAccessException if access checking fails
-         *                                or if the method's variable arity modifier bit
-         *                                is set and {@code asVarargsCollector} fails
-         * @exception SecurityException if a security manager is present and it
-         *                              <a href="MethodHandles.Lookup.html#secmgr">refuses access</a>
-         * @throws NullPointerException if any argument is null
-         */
         public MethodHandle findSpecial(Class<?> refc, String name, MethodType type,
-                                        Class<?> specialCaller) throws NoSuchMethodException, IllegalAccessException {
-            if (specialCaller == null) {
-                throw new NullPointerException("specialCaller == null");
-            }
+                                        Class<?> specialCaller) throws NoSuchMethodException, IllegalAccessException { return null; }
 
-            if (type == null) {
-                throw new NullPointerException("type == null");
-            }
+        public MethodHandle findGetter(Class<?> refc, String name, Class<?> type) throws NoSuchFieldException, IllegalAccessException { return null; }
 
-            if (name == null) {
-                throw new NullPointerException("name == null");
-            }
+        public MethodHandle findSetter(Class<?> refc, String name, Class<?> type) throws NoSuchFieldException, IllegalAccessException { return null; }
 
-            if (refc == null) {
-                throw new NullPointerException("ref == null");
-            }
+        public MethodHandle findStaticGetter(Class<?> refc, String name, Class<?> type) throws NoSuchFieldException, IllegalAccessException { return null; }
 
-            // Make sure that the special caller is identical to the lookup class or that we have
-            // private access.
-            checkSpecialCaller(specialCaller);
+        public MethodHandle findStaticSetter(Class<?> refc, String name, Class<?> type) throws NoSuchFieldException, IllegalAccessException { return null; }
 
-            // Even though constructors are invoked using a "special" invoke, handles to them can't
-            // be created using findSpecial. Callers must use findConstructor instead. Similarly,
-            // there is no path for calling static class initializers.
-            if (name.startsWith("<")) {
-                throw new NoSuchMethodException(name + " is not a valid method name.");
-            }
+        public MethodHandle bind(Object receiver, String name, MethodType type) throws NoSuchMethodException, IllegalAccessException { return null; }
 
-            Method method = refc.getDeclaredMethod(name, type.ptypes());
-            checkReturnType(method, type);
-            return findSpecial(method, type, refc, specialCaller);
-        }
+        public MethodHandle unreflect(Method m) throws IllegalAccessException { return null; }
 
-        private MethodHandle findSpecial(Method method, MethodType type,
-                                         Class<?> refc, Class<?> specialCaller)
-                throws IllegalAccessException {
-            if (Modifier.isStatic(method.getModifiers())) {
-                throw new IllegalAccessException("expected a non-static method:" + method);
-            }
+        public MethodHandle unreflectSpecial(Method m, Class<?> specialCaller) throws IllegalAccessException { return null; }
 
-            if (Modifier.isPrivate(method.getModifiers())) {
-                // Since this is a private method, we'll need to also make sure that the
-                // lookup class is the same as the refering class. We've already checked that
-                // the specialCaller is the same as the special lookup class, both of these must
-                // be the same as the declaring class(*) in order to access the private method.
-                //
-                // (*) Well, this isn't true for nested classes but OpenJDK doesn't support those
-                // either.
-                if (refc != lookupClass()) {
-                    throw new IllegalAccessException("no private access for invokespecial : "
-                            + refc + ", from" + this);
-                }
+        public MethodHandle unreflectConstructor(Constructor<?> c) throws IllegalAccessException { return null; }
 
-                // This is a private method, so there's nothing special to do.
-                MethodType handleType = type.insertParameterTypes(0, refc);
-                return createMethodHandle(method, MethodHandle.INVOKE_DIRECT, handleType);
-            }
+        public MethodHandle unreflectGetter(Field f) throws IllegalAccessException { return null; }
 
-            // This is a public, protected or package-private method, which means we're expecting
-            // invoke-super semantics. We'll have to restrict the receiver type appropriately on the
-            // handle once we check that there really is a "super" relationship between them.
-            if (!method.getDeclaringClass().isAssignableFrom(specialCaller)) {
-                throw new IllegalAccessException(refc + "is not assignable from " + specialCaller);
-            }
+        public MethodHandle unreflectSetter(Field f) throws IllegalAccessException { return null; }
 
-            // Note that we restrict the receiver to "specialCaller" instances.
-            MethodType handleType = type.insertParameterTypes(0, specialCaller);
-            return createMethodHandle(method, MethodHandle.INVOKE_SUPER, handleType);
-        }
+        public MethodHandleInfo revealDirect(MethodHandle target) { return null; }
 
-        /**
-         * Produces a method handle giving read access to a non-static field.
-         * The type of the method handle will have a return type of the field's
-         * value type.
-         * The method handle's single argument will be the instance containing
-         * the field.
-         * Access checking is performed immediately on behalf of the lookup class.
-         * @param refc the class or interface from which the method is accessed
-         * @param name the field's name
-         * @param type the field's type
-         * @return a method handle which can load values from the field
-         * @throws NoSuchFieldException if the field does not exist
-         * @throws IllegalAccessException if access checking fails, or if the field is {@code static}
-         * @exception SecurityException if a security manager is present and it
-         *                              <a href="MethodHandles.Lookup.html#secmgr">refuses access</a>
-         * @throws NullPointerException if any argument is null
-         */
-        public MethodHandle findGetter(Class<?> refc, String name, Class<?> type) throws NoSuchFieldException, IllegalAccessException {
-            return findAccessor(refc, name, type, MethodHandle.IGET);
-        }
-
-        private MethodHandle findAccessor(Class<?> refc, String name, Class<?> type, int kind)
-            throws NoSuchFieldException, IllegalAccessException {
-            final Field field = refc.getDeclaredField(name);
-            final Class<?> fieldType = field.getType();
-            if (fieldType != type) {
-                throw new NoSuchFieldException(
-                        "Field has wrong type: " + fieldType + " != " + type);
-            }
-
-            return findAccessor(field, refc, type, kind, true /* performAccessChecks */);
-        }
-
-        private MethodHandle findAccessor(Field field, Class<?> refc, Class<?> fieldType, int kind,
-                                          boolean performAccessChecks)
-                throws IllegalAccessException {
-            if (!performAccessChecks) {
-                checkAccess(refc, field.getDeclaringClass(), field.getModifiers(), field.getName());
-            }
-
-            final boolean isStaticKind = kind == MethodHandle.SGET || kind == MethodHandle.SPUT;
-            final int modifiers = field.getModifiers();
-            if (Modifier.isStatic(modifiers) != isStaticKind) {
-                String reason = "Field " + field + " is " +
-                        (isStaticKind ? "not " : "") + "static";
-                throw new IllegalAccessException(reason);
-            }
-
-            final boolean isSetterKind = kind == MethodHandle.IPUT || kind == MethodHandle.SPUT;
-            if (Modifier.isFinal(modifiers) && isSetterKind) {
-                throw new IllegalAccessException("Field " + field + " is final");
-            }
-
-            final MethodType methodType;
-            switch (kind) {
-                case MethodHandle.SGET:
-                    methodType = MethodType.methodType(fieldType);
-                    break;
-                case MethodHandle.SPUT:
-                    methodType = MethodType.methodType(void.class, fieldType);
-                    break;
-                case MethodHandle.IGET:
-                    methodType = MethodType.methodType(fieldType, refc);
-                    break;
-                case MethodHandle.IPUT:
-                    methodType = MethodType.methodType(void.class, refc, fieldType);
-                    break;
-                default:
-                    throw new IllegalArgumentException("Invalid kind " + kind);
-            }
-            return new MethodHandleImpl(field.getArtField(), kind, methodType);
-        }
-
-        /**
-         * Produces a method handle giving write access to a non-static field.
-         * The type of the method handle will have a void return type.
-         * The method handle will take two arguments, the instance containing
-         * the field, and the value to be stored.
-         * The second argument will be of the field's value type.
-         * Access checking is performed immediately on behalf of the lookup class.
-         * @param refc the class or interface from which the method is accessed
-         * @param name the field's name
-         * @param type the field's type
-         * @return a method handle which can store values into the field
-         * @throws NoSuchFieldException if the field does not exist
-         * @throws IllegalAccessException if access checking fails, or if the field is {@code static}
-         * @exception SecurityException if a security manager is present and it
-         *                              <a href="MethodHandles.Lookup.html#secmgr">refuses access</a>
-         * @throws NullPointerException if any argument is null
-         */
-        public MethodHandle findSetter(Class<?> refc, String name, Class<?> type) throws NoSuchFieldException, IllegalAccessException {
-            return findAccessor(refc, name, type, MethodHandle.IPUT);
-        }
-
-        // BEGIN Android-changed: OpenJDK 9+181 VarHandle API factory method for bring up purposes.
-        /**
-         * Produces a VarHandle giving access to a non-static field {@code name}
-         * of type {@code type} declared in a class of type {@code recv}.
-         * The VarHandle's variable type is {@code type} and it has one
-         * coordinate type, {@code recv}.
-         * <p>
-         * Access checking is performed immediately on behalf of the lookup
-         * class.
-         * <p>
-         * Certain access modes of the returned VarHandle are unsupported under
-         * the following conditions:
-         * <ul>
-         * <li>if the field is declared {@code final}, then the write, atomic
-         *     update, numeric atomic update, and bitwise atomic update access
-         *     modes are unsupported.
-         * <li>if the field type is anything other than {@code byte},
-         *     {@code short}, {@code char}, {@code int}, {@code long},
-         *     {@code float}, or {@code double} then numeric atomic update
-         *     access modes are unsupported.
-         * <li>if the field type is anything other than {@code boolean},
-         *     {@code byte}, {@code short}, {@code char}, {@code int} or
-         *     {@code long} then bitwise atomic update access modes are
-         *     unsupported.
-         * </ul>
-         * <p>
-         * If the field is declared {@code volatile} then the returned VarHandle
-         * will override access to the field (effectively ignore the
-         * {@code volatile} declaration) in accordance to its specified
-         * access modes.
-         * <p>
-         * If the field type is {@code float} or {@code double} then numeric
-         * and atomic update access modes compare values using their bitwise
-         * representation (see {@link Float#floatToRawIntBits} and
-         * {@link Double#doubleToRawLongBits}, respectively).
-         * @apiNote
-         * Bitwise comparison of {@code float} values or {@code double} values,
-         * as performed by the numeric and atomic update access modes, differ
-         * from the primitive {@code ==} operator and the {@link Float#equals}
-         * and {@link Double#equals} methods, specifically with respect to
-         * comparing NaN values or comparing {@code -0.0} with {@code +0.0}.
-         * Care should be taken when performing a compare and set or a compare
-         * and exchange operation with such values since the operation may
-         * unexpectedly fail.
-         * There are many possible NaN values that are considered to be
-         * {@code NaN} in Java, although no IEEE 754 floating-point operation
-         * provided by Java can distinguish between them.  Operation failure can
-         * occur if the expected or witness value is a NaN value and it is
-         * transformed (perhaps in a platform specific manner) into another NaN
-         * value, and thus has a different bitwise representation (see
-         * {@link Float#intBitsToFloat} or {@link Double#longBitsToDouble} for more
-         * details).
-         * The values {@code -0.0} and {@code +0.0} have different bitwise
-         * representations but are considered equal when using the primitive
-         * {@code ==} operator.  Operation failure can occur if, for example, a
-         * numeric algorithm computes an expected value to be say {@code -0.0}
-         * and previously computed the witness value to be say {@code +0.0}.
-         * @param recv the receiver class, of type {@code R}, that declares the
-         * non-static field
-         * @param name the field's name
-         * @param type the field's type, of type {@code T}
-         * @return a VarHandle giving access to non-static fields.
-         * @throws NoSuchFieldException if the field does not exist
-         * @throws IllegalAccessException if access checking fails, or if the field is {@code static}
-         * @exception SecurityException if a security manager is present and it
-         *                              <a href="MethodHandles.Lookup.html#secmgr">refuses access</a>
-         * @throws NullPointerException if any argument is null
-         * @since 9
-         * @hide
-         */
-        public VarHandle findVarHandle(Class<?> recv, String name, Class<?> type) throws NoSuchFieldException, IllegalAccessException {
-            unsupported("MethodHandles.Lookup.findVarHandle()");  // TODO(b/65872996)
-            return null;
-        }
-        // END Android-changed: OpenJDK 9+181 VarHandle API factory method for bring up purposes.
-
-        /**
-         * Produces a method handle giving read access to a static field.
-         * The type of the method handle will have a return type of the field's
-         * value type.
-         * The method handle will take no arguments.
-         * Access checking is performed immediately on behalf of the lookup class.
-         * <p>
-         * If the returned method handle is invoked, the field's class will
-         * be initialized, if it has not already been initialized.
-         * @param refc the class or interface from which the method is accessed
-         * @param name the field's name
-         * @param type the field's type
-         * @return a method handle which can load values from the field
-         * @throws NoSuchFieldException if the field does not exist
-         * @throws IllegalAccessException if access checking fails, or if the field is not {@code static}
-         * @exception SecurityException if a security manager is present and it
-         *                              <a href="MethodHandles.Lookup.html#secmgr">refuses access</a>
-         * @throws NullPointerException if any argument is null
-         */
-        public MethodHandle findStaticGetter(Class<?> refc, String name, Class<?> type) throws NoSuchFieldException, IllegalAccessException {
-            return findAccessor(refc, name, type, MethodHandle.SGET);
-        }
-
-        /**
-         * Produces a method handle giving write access to a static field.
-         * The type of the method handle will have a void return type.
-         * The method handle will take a single
-         * argument, of the field's value type, the value to be stored.
-         * Access checking is performed immediately on behalf of the lookup class.
-         * <p>
-         * If the returned method handle is invoked, the field's class will
-         * be initialized, if it has not already been initialized.
-         * @param refc the class or interface from which the method is accessed
-         * @param name the field's name
-         * @param type the field's type
-         * @return a method handle which can store values into the field
-         * @throws NoSuchFieldException if the field does not exist
-         * @throws IllegalAccessException if access checking fails, or if the field is not {@code static}
-         * @exception SecurityException if a security manager is present and it
-         *                              <a href="MethodHandles.Lookup.html#secmgr">refuses access</a>
-         * @throws NullPointerException if any argument is null
-         */
-        public MethodHandle findStaticSetter(Class<?> refc, String name, Class<?> type) throws NoSuchFieldException, IllegalAccessException {
-            return findAccessor(refc, name, type, MethodHandle.SPUT);
-        }
-
-        // BEGIN Android-changed: OpenJDK 9+181 VarHandle API factory method for bring up purposes.
-        /**
-         * Produces a VarHandle giving access to a static field {@code name} of
-         * type {@code type} declared in a class of type {@code decl}.
-         * The VarHandle's variable type is {@code type} and it has no
-         * coordinate types.
-         * <p>
-         * Access checking is performed immediately on behalf of the lookup
-         * class.
-         * <p>
-         * If the returned VarHandle is operated on, the declaring class will be
-         * initialized, if it has not already been initialized.
-         * <p>
-         * Certain access modes of the returned VarHandle are unsupported under
-         * the following conditions:
-         * <ul>
-         * <li>if the field is declared {@code final}, then the write, atomic
-         *     update, numeric atomic update, and bitwise atomic update access
-         *     modes are unsupported.
-         * <li>if the field type is anything other than {@code byte},
-         *     {@code short}, {@code char}, {@code int}, {@code long},
-         *     {@code float}, or {@code double}, then numeric atomic update
-         *     access modes are unsupported.
-         * <li>if the field type is anything other than {@code boolean},
-         *     {@code byte}, {@code short}, {@code char}, {@code int} or
-         *     {@code long} then bitwise atomic update access modes are
-         *     unsupported.
-         * </ul>
-         * <p>
-         * If the field is declared {@code volatile} then the returned VarHandle
-         * will override access to the field (effectively ignore the
-         * {@code volatile} declaration) in accordance to its specified
-         * access modes.
-         * <p>
-         * If the field type is {@code float} or {@code double} then numeric
-         * and atomic update access modes compare values using their bitwise
-         * representation (see {@link Float#floatToRawIntBits} and
-         * {@link Double#doubleToRawLongBits}, respectively).
-         * @apiNote
-         * Bitwise comparison of {@code float} values or {@code double} values,
-         * as performed by the numeric and atomic update access modes, differ
-         * from the primitive {@code ==} operator and the {@link Float#equals}
-         * and {@link Double#equals} methods, specifically with respect to
-         * comparing NaN values or comparing {@code -0.0} with {@code +0.0}.
-         * Care should be taken when performing a compare and set or a compare
-         * and exchange operation with such values since the operation may
-         * unexpectedly fail.
-         * There are many possible NaN values that are considered to be
-         * {@code NaN} in Java, although no IEEE 754 floating-point operation
-         * provided by Java can distinguish between them.  Operation failure can
-         * occur if the expected or witness value is a NaN value and it is
-         * transformed (perhaps in a platform specific manner) into another NaN
-         * value, and thus has a different bitwise representation (see
-         * {@link Float#intBitsToFloat} or {@link Double#longBitsToDouble} for more
-         * details).
-         * The values {@code -0.0} and {@code +0.0} have different bitwise
-         * representations but are considered equal when using the primitive
-         * {@code ==} operator.  Operation failure can occur if, for example, a
-         * numeric algorithm computes an expected value to be say {@code -0.0}
-         * and previously computed the witness value to be say {@code +0.0}.
-         * @param decl the class that declares the static field
-         * @param name the field's name
-         * @param type the field's type, of type {@code T}
-         * @return a VarHandle giving access to a static field
-         * @throws NoSuchFieldException if the field does not exist
-         * @throws IllegalAccessException if access checking fails, or if the field is not {@code static}
-         * @exception SecurityException if a security manager is present and it
-         *                              <a href="MethodHandles.Lookup.html#secmgr">refuses access</a>
-         * @throws NullPointerException if any argument is null
-         * @since 9
-         * @hide
-         */
-        public VarHandle findStaticVarHandle(Class<?> decl, String name, Class<?> type) throws NoSuchFieldException, IllegalAccessException {
-            unsupported("MethodHandles.Lookup.findStaticVarHandle()");  // TODO(b/65872996)
-            return null;
-        }
-        // END Android-changed: OpenJDK 9+181 VarHandle API factory method for bring up purposes.
-
-        /**
-         * Produces an early-bound method handle for a non-static method.
-         * The receiver must have a supertype {@code defc} in which a method
-         * of the given name and type is accessible to the lookup class.
-         * The method and all its argument types must be accessible to the lookup object.
-         * The type of the method handle will be that of the method,
-         * without any insertion of an additional receiver parameter.
-         * The given receiver will be bound into the method handle,
-         * so that every call to the method handle will invoke the
-         * requested method on the given receiver.
-         * <p>
-         * The returned method handle will have
-         * {@linkplain MethodHandle#asVarargsCollector variable arity} if and only if
-         * the method's variable arity modifier bit ({@code 0x0080}) is set
-         * <em>and</em> the trailing array argument is not the only argument.
-         * (If the trailing array argument is the only argument,
-         * the given receiver value will be bound to it.)
-         * <p>
-         * This is equivalent to the following code:
-         * <blockquote><pre>{@code
-import static java.lang.invoke.MethodHandles.*;
-import static java.lang.invoke.MethodType.*;
-...
-MethodHandle mh0 = lookup().findVirtual(defc, name, type);
-MethodHandle mh1 = mh0.bindTo(receiver);
-MethodType mt1 = mh1.type();
-if (mh0.isVarargsCollector())
-  mh1 = mh1.asVarargsCollector(mt1.parameterType(mt1.parameterCount()-1));
-return mh1;
-         * }</pre></blockquote>
-         * where {@code defc} is either {@code receiver.getClass()} or a super
-         * type of that class, in which the requested method is accessible
-         * to the lookup class.
-         * (Note that {@code bindTo} does not preserve variable arity.)
-         * @param receiver the object from which the method is accessed
-         * @param name the name of the method
-         * @param type the type of the method, with the receiver argument omitted
-         * @return the desired method handle
-         * @throws NoSuchMethodException if the method does not exist
-         * @throws IllegalAccessException if access checking fails
-         *                                or if the method's variable arity modifier bit
-         *                                is set and {@code asVarargsCollector} fails
-         * @exception SecurityException if a security manager is present and it
-         *                              <a href="MethodHandles.Lookup.html#secmgr">refuses access</a>
-         * @throws NullPointerException if any argument is null
-         * @see MethodHandle#bindTo
-         * @see #findVirtual
-         */
-        public MethodHandle bind(Object receiver, String name, MethodType type) throws NoSuchMethodException, IllegalAccessException {
-            MethodHandle handle = findVirtual(receiver.getClass(), name, type);
-            MethodHandle adapter = handle.bindTo(receiver);
-            MethodType adapterType = adapter.type();
-            if (handle.isVarargsCollector()) {
-                adapter = adapter.asVarargsCollector(
-                        adapterType.parameterType(adapterType.parameterCount() - 1));
-            }
-
-            return adapter;
-        }
-
-        /**
-         * Makes a <a href="MethodHandleInfo.html#directmh">direct method handle</a>
-         * to <i>m</i>, if the lookup class has permission.
-         * If <i>m</i> is non-static, the receiver argument is treated as an initial argument.
-         * If <i>m</i> is virtual, overriding is respected on every call.
-         * Unlike the Core Reflection API, exceptions are <em>not</em> wrapped.
-         * The type of the method handle will be that of the method,
-         * with the receiver type prepended (but only if it is non-static).
-         * If the method's {@code accessible} flag is not set,
-         * access checking is performed immediately on behalf of the lookup class.
-         * If <i>m</i> is not public, do not share the resulting handle with untrusted parties.
-         * <p>
-         * The returned method handle will have
-         * {@linkplain MethodHandle#asVarargsCollector variable arity} if and only if
-         * the method's variable arity modifier bit ({@code 0x0080}) is set.
-         * <p>
-         * If <i>m</i> is static, and
-         * if the returned method handle is invoked, the method's class will
-         * be initialized, if it has not already been initialized.
-         * @param m the reflected method
-         * @return a method handle which can invoke the reflected method
-         * @throws IllegalAccessException if access checking fails
-         *                                or if the method's variable arity modifier bit
-         *                                is set and {@code asVarargsCollector} fails
-         * @throws NullPointerException if the argument is null
-         */
-        public MethodHandle unreflect(Method m) throws IllegalAccessException {
-            if (m == null) {
-                throw new NullPointerException("m == null");
-            }
-
-            MethodType methodType = MethodType.methodType(m.getReturnType(),
-                    m.getParameterTypes());
-
-            // We should only perform access checks if setAccessible hasn't been called yet.
-            if (!m.isAccessible()) {
-                checkAccess(m.getDeclaringClass(), m.getDeclaringClass(), m.getModifiers(),
-                        m.getName());
-            }
-
-            if (Modifier.isStatic(m.getModifiers())) {
-                return createMethodHandle(m, MethodHandle.INVOKE_STATIC, methodType);
-            } else {
-                methodType = methodType.insertParameterTypes(0, m.getDeclaringClass());
-                return createMethodHandle(m, MethodHandle.INVOKE_VIRTUAL, methodType);
-            }
-        }
-
-        /**
-         * Produces a method handle for a reflected method.
-         * It will bypass checks for overriding methods on the receiver,
-         * <a href="MethodHandles.Lookup.html#equiv">as if called</a> from an {@code invokespecial}
-         * instruction from within the explicitly specified {@code specialCaller}.
-         * The type of the method handle will be that of the method,
-         * with a suitably restricted receiver type prepended.
-         * (The receiver type will be {@code specialCaller} or a subtype.)
-         * If the method's {@code accessible} flag is not set,
-         * access checking is performed immediately on behalf of the lookup class,
-         * as if {@code invokespecial} instruction were being linked.
-         * <p>
-         * Before method resolution,
-         * if the explicitly specified caller class is not identical with the
-         * lookup class, or if this lookup object does not have
-         * <a href="MethodHandles.Lookup.html#privacc">private access</a>
-         * privileges, the access fails.
-         * <p>
-         * The returned method handle will have
-         * {@linkplain MethodHandle#asVarargsCollector variable arity} if and only if
-         * the method's variable arity modifier bit ({@code 0x0080}) is set.
-         * @param m the reflected method
-         * @param specialCaller the class nominally calling the method
-         * @return a method handle which can invoke the reflected method
-         * @throws IllegalAccessException if access checking fails
-         *                                or if the method's variable arity modifier bit
-         *                                is set and {@code asVarargsCollector} fails
-         * @throws NullPointerException if any argument is null
-         */
-        public MethodHandle unreflectSpecial(Method m, Class<?> specialCaller) throws IllegalAccessException {
-            if (m == null) {
-                throw new NullPointerException("m == null");
-            }
-
-            if (specialCaller == null) {
-                throw new NullPointerException("specialCaller == null");
-            }
-
-            if (!m.isAccessible()) {
-                checkSpecialCaller(specialCaller);
-            }
-
-            final MethodType methodType = MethodType.methodType(m.getReturnType(),
-                    m.getParameterTypes());
-            return findSpecial(m, methodType, m.getDeclaringClass() /* refc */, specialCaller);
-        }
-
-        /**
-         * Produces a method handle for a reflected constructor.
-         * The type of the method handle will be that of the constructor,
-         * with the return type changed to the declaring class.
-         * The method handle will perform a {@code newInstance} operation,
-         * creating a new instance of the constructor's class on the
-         * arguments passed to the method handle.
-         * <p>
-         * If the constructor's {@code accessible} flag is not set,
-         * access checking is performed immediately on behalf of the lookup class.
-         * <p>
-         * The returned method handle will have
-         * {@linkplain MethodHandle#asVarargsCollector variable arity} if and only if
-         * the constructor's variable arity modifier bit ({@code 0x0080}) is set.
-         * <p>
-         * If the returned method handle is invoked, the constructor's class will
-         * be initialized, if it has not already been initialized.
-         * @param c the reflected constructor
-         * @return a method handle which can invoke the reflected constructor
-         * @throws IllegalAccessException if access checking fails
-         *                                or if the method's variable arity modifier bit
-         *                                is set and {@code asVarargsCollector} fails
-         * @throws NullPointerException if the argument is null
-         */
-        public MethodHandle unreflectConstructor(Constructor<?> c) throws IllegalAccessException {
-            if (c == null) {
-                throw new NullPointerException("c == null");
-            }
-
-            if (!c.isAccessible()) {
-                checkAccess(c.getDeclaringClass(), c.getDeclaringClass(), c.getModifiers(),
-                        c.getName());
-            }
-
-            return createMethodHandleForConstructor(c);
-        }
-
-        /**
-         * Produces a method handle giving read access to a reflected field.
-         * The type of the method handle will have a return type of the field's
-         * value type.
-         * If the field is static, the method handle will take no arguments.
-         * Otherwise, its single argument will be the instance containing
-         * the field.
-         * If the field's {@code accessible} flag is not set,
-         * access checking is performed immediately on behalf of the lookup class.
-         * <p>
-         * If the field is static, and
-         * if the returned method handle is invoked, the field's class will
-         * be initialized, if it has not already been initialized.
-         * @param f the reflected field
-         * @return a method handle which can load values from the reflected field
-         * @throws IllegalAccessException if access checking fails
-         * @throws NullPointerException if the argument is null
-         */
-        public MethodHandle unreflectGetter(Field f) throws IllegalAccessException {
-            return findAccessor(f, f.getDeclaringClass(), f.getType(),
-                    Modifier.isStatic(f.getModifiers()) ? MethodHandle.SGET : MethodHandle.IGET,
-                    f.isAccessible() /* performAccessChecks */);
-        }
-
-        /**
-         * Produces a method handle giving write access to a reflected field.
-         * The type of the method handle will have a void return type.
-         * If the field is static, the method handle will take a single
-         * argument, of the field's value type, the value to be stored.
-         * Otherwise, the two arguments will be the instance containing
-         * the field, and the value to be stored.
-         * If the field's {@code accessible} flag is not set,
-         * access checking is performed immediately on behalf of the lookup class.
-         * <p>
-         * If the field is static, and
-         * if the returned method handle is invoked, the field's class will
-         * be initialized, if it has not already been initialized.
-         * @param f the reflected field
-         * @return a method handle which can store values into the reflected field
-         * @throws IllegalAccessException if access checking fails
-         * @throws NullPointerException if the argument is null
-         */
-        public MethodHandle unreflectSetter(Field f) throws IllegalAccessException {
-            return findAccessor(f, f.getDeclaringClass(), f.getType(),
-                    Modifier.isStatic(f.getModifiers()) ? MethodHandle.SPUT : MethodHandle.IPUT,
-                    f.isAccessible() /* performAccessChecks */);
-        }
-
-        // BEGIN Android-changed: OpenJDK 9+181 VarHandle API factory method for bring up purposes.
-        /**
-         * Produces a VarHandle giving access to a reflected field {@code f}
-         * of type {@code T} declared in a class of type {@code R}.
-         * The VarHandle's variable type is {@code T}.
-         * If the field is non-static the VarHandle has one coordinate type,
-         * {@code R}.  Otherwise, the field is static, and the VarHandle has no
-         * coordinate types.
-         * <p>
-         * Access checking is performed immediately on behalf of the lookup
-         * class, regardless of the value of the field's {@code accessible}
-         * flag.
-         * <p>
-         * If the field is static, and if the returned VarHandle is operated
-         * on, the field's declaring class will be initialized, if it has not
-         * already been initialized.
-         * <p>
-         * Certain access modes of the returned VarHandle are unsupported under
-         * the following conditions:
-         * <ul>
-         * <li>if the field is declared {@code final}, then the write, atomic
-         *     update, numeric atomic update, and bitwise atomic update access
-         *     modes are unsupported.
-         * <li>if the field type is anything other than {@code byte},
-         *     {@code short}, {@code char}, {@code int}, {@code long},
-         *     {@code float}, or {@code double} then numeric atomic update
-         *     access modes are unsupported.
-         * <li>if the field type is anything other than {@code boolean},
-         *     {@code byte}, {@code short}, {@code char}, {@code int} or
-         *     {@code long} then bitwise atomic update access modes are
-         *     unsupported.
-         * </ul>
-         * <p>
-         * If the field is declared {@code volatile} then the returned VarHandle
-         * will override access to the field (effectively ignore the
-         * {@code volatile} declaration) in accordance to its specified
-         * access modes.
-         * <p>
-         * If the field type is {@code float} or {@code double} then numeric
-         * and atomic update access modes compare values using their bitwise
-         * representation (see {@link Float#floatToRawIntBits} and
-         * {@link Double#doubleToRawLongBits}, respectively).
-         * @apiNote
-         * Bitwise comparison of {@code float} values or {@code double} values,
-         * as performed by the numeric and atomic update access modes, differ
-         * from the primitive {@code ==} operator and the {@link Float#equals}
-         * and {@link Double#equals} methods, specifically with respect to
-         * comparing NaN values or comparing {@code -0.0} with {@code +0.0}.
-         * Care should be taken when performing a compare and set or a compare
-         * and exchange operation with such values since the operation may
-         * unexpectedly fail.
-         * There are many possible NaN values that are considered to be
-         * {@code NaN} in Java, although no IEEE 754 floating-point operation
-         * provided by Java can distinguish between them.  Operation failure can
-         * occur if the expected or witness value is a NaN value and it is
-         * transformed (perhaps in a platform specific manner) into another NaN
-         * value, and thus has a different bitwise representation (see
-         * {@link Float#intBitsToFloat} or {@link Double#longBitsToDouble} for more
-         * details).
-         * The values {@code -0.0} and {@code +0.0} have different bitwise
-         * representations but are considered equal when using the primitive
-         * {@code ==} operator.  Operation failure can occur if, for example, a
-         * numeric algorithm computes an expected value to be say {@code -0.0}
-         * and previously computed the witness value to be say {@code +0.0}.
-         * @param f the reflected field, with a field of type {@code T}, and
-         * a declaring class of type {@code R}
-         * @return a VarHandle giving access to non-static fields or a static
-         * field
-         * @throws IllegalAccessException if access checking fails
-         * @throws NullPointerException if the argument is null
-         * @since 9
-         * @hide
-         */
-        public VarHandle unreflectVarHandle(Field f) throws IllegalAccessException {
-            unsupported("MethodHandles.Lookup.unreflectVarHandle()");  // TODO(b/65872996)
-            return null;
-        }
-        // END Android-changed: OpenJDK 9+181 VarHandle API factory method for bring up purposes.
-
-        /**
-         * Cracks a <a href="MethodHandleInfo.html#directmh">direct method handle</a>
-         * created by this lookup object or a similar one.
-         * Security and access checks are performed to ensure that this lookup object
-         * is capable of reproducing the target method handle.
-         * This means that the cracking may fail if target is a direct method handle
-         * but was created by an unrelated lookup object.
-         * This can happen if the method handle is <a href="MethodHandles.Lookup.html#callsens">caller sensitive</a>
-         * and was created by a lookup object for a different class.
-         * @param target a direct method handle to crack into symbolic reference components
-         * @return a symbolic reference which can be used to reconstruct this method handle from this lookup object
-         * @exception SecurityException if a security manager is present and it
-         *                              <a href="MethodHandles.Lookup.html#secmgr">refuses access</a>
-         * @throws IllegalArgumentException if the target is not a direct method handle or if access checking fails
-         * @exception NullPointerException if the target is {@code null}
-         * @see MethodHandleInfo
-         * @since 1.8
-         */
-        public MethodHandleInfo revealDirect(MethodHandle target) {
-            MethodHandleImpl directTarget = getMethodHandleImpl(target);
-            MethodHandleInfo info = directTarget.reveal();
-
-            try {
-                checkAccess(lookupClass(), info.getDeclaringClass(), info.getModifiers(),
-                        info.getName());
-            } catch (IllegalAccessException exception) {
-                throw new IllegalArgumentException("Unable to access memeber.", exception);
-            }
-
-            return info;
-        }
-
-        private boolean hasPrivateAccess() {
-            return (allowedModes & PRIVATE) != 0;
-        }
-
-        /** Check public/protected/private bits on the symbolic reference class and its member. */
-        void checkAccess(Class<?> refc, Class<?> defc, int mods, String methName)
-                throws IllegalAccessException {
-            int allowedModes = this.allowedModes;
-
-            if (Modifier.isProtected(mods) &&
-                    defc == Object.class &&
-                    "clone".equals(methName) &&
-                    refc.isArray()) {
-                // The JVM does this hack also.
-                // (See ClassVerifier::verify_invoke_instructions
-                // and LinkResolver::check_method_accessability.)
-                // Because the JVM does not allow separate methods on array types,
-                // there is no separate method for int[].clone.
-                // All arrays simply inherit Object.clone.
-                // But for access checking logic, we make Object.clone
-                // (normally protected) appear to be public.
-                // Later on, when the DirectMethodHandle is created,
-                // its leading argument will be restricted to the
-                // requested array type.
-                // N.B. The return type is not adjusted, because
-                // that is *not* the bytecode behavior.
-                mods ^= Modifier.PROTECTED | Modifier.PUBLIC;
-            }
-
-            if (Modifier.isProtected(mods) && Modifier.isConstructor(mods)) {
-                // cannot "new" a protected ctor in a different package
-                mods ^= Modifier.PROTECTED;
-            }
-
-            if (Modifier.isPublic(mods) && Modifier.isPublic(refc.getModifiers()) && allowedModes != 0)
-                return;  // common case
-            int requestedModes = fixmods(mods);  // adjust 0 => PACKAGE
-            if ((requestedModes & allowedModes) != 0) {
-                if (VerifyAccess.isMemberAccessible(refc, defc, mods, lookupClass(), allowedModes))
-                    return;
-            } else {
-                // Protected members can also be checked as if they were package-private.
-                if ((requestedModes & PROTECTED) != 0 && (allowedModes & PACKAGE) != 0
-                        && VerifyAccess.isSamePackage(defc, lookupClass()))
-                    return;
-            }
-
-            throwMakeAccessException(accessFailedMessage(refc, defc, mods), this);
-        }
-
-        String accessFailedMessage(Class<?> refc, Class<?> defc, int mods) {
-            // check the class first:
-            boolean classOK = (Modifier.isPublic(defc.getModifiers()) &&
-                    (defc == refc ||
-                            Modifier.isPublic(refc.getModifiers())));
-            if (!classOK && (allowedModes & PACKAGE) != 0) {
-                classOK = (VerifyAccess.isClassAccessible(defc, lookupClass(), ALL_MODES) &&
-                        (defc == refc ||
-                                VerifyAccess.isClassAccessible(refc, lookupClass(), ALL_MODES)));
-            }
-            if (!classOK)
-                return "class is not public";
-            if (Modifier.isPublic(mods))
-                return "access to public member failed";  // (how?)
-            if (Modifier.isPrivate(mods))
-                return "member is private";
-            if (Modifier.isProtected(mods))
-                return "member is protected";
-            return "member is private to package";
-        }
-
-        // Android-changed: checkSpecialCaller assumes that ALLOW_NESTMATE_ACCESS = false,
-        // as in upstream OpenJDK.
-        //
-        // private static final boolean ALLOW_NESTMATE_ACCESS = false;
-
-        private void checkSpecialCaller(Class<?> specialCaller) throws IllegalAccessException {
-            // Android-changed: No support for TRUSTED lookups. Also construct the
-            // IllegalAccessException by hand because the upstream code implicitly assumes
-            // that the lookupClass == specialCaller.
-            //
-            // if (allowedModes == TRUSTED)  return;
-            if (!hasPrivateAccess() || (specialCaller != lookupClass())) {
-                throw new IllegalAccessException("no private access for invokespecial : "
-                        + specialCaller + ", from" + this);
-            }
-        }
-
-        private void throwMakeAccessException(String message, Object from) throws
-                IllegalAccessException{
-            message = message + ": "+ toString();
-            if (from != null)  message += ", from " + from;
-            throw new IllegalAccessException(message);
-        }
-
-        private void checkReturnType(Method method, MethodType methodType)
-                throws NoSuchMethodException {
-            if (method.getReturnType() != methodType.rtype()) {
-                throw new NoSuchMethodException(method.getName() + methodType);
-            }
-        }
     }
 
-    /**
-     * "Cracks" {@code target} to reveal the underlying {@code MethodHandleImpl}.
-     */
-    private static MethodHandleImpl getMethodHandleImpl(MethodHandle target) {
-        // Special case : We implement handles to constructors as transformers,
-        // so we must extract the underlying handle from the transformer.
-        if (target instanceof Transformers.Construct) {
-            target = ((Transformers.Construct) target).getConstructorHandle();
-        }
-
-        // Special case: Var-args methods are also implemented as Transformers,
-        // so we should get the underlying handle in that case as well.
-        if (target instanceof Transformers.VarargsCollector) {
-            target = target.asFixedArity();
-        }
-
-        if (target instanceof MethodHandleImpl) {
-            return (MethodHandleImpl) target;
-        }
-
-        throw new IllegalArgumentException(target + " is not a direct handle");
-    }
-
-    /**
-     * Produces a method handle giving read access to elements of an array.
-     * The type of the method handle will have a return type of the array's
-     * element type.  Its first argument will be the array type,
-     * and the second will be {@code int}.
-     * @param arrayClass an array type
-     * @return a method handle which can load values from the given array type
-     * @throws NullPointerException if the argument is null
-     * @throws  IllegalArgumentException if arrayClass is not an array type
-     */
     public static
-    MethodHandle arrayElementGetter(Class<?> arrayClass) throws IllegalArgumentException {
-        final Class<?> componentType = arrayClass.getComponentType();
-        if (componentType == null) {
-            throw new IllegalArgumentException("Not an array type: " + arrayClass);
-        }
+    MethodHandle arrayElementGetter(Class<?> arrayClass) throws IllegalArgumentException { return null; }
 
-        if (componentType.isPrimitive()) {
-            try {
-                return Lookup.PUBLIC_LOOKUP.findStatic(MethodHandles.class,
-                        "arrayElementGetter",
-                        MethodType.methodType(componentType, arrayClass, int.class));
-            } catch (NoSuchMethodException | IllegalAccessException exception) {
-                throw new AssertionError(exception);
-            }
-        }
-
-        return new Transformers.ReferenceArrayElementGetter(arrayClass);
-    }
-
-    /** @hide */ public static byte arrayElementGetter(byte[] array, int i) { return array[i]; }
-    /** @hide */ public static boolean arrayElementGetter(boolean[] array, int i) { return array[i]; }
-    /** @hide */ public static char arrayElementGetter(char[] array, int i) { return array[i]; }
-    /** @hide */ public static short arrayElementGetter(short[] array, int i) { return array[i]; }
-    /** @hide */ public static int arrayElementGetter(int[] array, int i) { return array[i]; }
-    /** @hide */ public static long arrayElementGetter(long[] array, int i) { return array[i]; }
-    /** @hide */ public static float arrayElementGetter(float[] array, int i) { return array[i]; }
-    /** @hide */ public static double arrayElementGetter(double[] array, int i) { return array[i]; }
-
-    /**
-     * Produces a method handle giving write access to elements of an array.
-     * The type of the method handle will have a void return type.
-     * Its last argument will be the array's element type.
-     * The first and second arguments will be the array type and int.
-     * @param arrayClass the class of an array
-     * @return a method handle which can store values into the array type
-     * @throws NullPointerException if the argument is null
-     * @throws IllegalArgumentException if arrayClass is not an array type
-     */
     public static
-    MethodHandle arrayElementSetter(Class<?> arrayClass) throws IllegalArgumentException {
-        final Class<?> componentType = arrayClass.getComponentType();
-        if (componentType == null) {
-            throw new IllegalArgumentException("Not an array type: " + arrayClass);
-        }
+    MethodHandle arrayElementSetter(Class<?> arrayClass) throws IllegalArgumentException { return null; }
 
-        if (componentType.isPrimitive()) {
-            try {
-                return Lookup.PUBLIC_LOOKUP.findStatic(MethodHandles.class,
-                        "arrayElementSetter",
-                        MethodType.methodType(void.class, arrayClass, int.class, componentType));
-            } catch (NoSuchMethodException | IllegalAccessException exception) {
-                throw new AssertionError(exception);
-            }
-        }
-
-        return new Transformers.ReferenceArrayElementSetter(arrayClass);
-    }
-
-    /** @hide */
-    public static void arrayElementSetter(byte[] array, int i, byte val) { array[i] = val; }
-    /** @hide */
-    public static void arrayElementSetter(boolean[] array, int i, boolean val) { array[i] = val; }
-    /** @hide */
-    public static void arrayElementSetter(char[] array, int i, char val) { array[i] = val; }
-    /** @hide */
-    public static void arrayElementSetter(short[] array, int i, short val) { array[i] = val; }
-    /** @hide */
-    public static void arrayElementSetter(int[] array, int i, int val) { array[i] = val; }
-    /** @hide */
-    public static void arrayElementSetter(long[] array, int i, long val) { array[i] = val; }
-    /** @hide */
-    public static void arrayElementSetter(float[] array, int i, float val) { array[i] = val; }
-    /** @hide */
-    public static void arrayElementSetter(double[] array, int i, double val) { array[i] = val; }
-
-    // BEGIN Android-changed: OpenJDK 9+181 VarHandle API factory methods for bring up purposes.
-    /**
-     * Produces a VarHandle giving access to elements of an array of type
-     * {@code arrayClass}.  The VarHandle's variable type is the component type
-     * of {@code arrayClass} and the list of coordinate types is
-     * {@code (arrayClass, int)}, where the {@code int} coordinate type
-     * corresponds to an argument that is an index into an array.
-     * <p>
-     * Certain access modes of the returned VarHandle are unsupported under
-     * the following conditions:
-     * <ul>
-     * <li>if the component type is anything other than {@code byte},
-     *     {@code short}, {@code char}, {@code int}, {@code long},
-     *     {@code float}, or {@code double} then numeric atomic update access
-     *     modes are unsupported.
-     * <li>if the field type is anything other than {@code boolean},
-     *     {@code byte}, {@code short}, {@code char}, {@code int} or
-     *     {@code long} then bitwise atomic update access modes are
-     *     unsupported.
-     * </ul>
-     * <p>
-     * If the component type is {@code float} or {@code double} then numeric
-     * and atomic update access modes compare values using their bitwise
-     * representation (see {@link Float#floatToRawIntBits} and
-     * {@link Double#doubleToRawLongBits}, respectively).
-     * @apiNote
-     * Bitwise comparison of {@code float} values or {@code double} values,
-     * as performed by the numeric and atomic update access modes, differ
-     * from the primitive {@code ==} operator and the {@link Float#equals}
-     * and {@link Double#equals} methods, specifically with respect to
-     * comparing NaN values or comparing {@code -0.0} with {@code +0.0}.
-     * Care should be taken when performing a compare and set or a compare
-     * and exchange operation with such values since the operation may
-     * unexpectedly fail.
-     * There are many possible NaN values that are considered to be
-     * {@code NaN} in Java, although no IEEE 754 floating-point operation
-     * provided by Java can distinguish between them.  Operation failure can
-     * occur if the expected or witness value is a NaN value and it is
-     * transformed (perhaps in a platform specific manner) into another NaN
-     * value, and thus has a different bitwise representation (see
-     * {@link Float#intBitsToFloat} or {@link Double#longBitsToDouble} for more
-     * details).
-     * The values {@code -0.0} and {@code +0.0} have different bitwise
-     * representations but are considered equal when using the primitive
-     * {@code ==} operator.  Operation failure can occur if, for example, a
-     * numeric algorithm computes an expected value to be say {@code -0.0}
-     * and previously computed the witness value to be say {@code +0.0}.
-     * @param arrayClass the class of an array, of type {@code T[]}
-     * @return a VarHandle giving access to elements of an array
-     * @throws NullPointerException if the arrayClass is null
-     * @throws IllegalArgumentException if arrayClass is not an array type
-     * @since 9
-     * @hide
-     */
-    public static
-    VarHandle arrayElementVarHandle(Class<?> arrayClass) throws IllegalArgumentException {
-        unsupported("MethodHandles.arrayElementVarHandle()");  // TODO(b/65872996)
-        return null;
-    }
-
-    /**
-     * Produces a VarHandle giving access to elements of a {@code byte[]} array
-     * viewed as if it were a different primitive array type, such as
-     * {@code int[]} or {@code long[]}.
-     * The VarHandle's variable type is the component type of
-     * {@code viewArrayClass} and the list of coordinate types is
-     * {@code (byte[], int)}, where the {@code int} coordinate type
-     * corresponds to an argument that is an index into a {@code byte[]} array.
-     * The returned VarHandle accesses bytes at an index in a {@code byte[]}
-     * array, composing bytes to or from a value of the component type of
-     * {@code viewArrayClass} according to the given endianness.
-     * <p>
-     * The supported component types (variables types) are {@code short},
-     * {@code char}, {@code int}, {@code long}, {@code float} and
-     * {@code double}.
-     * <p>
-     * Access of bytes at a given index will result in an
-     * {@code IndexOutOfBoundsException} if the index is less than {@code 0}
-     * or greater than the {@code byte[]} array length minus the size (in bytes)
-     * of {@code T}.
-     * <p>
-     * Access of bytes at an index may be aligned or misaligned for {@code T},
-     * with respect to the underlying memory address, {@code A} say, associated
-     * with the array and index.
-     * If access is misaligned then access for anything other than the
-     * {@code get} and {@code set} access modes will result in an
-     * {@code IllegalStateException}.  In such cases atomic access is only
-     * guaranteed with respect to the largest power of two that divides the GCD
-     * of {@code A} and the size (in bytes) of {@code T}.
-     * If access is aligned then following access modes are supported and are
-     * guaranteed to support atomic access:
-     * <ul>
-     * <li>read write access modes for all {@code T}, with the exception of
-     *     access modes {@code get} and {@code set} for {@code long} and
-     *     {@code double} on 32-bit platforms.
-     * <li>atomic update access modes for {@code int}, {@code long},
-     *     {@code float} or {@code double}.
-     *     (Future major platform releases of the JDK may support additional
-     *     types for certain currently unsupported access modes.)
-     * <li>numeric atomic update access modes for {@code int} and {@code long}.
-     *     (Future major platform releases of the JDK may support additional
-     *     numeric types for certain currently unsupported access modes.)
-     * <li>bitwise atomic update access modes for {@code int} and {@code long}.
-     *     (Future major platform releases of the JDK may support additional
-     *     numeric types for certain currently unsupported access modes.)
-     * </ul>
-     * <p>
-     * Misaligned access, and therefore atomicity guarantees, may be determined
-     * for {@code byte[]} arrays without operating on a specific array.  Given
-     * an {@code index}, {@code T} and it's corresponding boxed type,
-     * {@code T_BOX}, misalignment may be determined as follows:
-     * <pre>{@code
-     * int sizeOfT = T_BOX.BYTES;  // size in bytes of T
-     * int misalignedAtZeroIndex = ByteBuffer.wrap(new byte[0]).
-     *     alignmentOffset(0, sizeOfT);
-     * int misalignedAtIndex = (misalignedAtZeroIndex + index) % sizeOfT;
-     * boolean isMisaligned = misalignedAtIndex != 0;
-     * }</pre>
-     * <p>
-     * If the variable type is {@code float} or {@code double} then atomic
-     * update access modes compare values using their bitwise representation
-     * (see {@link Float#floatToRawIntBits} and
-     * {@link Double#doubleToRawLongBits}, respectively).
-     * @param viewArrayClass the view array class, with a component type of
-     * type {@code T}
-     * @param byteOrder the endianness of the view array elements, as
-     * stored in the underlying {@code byte} array
-     * @return a VarHandle giving access to elements of a {@code byte[]} array
-     * viewed as if elements corresponding to the components type of the view
-     * array class
-     * @throws NullPointerException if viewArrayClass or byteOrder is null
-     * @throws IllegalArgumentException if viewArrayClass is not an array type
-     * @throws UnsupportedOperationException if the component type of
-     * viewArrayClass is not supported as a variable type
-     * @since 9
-     * @hide
-     */
-    public static
-    VarHandle byteArrayViewVarHandle(Class<?> viewArrayClass,
-                                     ByteOrder byteOrder) throws IllegalArgumentException {
-        unsupported("MethodHandles.byteArrayViewVarHandle()");  // TODO(b/65872996)
-        return null;
-    }
-
-    /**
-     * Produces a VarHandle giving access to elements of a {@code ByteBuffer}
-     * viewed as if it were an array of elements of a different primitive
-     * component type to that of {@code byte}, such as {@code int[]} or
-     * {@code long[]}.
-     * The VarHandle's variable type is the component type of
-     * {@code viewArrayClass} and the list of coordinate types is
-     * {@code (ByteBuffer, int)}, where the {@code int} coordinate type
-     * corresponds to an argument that is an index into a {@code byte[]} array.
-     * The returned VarHandle accesses bytes at an index in a
-     * {@code ByteBuffer}, composing bytes to or from a value of the component
-     * type of {@code viewArrayClass} according to the given endianness.
-     * <p>
-     * The supported component types (variables types) are {@code short},
-     * {@code char}, {@code int}, {@code long}, {@code float} and
-     * {@code double}.
-     * <p>
-     * Access will result in a {@code ReadOnlyBufferException} for anything
-     * other than the read access modes if the {@code ByteBuffer} is read-only.
-     * <p>
-     * Access of bytes at a given index will result in an
-     * {@code IndexOutOfBoundsException} if the index is less than {@code 0}
-     * or greater than the {@code ByteBuffer} limit minus the size (in bytes) of
-     * {@code T}.
-     * <p>
-     * Access of bytes at an index may be aligned or misaligned for {@code T},
-     * with respect to the underlying memory address, {@code A} say, associated
-     * with the {@code ByteBuffer} and index.
-     * If access is misaligned then access for anything other than the
-     * {@code get} and {@code set} access modes will result in an
-     * {@code IllegalStateException}.  In such cases atomic access is only
-     * guaranteed with respect to the largest power of two that divides the GCD
-     * of {@code A} and the size (in bytes) of {@code T}.
-     * If access is aligned then following access modes are supported and are
-     * guaranteed to support atomic access:
-     * <ul>
-     * <li>read write access modes for all {@code T}, with the exception of
-     *     access modes {@code get} and {@code set} for {@code long} and
-     *     {@code double} on 32-bit platforms.
-     * <li>atomic update access modes for {@code int}, {@code long},
-     *     {@code float} or {@code double}.
-     *     (Future major platform releases of the JDK may support additional
-     *     types for certain currently unsupported access modes.)
-     * <li>numeric atomic update access modes for {@code int} and {@code long}.
-     *     (Future major platform releases of the JDK may support additional
-     *     numeric types for certain currently unsupported access modes.)
-     * <li>bitwise atomic update access modes for {@code int} and {@code long}.
-     *     (Future major platform releases of the JDK may support additional
-     *     numeric types for certain currently unsupported access modes.)
-     * </ul>
-     * <p>
-     * Misaligned access, and therefore atomicity guarantees, may be determined
-     * for a {@code ByteBuffer}, {@code bb} (direct or otherwise), an
-     * {@code index}, {@code T} and it's corresponding boxed type,
-     * {@code T_BOX}, as follows:
-     * <pre>{@code
-     * int sizeOfT = T_BOX.BYTES;  // size in bytes of T
-     * ByteBuffer bb = ...
-     * int misalignedAtIndex = bb.alignmentOffset(index, sizeOfT);
-     * boolean isMisaligned = misalignedAtIndex != 0;
-     * }</pre>
-     * <p>
-     * If the variable type is {@code float} or {@code double} then atomic
-     * update access modes compare values using their bitwise representation
-     * (see {@link Float#floatToRawIntBits} and
-     * {@link Double#doubleToRawLongBits}, respectively).
-     * @param viewArrayClass the view array class, with a component type of
-     * type {@code T}
-     * @param byteOrder the endianness of the view array elements, as
-     * stored in the underlying {@code ByteBuffer} (Note this overrides the
-     * endianness of a {@code ByteBuffer})
-     * @return a VarHandle giving access to elements of a {@code ByteBuffer}
-     * viewed as if elements corresponding to the components type of the view
-     * array class
-     * @throws NullPointerException if viewArrayClass or byteOrder is null
-     * @throws IllegalArgumentException if viewArrayClass is not an array type
-     * @throws UnsupportedOperationException if the component type of
-     * viewArrayClass is not supported as a variable type
-     * @since 9
-     * @hide
-     */
-    public static
-    VarHandle byteBufferViewVarHandle(Class<?> viewArrayClass,
-                                      ByteOrder byteOrder) throws IllegalArgumentException {
-
-        unsupported("MethodHandles.byteBufferViewVarHandle()");  // TODO(b/65872996)
-        return null;
-    }
-    // END Android-changed: OpenJDK 9+181 VarHandle API factory methods for bring up purposes.
-
-    /// method handle invocation (reflective style)
-
-    /**
-     * Produces a method handle which will invoke any method handle of the
-     * given {@code type}, with a given number of trailing arguments replaced by
-     * a single trailing {@code Object[]} array.
-     * The resulting invoker will be a method handle with the following
-     * arguments:
-     * <ul>
-     * <li>a single {@code MethodHandle} target
-     * <li>zero or more leading values (counted by {@code leadingArgCount})
-     * <li>an {@code Object[]} array containing trailing arguments
-     * </ul>
-     * <p>
-     * The invoker will invoke its target like a call to {@link MethodHandle#invoke invoke} with
-     * the indicated {@code type}.
-     * That is, if the target is exactly of the given {@code type}, it will behave
-     * like {@code invokeExact}; otherwise it behave as if {@link MethodHandle#asType asType}
-     * is used to convert the target to the required {@code type}.
-     * <p>
-     * The type of the returned invoker will not be the given {@code type}, but rather
-     * will have all parameters except the first {@code leadingArgCount}
-     * replaced by a single array of type {@code Object[]}, which will be
-     * the final parameter.
-     * <p>
-     * Before invoking its target, the invoker will spread the final array, apply
-     * reference casts as necessary, and unbox and widen primitive arguments.
-     * If, when the invoker is called, the supplied array argument does
-     * not have the correct number of elements, the invoker will throw
-     * an {@link IllegalArgumentException} instead of invoking the target.
-     * <p>
-     * This method is equivalent to the following code (though it may be more efficient):
-     * <blockquote><pre>{@code
-MethodHandle invoker = MethodHandles.invoker(type);
-int spreadArgCount = type.parameterCount() - leadingArgCount;
-invoker = invoker.asSpreader(Object[].class, spreadArgCount);
-return invoker;
-     * }</pre></blockquote>
-     * This method throws no reflective or security exceptions.
-     * @param type the desired target type
-     * @param leadingArgCount number of fixed arguments, to be passed unchanged to the target
-     * @return a method handle suitable for invoking any method handle of the given type
-     * @throws NullPointerException if {@code type} is null
-     * @throws IllegalArgumentException if {@code leadingArgCount} is not in
-     *                  the range from 0 to {@code type.parameterCount()} inclusive,
-     *                  or if the resulting method handle's type would have
-     *          <a href="MethodHandle.html#maxarity">too many parameters</a>
-     */
     static public
-    MethodHandle spreadInvoker(MethodType type, int leadingArgCount) {
-        if (leadingArgCount < 0 || leadingArgCount > type.parameterCount())
-            throw newIllegalArgumentException("bad argument count", leadingArgCount);
+    MethodHandle spreadInvoker(MethodType type, int leadingArgCount) { return null; }
 
-        MethodHandle invoker = MethodHandles.invoker(type);
-        int spreadArgCount = type.parameterCount() - leadingArgCount;
-        invoker = invoker.asSpreader(Object[].class, spreadArgCount);
-        return invoker;
-    }
-
-    /**
-     * Produces a special <em>invoker method handle</em> which can be used to
-     * invoke any method handle of the given type, as if by {@link MethodHandle#invokeExact invokeExact}.
-     * The resulting invoker will have a type which is
-     * exactly equal to the desired type, except that it will accept
-     * an additional leading argument of type {@code MethodHandle}.
-     * <p>
-     * This method is equivalent to the following code (though it may be more efficient):
-     * {@code publicLookup().findVirtual(MethodHandle.class, "invokeExact", type)}
-     *
-     * <p style="font-size:smaller;">
-     * <em>Discussion:</em>
-     * Invoker method handles can be useful when working with variable method handles
-     * of unknown types.
-     * For example, to emulate an {@code invokeExact} call to a variable method
-     * handle {@code M}, extract its type {@code T},
-     * look up the invoker method {@code X} for {@code T},
-     * and call the invoker method, as {@code X.invoke(T, A...)}.
-     * (It would not work to call {@code X.invokeExact}, since the type {@code T}
-     * is unknown.)
-     * If spreading, collecting, or other argument transformations are required,
-     * they can be applied once to the invoker {@code X} and reused on many {@code M}
-     * method handle values, as long as they are compatible with the type of {@code X}.
-     * <p style="font-size:smaller;">
-     * <em>(Note:  The invoker method is not available via the Core Reflection API.
-     * An attempt to call {@linkplain java.lang.reflect.Method#invoke java.lang.reflect.Method.invoke}
-     * on the declared {@code invokeExact} or {@code invoke} method will raise an
-     * {@link java.lang.UnsupportedOperationException UnsupportedOperationException}.)</em>
-     * <p>
-     * This method throws no reflective or security exceptions.
-     * @param type the desired target type
-     * @return a method handle suitable for invoking any method handle of the given type
-     * @throws IllegalArgumentException if the resulting method handle's type would have
-     *          <a href="MethodHandle.html#maxarity">too many parameters</a>
-     */
     static public
-    MethodHandle exactInvoker(MethodType type) {
-        return new Transformers.Invoker(type, true /* isExactInvoker */);
-    }
+    MethodHandle exactInvoker(MethodType type) { return null; }
 
-    /**
-     * Produces a special <em>invoker method handle</em> which can be used to
-     * invoke any method handle compatible with the given type, as if by {@link MethodHandle#invoke invoke}.
-     * The resulting invoker will have a type which is
-     * exactly equal to the desired type, except that it will accept
-     * an additional leading argument of type {@code MethodHandle}.
-     * <p>
-     * Before invoking its target, if the target differs from the expected type,
-     * the invoker will apply reference casts as
-     * necessary and box, unbox, or widen primitive values, as if by {@link MethodHandle#asType asType}.
-     * Similarly, the return value will be converted as necessary.
-     * If the target is a {@linkplain MethodHandle#asVarargsCollector variable arity method handle},
-     * the required arity conversion will be made, again as if by {@link MethodHandle#asType asType}.
-     * <p>
-     * This method is equivalent to the following code (though it may be more efficient):
-     * {@code publicLookup().findVirtual(MethodHandle.class, "invoke", type)}
-     * <p style="font-size:smaller;">
-     * <em>Discussion:</em>
-     * A {@linkplain MethodType#genericMethodType general method type} is one which
-     * mentions only {@code Object} arguments and return values.
-     * An invoker for such a type is capable of calling any method handle
-     * of the same arity as the general type.
-     * <p style="font-size:smaller;">
-     * <em>(Note:  The invoker method is not available via the Core Reflection API.
-     * An attempt to call {@linkplain java.lang.reflect.Method#invoke java.lang.reflect.Method.invoke}
-     * on the declared {@code invokeExact} or {@code invoke} method will raise an
-     * {@link java.lang.UnsupportedOperationException UnsupportedOperationException}.)</em>
-     * <p>
-     * This method throws no reflective or security exceptions.
-     * @param type the desired target type
-     * @return a method handle suitable for invoking any method handle convertible to the given type
-     * @throws IllegalArgumentException if the resulting method handle's type would have
-     *          <a href="MethodHandle.html#maxarity">too many parameters</a>
-     */
     static public
-    MethodHandle invoker(MethodType type) {
-        return new Transformers.Invoker(type, false /* isExactInvoker */);
-    }
+    MethodHandle invoker(MethodType type) { return null; }
 
-    /**
-     * Produces a special <em>invoker method handle</em> which can be used to
-     * invoke a signature-polymorphic access mode method on any VarHandle whose
-     * associated access mode type is compatible with the given type.
-     * The resulting invoker will have a type which is exactly equal to the
-     * desired given type, except that it will accept an additional leading
-     * argument of type {@code VarHandle}.
-     *
-     * @param accessMode the VarHandle access mode
-     * @param type the desired target type
-     * @return a method handle suitable for invoking an access mode method of
-     *         any VarHandle whose access mode type is of the given type.
-     * @since 9
-     * @hide
-     */
-    static public
-    MethodHandle varHandleExactInvoker(VarHandle.AccessMode accessMode, MethodType type) {
-        unsupported("MethodHandles.varHandleExactInvoker()");  // TODO(b/65872996)
-        return null;
-    }
-
-    /**
-     * Produces a special <em>invoker method handle</em> which can be used to
-     * invoke a signature-polymorphic access mode method on any VarHandle whose
-     * associated access mode type is compatible with the given type.
-     * The resulting invoker will have a type which is exactly equal to the
-     * desired given type, except that it will accept an additional leading
-     * argument of type {@code VarHandle}.
-     * <p>
-     * Before invoking its target, if the access mode type differs from the
-     * desired given type, the invoker will apply reference casts as necessary
-     * and box, unbox, or widen primitive values, as if by
-     * {@link MethodHandle#asType asType}.  Similarly, the return value will be
-     * converted as necessary.
-     * <p>
-     * This method is equivalent to the following code (though it may be more
-     * efficient): {@code publicLookup().findVirtual(VarHandle.class, accessMode.name(), type)}
-     *
-     * @param accessMode the VarHandle access mode
-     * @param type the desired target type
-     * @return a method handle suitable for invoking an access mode method of
-     *         any VarHandle whose access mode type is convertible to the given
-     *         type.
-     * @since 9
-     * @hide
-     */
-    static public
-    MethodHandle varHandleInvoker(VarHandle.AccessMode accessMode, MethodType type) {
-        unsupported("MethodHandles.varHandleInvoker()");  // TODO(b/65872996)
-        return null;
-    }
-
-    // Android-changed: Basic invokers are not supported.
-    //
-    // static /*non-public*/
-    // MethodHandle basicInvoker(MethodType type) {
-    //     return type.invokers().basicInvoker();
-    // }
-
-     /// method handle modification (creation from other method handles)
-
-    /**
-     * Produces a method handle which adapts the type of the
-     * given method handle to a new type by pairwise argument and return type conversion.
-     * The original type and new type must have the same number of arguments.
-     * The resulting method handle is guaranteed to report a type
-     * which is equal to the desired new type.
-     * <p>
-     * If the original type and new type are equal, returns target.
-     * <p>
-     * The same conversions are allowed as for {@link MethodHandle#asType MethodHandle.asType},
-     * and some additional conversions are also applied if those conversions fail.
-     * Given types <em>T0</em>, <em>T1</em>, one of the following conversions is applied
-     * if possible, before or instead of any conversions done by {@code asType}:
-     * <ul>
-     * <li>If <em>T0</em> and <em>T1</em> are references, and <em>T1</em> is an interface type,
-     *     then the value of type <em>T0</em> is passed as a <em>T1</em> without a cast.
-     *     (This treatment of interfaces follows the usage of the bytecode verifier.)
-     * <li>If <em>T0</em> is boolean and <em>T1</em> is another primitive,
-     *     the boolean is converted to a byte value, 1 for true, 0 for false.
-     *     (This treatment follows the usage of the bytecode verifier.)
-     * <li>If <em>T1</em> is boolean and <em>T0</em> is another primitive,
-     *     <em>T0</em> is converted to byte via Java casting conversion (JLS 5.5),
-     *     and the low order bit of the result is tested, as if by {@code (x & 1) != 0}.
-     * <li>If <em>T0</em> and <em>T1</em> are primitives other than boolean,
-     *     then a Java casting conversion (JLS 5.5) is applied.
-     *     (Specifically, <em>T0</em> will convert to <em>T1</em> by
-     *     widening and/or narrowing.)
-     * <li>If <em>T0</em> is a reference and <em>T1</em> a primitive, an unboxing
-     *     conversion will be applied at runtime, possibly followed
-     *     by a Java casting conversion (JLS 5.5) on the primitive value,
-     *     possibly followed by a conversion from byte to boolean by testing
-     *     the low-order bit.
-     * <li>If <em>T0</em> is a reference and <em>T1</em> a primitive,
-     *     and if the reference is null at runtime, a zero value is introduced.
-     * </ul>
-     * @param target the method handle to invoke after arguments are retyped
-     * @param newType the expected type of the new method handle
-     * @return a method handle which delegates to the target after performing
-     *           any necessary argument conversions, and arranges for any
-     *           necessary return value conversions
-     * @throws NullPointerException if either argument is null
-     * @throws WrongMethodTypeException if the conversion cannot be made
-     * @see MethodHandle#asType
-     */
     public static
-    MethodHandle explicitCastArguments(MethodHandle target, MethodType newType) {
-        explicitCastArgumentsChecks(target, newType);
-        // use the asTypeCache when possible:
-        MethodType oldType = target.type();
-        if (oldType == newType) return target;
-        if (oldType.explicitCastEquivalentToAsType(newType)) {
-            return target.asFixedArity().asType(newType);
-        }
+    MethodHandle explicitCastArguments(MethodHandle target, MethodType newType) { return null; }
 
-        return new Transformers.ExplicitCastArguments(target, newType);
-    }
-
-    private static void explicitCastArgumentsChecks(MethodHandle target, MethodType newType) {
-        if (target.type().parameterCount() != newType.parameterCount()) {
-            throw new WrongMethodTypeException("cannot explicitly cast " + target + " to " + newType);
-        }
-    }
-
-    /**
-     * Produces a method handle which adapts the calling sequence of the
-     * given method handle to a new type, by reordering the arguments.
-     * The resulting method handle is guaranteed to report a type
-     * which is equal to the desired new type.
-     * <p>
-     * The given array controls the reordering.
-     * Call {@code #I} the number of incoming parameters (the value
-     * {@code newType.parameterCount()}, and call {@code #O} the number
-     * of outgoing parameters (the value {@code target.type().parameterCount()}).
-     * Then the length of the reordering array must be {@code #O},
-     * and each element must be a non-negative number less than {@code #I}.
-     * For every {@code N} less than {@code #O}, the {@code N}-th
-     * outgoing argument will be taken from the {@code I}-th incoming
-     * argument, where {@code I} is {@code reorder[N]}.
-     * <p>
-     * No argument or return value conversions are applied.
-     * The type of each incoming argument, as determined by {@code newType},
-     * must be identical to the type of the corresponding outgoing parameter
-     * or parameters in the target method handle.
-     * The return type of {@code newType} must be identical to the return
-     * type of the original target.
-     * <p>
-     * The reordering array need not specify an actual permutation.
-     * An incoming argument will be duplicated if its index appears
-     * more than once in the array, and an incoming argument will be dropped
-     * if its index does not appear in the array.
-     * As in the case of {@link #dropArguments(MethodHandle,int,List) dropArguments},
-     * incoming arguments which are not mentioned in the reordering array
-     * are may be any type, as determined only by {@code newType}.
-     * <blockquote><pre>{@code
-import static java.lang.invoke.MethodHandles.*;
-import static java.lang.invoke.MethodType.*;
-...
-MethodType intfn1 = methodType(int.class, int.class);
-MethodType intfn2 = methodType(int.class, int.class, int.class);
-MethodHandle sub = ... (int x, int y) -> (x-y) ...;
-assert(sub.type().equals(intfn2));
-MethodHandle sub1 = permuteArguments(sub, intfn2, 0, 1);
-MethodHandle rsub = permuteArguments(sub, intfn2, 1, 0);
-assert((int)rsub.invokeExact(1, 100) == 99);
-MethodHandle add = ... (int x, int y) -> (x+y) ...;
-assert(add.type().equals(intfn2));
-MethodHandle twice = permuteArguments(add, intfn1, 0, 0);
-assert(twice.type().equals(intfn1));
-assert((int)twice.invokeExact(21) == 42);
-     * }</pre></blockquote>
-     * @param target the method handle to invoke after arguments are reordered
-     * @param newType the expected type of the new method handle
-     * @param reorder an index array which controls the reordering
-     * @return a method handle which delegates to the target after it
-     *           drops unused arguments and moves and/or duplicates the other arguments
-     * @throws NullPointerException if any argument is null
-     * @throws IllegalArgumentException if the index array length is not equal to
-     *                  the arity of the target, or if any index array element
-     *                  not a valid index for a parameter of {@code newType},
-     *                  or if two corresponding parameter types in
-     *                  {@code target.type()} and {@code newType} are not identical,
-     */
     public static
-    MethodHandle permuteArguments(MethodHandle target, MethodType newType, int... reorder) {
-        reorder = reorder.clone();  // get a private copy
-        MethodType oldType = target.type();
-        permuteArgumentChecks(reorder, newType, oldType);
+    MethodHandle permuteArguments(MethodHandle target, MethodType newType, int... reorder) { return null; }
 
-        return new Transformers.PermuteArguments(newType, target, reorder);
-    }
-
-    // Android-changed: findFirstDupOrDrop is unused and removed.
-    // private static int findFirstDupOrDrop(int[] reorder, int newArity);
-
-    private static boolean permuteArgumentChecks(int[] reorder, MethodType newType, MethodType oldType) {
-        if (newType.returnType() != oldType.returnType())
-            throw newIllegalArgumentException("return types do not match",
-                    oldType, newType);
-        if (reorder.length == oldType.parameterCount()) {
-            int limit = newType.parameterCount();
-            boolean bad = false;
-            for (int j = 0; j < reorder.length; j++) {
-                int i = reorder[j];
-                if (i < 0 || i >= limit) {
-                    bad = true; break;
-                }
-                Class<?> src = newType.parameterType(i);
-                Class<?> dst = oldType.parameterType(j);
-                if (src != dst)
-                    throw newIllegalArgumentException("parameter types do not match after reorder",
-                            oldType, newType);
-            }
-            if (!bad)  return true;
-        }
-        throw newIllegalArgumentException("bad reorder array: "+Arrays.toString(reorder));
-    }
-
-    /**
-     * Produces a method handle of the requested return type which returns the given
-     * constant value every time it is invoked.
-     * <p>
-     * Before the method handle is returned, the passed-in value is converted to the requested type.
-     * If the requested type is primitive, widening primitive conversions are attempted,
-     * else reference conversions are attempted.
-     * <p>The returned method handle is equivalent to {@code identity(type).bindTo(value)}.
-     * @param type the return type of the desired method handle
-     * @param value the value to return
-     * @return a method handle of the given return type and no arguments, which always returns the given value
-     * @throws NullPointerException if the {@code type} argument is null
-     * @throws ClassCastException if the value cannot be converted to the required return type
-     * @throws IllegalArgumentException if the given type is {@code void.class}
-     */
     public static
-    MethodHandle constant(Class<?> type, Object value) {
-        if (type.isPrimitive()) {
-            if (type == void.class)
-                throw newIllegalArgumentException("void type");
-            Wrapper w = Wrapper.forPrimitiveType(type);
-            value = w.convert(value, type);
-        }
+    MethodHandle constant(Class<?> type, Object value) { return null; }
 
-        return new Transformers.Constant(type, value);
-    }
-
-    /**
-     * Produces a method handle which returns its sole argument when invoked.
-     * @param type the type of the sole parameter and return value of the desired method handle
-     * @return a unary method handle which accepts and returns the given type
-     * @throws NullPointerException if the argument is null
-     * @throws IllegalArgumentException if the given type is {@code void.class}
-     */
     public static
-    MethodHandle identity(Class<?> type) {
-        if (type == null) {
-            throw new NullPointerException("type == null");
-        }
+    MethodHandle identity(Class<?> type) { return null; }
 
-        if (type.isPrimitive()) {
-            try {
-                return Lookup.PUBLIC_LOOKUP.findStatic(MethodHandles.class, "identity",
-                        MethodType.methodType(type, type));
-            } catch (NoSuchMethodException | IllegalAccessException e) {
-                throw new AssertionError(e);
-            }
-        }
-
-        return new Transformers.ReferenceIdentity(type);
-    }
-
-    /** @hide */ public static byte identity(byte val) { return val; }
-    /** @hide */ public static boolean identity(boolean val) { return val; }
-    /** @hide */ public static char identity(char val) { return val; }
-    /** @hide */ public static short identity(short val) { return val; }
-    /** @hide */ public static int identity(int val) { return val; }
-    /** @hide */ public static long identity(long val) { return val; }
-    /** @hide */ public static float identity(float val) { return val; }
-    /** @hide */ public static double identity(double val) { return val; }
-
-    /**
-     * Provides a target method handle with one or more <em>bound arguments</em>
-     * in advance of the method handle's invocation.
-     * The formal parameters to the target corresponding to the bound
-     * arguments are called <em>bound parameters</em>.
-     * Returns a new method handle which saves away the bound arguments.
-     * When it is invoked, it receives arguments for any non-bound parameters,
-     * binds the saved arguments to their corresponding parameters,
-     * and calls the original target.
-     * <p>
-     * The type of the new method handle will drop the types for the bound
-     * parameters from the original target type, since the new method handle
-     * will no longer require those arguments to be supplied by its callers.
-     * <p>
-     * Each given argument object must match the corresponding bound parameter type.
-     * If a bound parameter type is a primitive, the argument object
-     * must be a wrapper, and will be unboxed to produce the primitive value.
-     * <p>
-     * The {@code pos} argument selects which parameters are to be bound.
-     * It may range between zero and <i>N-L</i> (inclusively),
-     * where <i>N</i> is the arity of the target method handle
-     * and <i>L</i> is the length of the values array.
-     * @param target the method handle to invoke after the argument is inserted
-     * @param pos where to insert the argument (zero for the first)
-     * @param values the series of arguments to insert
-     * @return a method handle which inserts an additional argument,
-     *         before calling the original method handle
-     * @throws NullPointerException if the target or the {@code values} array is null
-     * @see MethodHandle#bindTo
-     */
     public static
-    MethodHandle insertArguments(MethodHandle target, int pos, Object... values) {
-        int insCount = values.length;
-        Class<?>[] ptypes = insertArgumentsChecks(target, insCount, pos);
-        if (insCount == 0)  {
-            return target;
-        }
+    MethodHandle insertArguments(MethodHandle target, int pos, Object... values) { return null; }
 
-        // Throw ClassCastExceptions early if we can't cast any of the provided values
-        // to the required type.
-        for (int i = 0; i < insCount; i++) {
-            final Class<?> ptype = ptypes[pos + i];
-            if (!ptype.isPrimitive()) {
-                ptypes[pos + i].cast(values[i]);
-            } else {
-                // Will throw a ClassCastException if something terrible happens.
-                values[i] = Wrapper.forPrimitiveType(ptype).convert(values[i], ptype);
-            }
-        }
-
-        return new Transformers.InsertArguments(target, pos, values);
-    }
-
-    // Android-changed: insertArgumentPrimitive is unused.
-    //
-    // private static BoundMethodHandle insertArgumentPrimitive(BoundMethodHandle result, int pos,
-    //                                                          Class<?> ptype, Object value) {
-    //     Wrapper w = Wrapper.forPrimitiveType(ptype);
-    //     // perform unboxing and/or primitive conversion
-    //     value = w.convert(value, ptype);
-    //     switch (w) {
-    //     case INT:     return result.bindArgumentI(pos, (int)value);
-    //     case LONG:    return result.bindArgumentJ(pos, (long)value);
-    //     case FLOAT:   return result.bindArgumentF(pos, (float)value);
-    //     case DOUBLE:  return result.bindArgumentD(pos, (double)value);
-    //     default:      return result.bindArgumentI(pos, ValueConversions.widenSubword(value));
-    //     }
-    // }
-
-    private static Class<?>[] insertArgumentsChecks(MethodHandle target, int insCount, int pos) throws RuntimeException {
-        MethodType oldType = target.type();
-        int outargs = oldType.parameterCount();
-        int inargs  = outargs - insCount;
-        if (inargs < 0)
-            throw newIllegalArgumentException("too many values to insert");
-        if (pos < 0 || pos > inargs)
-            throw newIllegalArgumentException("no argument type to append");
-        return oldType.ptypes();
-    }
-
-    /**
-     * Produces a method handle which will discard some dummy arguments
-     * before calling some other specified <i>target</i> method handle.
-     * The type of the new method handle will be the same as the target's type,
-     * except it will also include the dummy argument types,
-     * at some given position.
-     * <p>
-     * The {@code pos} argument may range between zero and <i>N</i>,
-     * where <i>N</i> is the arity of the target.
-     * If {@code pos} is zero, the dummy arguments will precede
-     * the target's real arguments; if {@code pos} is <i>N</i>
-     * they will come after.
-     * <p>
-     * <b>Example:</b>
-     * <blockquote><pre>{@code
-import static java.lang.invoke.MethodHandles.*;
-import static java.lang.invoke.MethodType.*;
-...
-MethodHandle cat = lookup().findVirtual(String.class,
-  "concat", methodType(String.class, String.class));
-assertEquals("xy", (String) cat.invokeExact("x", "y"));
-MethodType bigType = cat.type().insertParameterTypes(0, int.class, String.class);
-MethodHandle d0 = dropArguments(cat, 0, bigType.parameterList().subList(0,2));
-assertEquals(bigType, d0.type());
-assertEquals("yz", (String) d0.invokeExact(123, "x", "y", "z"));
-     * }</pre></blockquote>
-     * <p>
-     * This method is also equivalent to the following code:
-     * <blockquote><pre>
-     * {@link #dropArguments(MethodHandle,int,Class...) dropArguments}{@code (target, pos, valueTypes.toArray(new Class[0]))}
-     * </pre></blockquote>
-     * @param target the method handle to invoke after the arguments are dropped
-     * @param valueTypes the type(s) of the argument(s) to drop
-     * @param pos position of first argument to drop (zero for the leftmost)
-     * @return a method handle which drops arguments of the given types,
-     *         before calling the original method handle
-     * @throws NullPointerException if the target is null,
-     *                              or if the {@code valueTypes} list or any of its elements is null
-     * @throws IllegalArgumentException if any element of {@code valueTypes} is {@code void.class},
-     *                  or if {@code pos} is negative or greater than the arity of the target,
-     *                  or if the new method handle's type would have too many parameters
-     */
     public static
-    MethodHandle dropArguments(MethodHandle target, int pos, List<Class<?>> valueTypes) {
-        valueTypes = copyTypes(valueTypes);
-        MethodType oldType = target.type();  // get NPE
-        int dropped = dropArgumentChecks(oldType, pos, valueTypes);
+    MethodHandle dropArguments(MethodHandle target, int pos, List<Class<?>> valueTypes) { return null; }
 
-        MethodType newType = oldType.insertParameterTypes(pos, valueTypes);
-        if (dropped == 0) {
-            return target;
-        }
-
-        return new Transformers.DropArguments(newType, target, pos, valueTypes.size());
-    }
-
-    private static List<Class<?>> copyTypes(List<Class<?>> types) {
-        Object[] a = types.toArray();
-        return Arrays.asList(Arrays.copyOf(a, a.length, Class[].class));
-    }
-
-    private static int dropArgumentChecks(MethodType oldType, int pos, List<Class<?>> valueTypes) {
-        int dropped = valueTypes.size();
-        MethodType.checkSlotCount(dropped);
-        int outargs = oldType.parameterCount();
-        int inargs  = outargs + dropped;
-        if (pos < 0 || pos > outargs)
-            throw newIllegalArgumentException("no argument type to remove"
-                    + Arrays.asList(oldType, pos, valueTypes, inargs, outargs)
-                    );
-        return dropped;
-    }
-
-    /**
-     * Produces a method handle which will discard some dummy arguments
-     * before calling some other specified <i>target</i> method handle.
-     * The type of the new method handle will be the same as the target's type,
-     * except it will also include the dummy argument types,
-     * at some given position.
-     * <p>
-     * The {@code pos} argument may range between zero and <i>N</i>,
-     * where <i>N</i> is the arity of the target.
-     * If {@code pos} is zero, the dummy arguments will precede
-     * the target's real arguments; if {@code pos} is <i>N</i>
-     * they will come after.
-     * <p>
-     * <b>Example:</b>
-     * <blockquote><pre>{@code
-import static java.lang.invoke.MethodHandles.*;
-import static java.lang.invoke.MethodType.*;
-...
-MethodHandle cat = lookup().findVirtual(String.class,
-  "concat", methodType(String.class, String.class));
-assertEquals("xy", (String) cat.invokeExact("x", "y"));
-MethodHandle d0 = dropArguments(cat, 0, String.class);
-assertEquals("yz", (String) d0.invokeExact("x", "y", "z"));
-MethodHandle d1 = dropArguments(cat, 1, String.class);
-assertEquals("xz", (String) d1.invokeExact("x", "y", "z"));
-MethodHandle d2 = dropArguments(cat, 2, String.class);
-assertEquals("xy", (String) d2.invokeExact("x", "y", "z"));
-MethodHandle d12 = dropArguments(cat, 1, int.class, boolean.class);
-assertEquals("xz", (String) d12.invokeExact("x", 12, true, "z"));
-     * }</pre></blockquote>
-     * <p>
-     * This method is also equivalent to the following code:
-     * <blockquote><pre>
-     * {@link #dropArguments(MethodHandle,int,List) dropArguments}{@code (target, pos, Arrays.asList(valueTypes))}
-     * </pre></blockquote>
-     * @param target the method handle to invoke after the arguments are dropped
-     * @param valueTypes the type(s) of the argument(s) to drop
-     * @param pos position of first argument to drop (zero for the leftmost)
-     * @return a method handle which drops arguments of the given types,
-     *         before calling the original method handle
-     * @throws NullPointerException if the target is null,
-     *                              or if the {@code valueTypes} array or any of its elements is null
-     * @throws IllegalArgumentException if any element of {@code valueTypes} is {@code void.class},
-     *                  or if {@code pos} is negative or greater than the arity of the target,
-     *                  or if the new method handle's type would have
-     *                  <a href="MethodHandle.html#maxarity">too many parameters</a>
-     */
     public static
-    MethodHandle dropArguments(MethodHandle target, int pos, Class<?>... valueTypes) {
-        return dropArguments(target, pos, Arrays.asList(valueTypes));
-    }
+    MethodHandle dropArguments(MethodHandle target, int pos, Class<?>... valueTypes) { return null; }
 
-    /**
-     * Adapts a target method handle by pre-processing
-     * one or more of its arguments, each with its own unary filter function,
-     * and then calling the target with each pre-processed argument
-     * replaced by the result of its corresponding filter function.
-     * <p>
-     * The pre-processing is performed by one or more method handles,
-     * specified in the elements of the {@code filters} array.
-     * The first element of the filter array corresponds to the {@code pos}
-     * argument of the target, and so on in sequence.
-     * <p>
-     * Null arguments in the array are treated as identity functions,
-     * and the corresponding arguments left unchanged.
-     * (If there are no non-null elements in the array, the original target is returned.)
-     * Each filter is applied to the corresponding argument of the adapter.
-     * <p>
-     * If a filter {@code F} applies to the {@code N}th argument of
-     * the target, then {@code F} must be a method handle which
-     * takes exactly one argument.  The type of {@code F}'s sole argument
-     * replaces the corresponding argument type of the target
-     * in the resulting adapted method handle.
-     * The return type of {@code F} must be identical to the corresponding
-     * parameter type of the target.
-     * <p>
-     * It is an error if there are elements of {@code filters}
-     * (null or not)
-     * which do not correspond to argument positions in the target.
-     * <p><b>Example:</b>
-     * <blockquote><pre>{@code
-import static java.lang.invoke.MethodHandles.*;
-import static java.lang.invoke.MethodType.*;
-...
-MethodHandle cat = lookup().findVirtual(String.class,
-  "concat", methodType(String.class, String.class));
-MethodHandle upcase = lookup().findVirtual(String.class,
-  "toUpperCase", methodType(String.class));
-assertEquals("xy", (String) cat.invokeExact("x", "y"));
-MethodHandle f0 = filterArguments(cat, 0, upcase);
-assertEquals("Xy", (String) f0.invokeExact("x", "y")); // Xy
-MethodHandle f1 = filterArguments(cat, 1, upcase);
-assertEquals("xY", (String) f1.invokeExact("x", "y")); // xY
-MethodHandle f2 = filterArguments(cat, 0, upcase, upcase);
-assertEquals("XY", (String) f2.invokeExact("x", "y")); // XY
-     * }</pre></blockquote>
-     * <p> Here is pseudocode for the resulting adapter:
-     * <blockquote><pre>{@code
-     * V target(P... p, A[i]... a[i], B... b);
-     * A[i] filter[i](V[i]);
-     * T adapter(P... p, V[i]... v[i], B... b) {
-     *   return target(p..., f[i](v[i])..., b...);
-     * }
-     * }</pre></blockquote>
-     *
-     * @param target the method handle to invoke after arguments are filtered
-     * @param pos the position of the first argument to filter
-     * @param filters method handles to call initially on filtered arguments
-     * @return method handle which incorporates the specified argument filtering logic
-     * @throws NullPointerException if the target is null
-     *                              or if the {@code filters} array is null
-     * @throws IllegalArgumentException if a non-null element of {@code filters}
-     *          does not match a corresponding argument type of target as described above,
-     *          or if the {@code pos+filters.length} is greater than {@code target.type().parameterCount()},
-     *          or if the resulting method handle's type would have
-     *          <a href="MethodHandle.html#maxarity">too many parameters</a>
-     */
     public static
-    MethodHandle filterArguments(MethodHandle target, int pos, MethodHandle... filters) {
-        filterArgumentsCheckArity(target, pos, filters);
+    MethodHandle filterArguments(MethodHandle target, int pos, MethodHandle... filters) { return null; }
 
-        for (int i = 0; i < filters.length; ++i) {
-            filterArgumentChecks(target, i + pos, filters[i]);
-        }
-
-        return new Transformers.FilterArguments(target, pos, filters);
-    }
-
-    private static void filterArgumentsCheckArity(MethodHandle target, int pos, MethodHandle[] filters) {
-        MethodType targetType = target.type();
-        int maxPos = targetType.parameterCount();
-        if (pos + filters.length > maxPos)
-            throw newIllegalArgumentException("too many filters");
-    }
-
-    private static void filterArgumentChecks(MethodHandle target, int pos, MethodHandle filter) throws RuntimeException {
-        MethodType targetType = target.type();
-        MethodType filterType = filter.type();
-        if (filterType.parameterCount() != 1
-            || filterType.returnType() != targetType.parameterType(pos))
-            throw newIllegalArgumentException("target and filter types do not match", targetType, filterType);
-    }
-
-    /**
-     * Adapts a target method handle by pre-processing
-     * a sub-sequence of its arguments with a filter (another method handle).
-     * The pre-processed arguments are replaced by the result (if any) of the
-     * filter function.
-     * The target is then called on the modified (usually shortened) argument list.
-     * <p>
-     * If the filter returns a value, the target must accept that value as
-     * its argument in position {@code pos}, preceded and/or followed by
-     * any arguments not passed to the filter.
-     * If the filter returns void, the target must accept all arguments
-     * not passed to the filter.
-     * No arguments are reordered, and a result returned from the filter
-     * replaces (in order) the whole subsequence of arguments originally
-     * passed to the adapter.
-     * <p>
-     * The argument types (if any) of the filter
-     * replace zero or one argument types of the target, at position {@code pos},
-     * in the resulting adapted method handle.
-     * The return type of the filter (if any) must be identical to the
-     * argument type of the target at position {@code pos}, and that target argument
-     * is supplied by the return value of the filter.
-     * <p>
-     * In all cases, {@code pos} must be greater than or equal to zero, and
-     * {@code pos} must also be less than or equal to the target's arity.
-     * <p><b>Example:</b>
-     * <blockquote><pre>{@code
-import static java.lang.invoke.MethodHandles.*;
-import static java.lang.invoke.MethodType.*;
-...
-MethodHandle deepToString = publicLookup()
-  .findStatic(Arrays.class, "deepToString", methodType(String.class, Object[].class));
-
-MethodHandle ts1 = deepToString.asCollector(String[].class, 1);
-assertEquals("[strange]", (String) ts1.invokeExact("strange"));
-
-MethodHandle ts2 = deepToString.asCollector(String[].class, 2);
-assertEquals("[up, down]", (String) ts2.invokeExact("up", "down"));
-
-MethodHandle ts3 = deepToString.asCollector(String[].class, 3);
-MethodHandle ts3_ts2 = collectArguments(ts3, 1, ts2);
-assertEquals("[top, [up, down], strange]",
-             (String) ts3_ts2.invokeExact("top", "up", "down", "strange"));
-
-MethodHandle ts3_ts2_ts1 = collectArguments(ts3_ts2, 3, ts1);
-assertEquals("[top, [up, down], [strange]]",
-             (String) ts3_ts2_ts1.invokeExact("top", "up", "down", "strange"));
-
-MethodHandle ts3_ts2_ts3 = collectArguments(ts3_ts2, 1, ts3);
-assertEquals("[top, [[up, down, strange], charm], bottom]",
-             (String) ts3_ts2_ts3.invokeExact("top", "up", "down", "strange", "charm", "bottom"));
-     * }</pre></blockquote>
-     * <p> Here is pseudocode for the resulting adapter:
-     * <blockquote><pre>{@code
-     * T target(A...,V,C...);
-     * V filter(B...);
-     * T adapter(A... a,B... b,C... c) {
-     *   V v = filter(b...);
-     *   return target(a...,v,c...);
-     * }
-     * // and if the filter has no arguments:
-     * T target2(A...,V,C...);
-     * V filter2();
-     * T adapter2(A... a,C... c) {
-     *   V v = filter2();
-     *   return target2(a...,v,c...);
-     * }
-     * // and if the filter has a void return:
-     * T target3(A...,C...);
-     * void filter3(B...);
-     * void adapter3(A... a,B... b,C... c) {
-     *   filter3(b...);
-     *   return target3(a...,c...);
-     * }
-     * }</pre></blockquote>
-     * <p>
-     * A collection adapter {@code collectArguments(mh, 0, coll)} is equivalent to
-     * one which first "folds" the affected arguments, and then drops them, in separate
-     * steps as follows:
-     * <blockquote><pre>{@code
-     * mh = MethodHandles.dropArguments(mh, 1, coll.type().parameterList()); //step 2
-     * mh = MethodHandles.foldArguments(mh, coll); //step 1
-     * }</pre></blockquote>
-     * If the target method handle consumes no arguments besides than the result
-     * (if any) of the filter {@code coll}, then {@code collectArguments(mh, 0, coll)}
-     * is equivalent to {@code filterReturnValue(coll, mh)}.
-     * If the filter method handle {@code coll} consumes one argument and produces
-     * a non-void result, then {@code collectArguments(mh, N, coll)}
-     * is equivalent to {@code filterArguments(mh, N, coll)}.
-     * Other equivalences are possible but would require argument permutation.
-     *
-     * @param target the method handle to invoke after filtering the subsequence of arguments
-     * @param pos the position of the first adapter argument to pass to the filter,
-     *            and/or the target argument which receives the result of the filter
-     * @param filter method handle to call on the subsequence of arguments
-     * @return method handle which incorporates the specified argument subsequence filtering logic
-     * @throws NullPointerException if either argument is null
-     * @throws IllegalArgumentException if the return type of {@code filter}
-     *          is non-void and is not the same as the {@code pos} argument of the target,
-     *          or if {@code pos} is not between 0 and the target's arity, inclusive,
-     *          or if the resulting method handle's type would have
-     *          <a href="MethodHandle.html#maxarity">too many parameters</a>
-     * @see MethodHandles#foldArguments
-     * @see MethodHandles#filterArguments
-     * @see MethodHandles#filterReturnValue
-     */
     public static
-    MethodHandle collectArguments(MethodHandle target, int pos, MethodHandle filter) {
-        MethodType newType = collectArgumentsChecks(target, pos, filter);
-        return new Transformers.CollectArguments(target, filter, pos, newType);
-    }
+    MethodHandle collectArguments(MethodHandle target, int pos, MethodHandle filter) { return null; }
 
-    private static MethodType collectArgumentsChecks(MethodHandle target, int pos, MethodHandle filter) throws RuntimeException {
-        MethodType targetType = target.type();
-        MethodType filterType = filter.type();
-        Class<?> rtype = filterType.returnType();
-        List<Class<?>> filterArgs = filterType.parameterList();
-        if (rtype == void.class) {
-            return targetType.insertParameterTypes(pos, filterArgs);
-        }
-        if (rtype != targetType.parameterType(pos)) {
-            throw newIllegalArgumentException("target and filter types do not match", targetType, filterType);
-        }
-        return targetType.dropParameterTypes(pos, pos+1).insertParameterTypes(pos, filterArgs);
-    }
-
-    /**
-     * Adapts a target method handle by post-processing
-     * its return value (if any) with a filter (another method handle).
-     * The result of the filter is returned from the adapter.
-     * <p>
-     * If the target returns a value, the filter must accept that value as
-     * its only argument.
-     * If the target returns void, the filter must accept no arguments.
-     * <p>
-     * The return type of the filter
-     * replaces the return type of the target
-     * in the resulting adapted method handle.
-     * The argument type of the filter (if any) must be identical to the
-     * return type of the target.
-     * <p><b>Example:</b>
-     * <blockquote><pre>{@code
-import static java.lang.invoke.MethodHandles.*;
-import static java.lang.invoke.MethodType.*;
-...
-MethodHandle cat = lookup().findVirtual(String.class,
-  "concat", methodType(String.class, String.class));
-MethodHandle length = lookup().findVirtual(String.class,
-  "length", methodType(int.class));
-System.out.println((String) cat.invokeExact("x", "y")); // xy
-MethodHandle f0 = filterReturnValue(cat, length);
-System.out.println((int) f0.invokeExact("x", "y")); // 2
-     * }</pre></blockquote>
-     * <p> Here is pseudocode for the resulting adapter:
-     * <blockquote><pre>{@code
-     * V target(A...);
-     * T filter(V);
-     * T adapter(A... a) {
-     *   V v = target(a...);
-     *   return filter(v);
-     * }
-     * // and if the target has a void return:
-     * void target2(A...);
-     * T filter2();
-     * T adapter2(A... a) {
-     *   target2(a...);
-     *   return filter2();
-     * }
-     * // and if the filter has a void return:
-     * V target3(A...);
-     * void filter3(V);
-     * void adapter3(A... a) {
-     *   V v = target3(a...);
-     *   filter3(v);
-     * }
-     * }</pre></blockquote>
-     * @param target the method handle to invoke before filtering the return value
-     * @param filter method handle to call on the return value
-     * @return method handle which incorporates the specified return value filtering logic
-     * @throws NullPointerException if either argument is null
-     * @throws IllegalArgumentException if the argument list of {@code filter}
-     *          does not match the return type of target as described above
-     */
     public static
-    MethodHandle filterReturnValue(MethodHandle target, MethodHandle filter) {
-        MethodType targetType = target.type();
-        MethodType filterType = filter.type();
-        filterReturnValueChecks(targetType, filterType);
+    MethodHandle filterReturnValue(MethodHandle target, MethodHandle filter) { return null; }
 
-        return new Transformers.FilterReturnValue(target, filter);
-    }
-
-    private static void filterReturnValueChecks(MethodType targetType, MethodType filterType) throws RuntimeException {
-        Class<?> rtype = targetType.returnType();
-        int filterValues = filterType.parameterCount();
-        if (filterValues == 0
-                ? (rtype != void.class)
-                : (rtype != filterType.parameterType(0) || filterValues != 1))
-            throw newIllegalArgumentException("target and filter types do not match", targetType, filterType);
-    }
-
-    /**
-     * Adapts a target method handle by pre-processing
-     * some of its arguments, and then calling the target with
-     * the result of the pre-processing, inserted into the original
-     * sequence of arguments.
-     * <p>
-     * The pre-processing is performed by {@code combiner}, a second method handle.
-     * Of the arguments passed to the adapter, the first {@code N} arguments
-     * are copied to the combiner, which is then called.
-     * (Here, {@code N} is defined as the parameter count of the combiner.)
-     * After this, control passes to the target, with any result
-     * from the combiner inserted before the original {@code N} incoming
-     * arguments.
-     * <p>
-     * If the combiner returns a value, the first parameter type of the target
-     * must be identical with the return type of the combiner, and the next
-     * {@code N} parameter types of the target must exactly match the parameters
-     * of the combiner.
-     * <p>
-     * If the combiner has a void return, no result will be inserted,
-     * and the first {@code N} parameter types of the target
-     * must exactly match the parameters of the combiner.
-     * <p>
-     * The resulting adapter is the same type as the target, except that the
-     * first parameter type is dropped,
-     * if it corresponds to the result of the combiner.
-     * <p>
-     * (Note that {@link #dropArguments(MethodHandle,int,List) dropArguments} can be used to remove any arguments
-     * that either the combiner or the target does not wish to receive.
-     * If some of the incoming arguments are destined only for the combiner,
-     * consider using {@link MethodHandle#asCollector asCollector} instead, since those
-     * arguments will not need to be live on the stack on entry to the
-     * target.)
-     * <p><b>Example:</b>
-     * <blockquote><pre>{@code
-import static java.lang.invoke.MethodHandles.*;
-import static java.lang.invoke.MethodType.*;
-...
-MethodHandle trace = publicLookup().findVirtual(java.io.PrintStream.class,
-  "println", methodType(void.class, String.class))
-    .bindTo(System.out);
-MethodHandle cat = lookup().findVirtual(String.class,
-  "concat", methodType(String.class, String.class));
-assertEquals("boojum", (String) cat.invokeExact("boo", "jum"));
-MethodHandle catTrace = foldArguments(cat, trace);
-// also prints "boo":
-assertEquals("boojum", (String) catTrace.invokeExact("boo", "jum"));
-     * }</pre></blockquote>
-     * <p> Here is pseudocode for the resulting adapter:
-     * <blockquote><pre>{@code
-     * // there are N arguments in A...
-     * T target(V, A[N]..., B...);
-     * V combiner(A...);
-     * T adapter(A... a, B... b) {
-     *   V v = combiner(a...);
-     *   return target(v, a..., b...);
-     * }
-     * // and if the combiner has a void return:
-     * T target2(A[N]..., B...);
-     * void combiner2(A...);
-     * T adapter2(A... a, B... b) {
-     *   combiner2(a...);
-     *   return target2(a..., b...);
-     * }
-     * }</pre></blockquote>
-     * @param target the method handle to invoke after arguments are combined
-     * @param combiner method handle to call initially on the incoming arguments
-     * @return method handle which incorporates the specified argument folding logic
-     * @throws NullPointerException if either argument is null
-     * @throws IllegalArgumentException if {@code combiner}'s return type
-     *          is non-void and not the same as the first argument type of
-     *          the target, or if the initial {@code N} argument types
-     *          of the target
-     *          (skipping one matching the {@code combiner}'s return type)
-     *          are not identical with the argument types of {@code combiner}
-     */
     public static
-    MethodHandle foldArguments(MethodHandle target, MethodHandle combiner) {
-        int foldPos = 0;
-        MethodType targetType = target.type();
-        MethodType combinerType = combiner.type();
-        Class<?> rtype = foldArgumentChecks(foldPos, targetType, combinerType);
+    MethodHandle foldArguments(MethodHandle target, MethodHandle combiner) { return null; }
 
-        return new Transformers.FoldArguments(target, combiner);
-    }
-
-    private static Class<?> foldArgumentChecks(int foldPos, MethodType targetType, MethodType combinerType) {
-        int foldArgs   = combinerType.parameterCount();
-        Class<?> rtype = combinerType.returnType();
-        int foldVals = rtype == void.class ? 0 : 1;
-        int afterInsertPos = foldPos + foldVals;
-        boolean ok = (targetType.parameterCount() >= afterInsertPos + foldArgs);
-        if (ok && !(combinerType.parameterList()
-                    .equals(targetType.parameterList().subList(afterInsertPos,
-                                                               afterInsertPos + foldArgs))))
-            ok = false;
-        if (ok && foldVals != 0 && combinerType.returnType() != targetType.parameterType(0))
-            ok = false;
-        if (!ok)
-            throw misMatchedTypes("target and combiner types", targetType, combinerType);
-        return rtype;
-    }
-
-    /**
-     * Makes a method handle which adapts a target method handle,
-     * by guarding it with a test, a boolean-valued method handle.
-     * If the guard fails, a fallback handle is called instead.
-     * All three method handles must have the same corresponding
-     * argument and return types, except that the return type
-     * of the test must be boolean, and the test is allowed
-     * to have fewer arguments than the other two method handles.
-     * <p> Here is pseudocode for the resulting adapter:
-     * <blockquote><pre>{@code
-     * boolean test(A...);
-     * T target(A...,B...);
-     * T fallback(A...,B...);
-     * T adapter(A... a,B... b) {
-     *   if (test(a...))
-     *     return target(a..., b...);
-     *   else
-     *     return fallback(a..., b...);
-     * }
-     * }</pre></blockquote>
-     * Note that the test arguments ({@code a...} in the pseudocode) cannot
-     * be modified by execution of the test, and so are passed unchanged
-     * from the caller to the target or fallback as appropriate.
-     * @param test method handle used for test, must return boolean
-     * @param target method handle to call if test passes
-     * @param fallback method handle to call if test fails
-     * @return method handle which incorporates the specified if/then/else logic
-     * @throws NullPointerException if any argument is null
-     * @throws IllegalArgumentException if {@code test} does not return boolean,
-     *          or if all three method types do not match (with the return
-     *          type of {@code test} changed to match that of the target).
-     */
     public static
     MethodHandle guardWithTest(MethodHandle test,
                                MethodHandle target,
-                               MethodHandle fallback) {
-        MethodType gtype = test.type();
-        MethodType ttype = target.type();
-        MethodType ftype = fallback.type();
-        if (!ttype.equals(ftype))
-            throw misMatchedTypes("target and fallback types", ttype, ftype);
-        if (gtype.returnType() != boolean.class)
-            throw newIllegalArgumentException("guard type is not a predicate "+gtype);
-        List<Class<?>> targs = ttype.parameterList();
-        List<Class<?>> gargs = gtype.parameterList();
-        if (!targs.equals(gargs)) {
-            int gpc = gargs.size(), tpc = targs.size();
-            if (gpc >= tpc || !targs.subList(0, gpc).equals(gargs))
-                throw misMatchedTypes("target and test types", ttype, gtype);
-            test = dropArguments(test, gpc, targs.subList(gpc, tpc));
-            gtype = test.type();
-        }
+                               MethodHandle fallback) { return null; }
 
-        return new Transformers.GuardWithTest(test, target, fallback);
-    }
-
-    static RuntimeException misMatchedTypes(String what, MethodType t1, MethodType t2) {
-        return newIllegalArgumentException(what + " must match: " + t1 + " != " + t2);
-    }
-
-    /**
-     * Makes a method handle which adapts a target method handle,
-     * by running it inside an exception handler.
-     * If the target returns normally, the adapter returns that value.
-     * If an exception matching the specified type is thrown, the fallback
-     * handle is called instead on the exception, plus the original arguments.
-     * <p>
-     * The target and handler must have the same corresponding
-     * argument and return types, except that handler may omit trailing arguments
-     * (similarly to the predicate in {@link #guardWithTest guardWithTest}).
-     * Also, the handler must have an extra leading parameter of {@code exType} or a supertype.
-     * <p> Here is pseudocode for the resulting adapter:
-     * <blockquote><pre>{@code
-     * T target(A..., B...);
-     * T handler(ExType, A...);
-     * T adapter(A... a, B... b) {
-     *   try {
-     *     return target(a..., b...);
-     *   } catch (ExType ex) {
-     *     return handler(ex, a...);
-     *   }
-     * }
-     * }</pre></blockquote>
-     * Note that the saved arguments ({@code a...} in the pseudocode) cannot
-     * be modified by execution of the target, and so are passed unchanged
-     * from the caller to the handler, if the handler is invoked.
-     * <p>
-     * The target and handler must return the same type, even if the handler
-     * always throws.  (This might happen, for instance, because the handler
-     * is simulating a {@code finally} clause).
-     * To create such a throwing handler, compose the handler creation logic
-     * with {@link #throwException throwException},
-     * in order to create a method handle of the correct return type.
-     * @param target method handle to call
-     * @param exType the type of exception which the handler will catch
-     * @param handler method handle to call if a matching exception is thrown
-     * @return method handle which incorporates the specified try/catch logic
-     * @throws NullPointerException if any argument is null
-     * @throws IllegalArgumentException if {@code handler} does not accept
-     *          the given exception type, or if the method handle types do
-     *          not match in their return types and their
-     *          corresponding parameters
-     */
     public static
     MethodHandle catchException(MethodHandle target,
                                 Class<? extends Throwable> exType,
-                                MethodHandle handler) {
-        MethodType ttype = target.type();
-        MethodType htype = handler.type();
-        if (htype.parameterCount() < 1 ||
-            !htype.parameterType(0).isAssignableFrom(exType))
-            throw newIllegalArgumentException("handler does not accept exception type "+exType);
-        if (htype.returnType() != ttype.returnType())
-            throw misMatchedTypes("target and handler return types", ttype, htype);
-        List<Class<?>> targs = ttype.parameterList();
-        List<Class<?>> hargs = htype.parameterList();
-        hargs = hargs.subList(1, hargs.size());  // omit leading parameter from handler
-        if (!targs.equals(hargs)) {
-            int hpc = hargs.size(), tpc = targs.size();
-            if (hpc >= tpc || !targs.subList(0, hpc).equals(hargs))
-                throw misMatchedTypes("target and handler types", ttype, htype);
-        }
+                                MethodHandle handler) { return null; }
 
-        return new Transformers.CatchException(target, handler, exType);
-    }
-
-    /**
-     * Produces a method handle which will throw exceptions of the given {@code exType}.
-     * The method handle will accept a single argument of {@code exType},
-     * and immediately throw it as an exception.
-     * The method type will nominally specify a return of {@code returnType}.
-     * The return type may be anything convenient:  It doesn't matter to the
-     * method handle's behavior, since it will never return normally.
-     * @param returnType the return type of the desired method handle
-     * @param exType the parameter type of the desired method handle
-     * @return method handle which can throw the given exceptions
-     * @throws NullPointerException if either argument is null
-     */
     public static
-    MethodHandle throwException(Class<?> returnType, Class<? extends Throwable> exType) {
-        if (!Throwable.class.isAssignableFrom(exType))
-            throw new ClassCastException(exType.getName());
-
-        return new Transformers.AlwaysThrow(returnType, exType);
-    }
+    MethodHandle throwException(Class<?> returnType, Class<? extends Throwable> exType) { return null; }
 }
diff --git a/java/lang/invoke/MethodType.java b/java/lang/invoke/MethodType.java
index bfa7ccd..4cb5c22 100644
--- a/java/lang/invoke/MethodType.java
+++ b/java/lang/invoke/MethodType.java
@@ -25,1227 +25,78 @@
 
 package java.lang.invoke;
 
-import sun.invoke.util.Wrapper;
-import java.lang.ref.WeakReference;
-import java.lang.ref.Reference;
-import java.lang.ref.ReferenceQueue;
-import java.util.Arrays;
-import java.util.Collections;
 import java.util.List;
-import java.util.Objects;
-import java.util.concurrent.ConcurrentMap;
-import java.util.concurrent.ConcurrentHashMap;
-import sun.invoke.util.BytecodeDescriptor;
-import static java.lang.invoke.MethodHandleStatics.*;
 
-/**
- * A method type represents the arguments and return type accepted and
- * returned by a method handle, or the arguments and return type passed
- * and expected  by a method handle caller.  Method types must be properly
- * matched between a method handle and all its callers,
- * and the JVM's operations enforce this matching at, specifically
- * during calls to {@link MethodHandle#invokeExact MethodHandle.invokeExact}
- * and {@link MethodHandle#invoke MethodHandle.invoke}, and during execution
- * of {@code invokedynamic} instructions.
- * <p>
- * The structure is a return type accompanied by any number of parameter types.
- * The types (primitive, {@code void}, and reference) are represented by {@link Class} objects.
- * (For ease of exposition, we treat {@code void} as if it were a type.
- * In fact, it denotes the absence of a return type.)
- * <p>
- * All instances of {@code MethodType} are immutable.
- * Two instances are completely interchangeable if they compare equal.
- * Equality depends on pairwise correspondence of the return and parameter types and on nothing else.
- * <p>
- * This type can be created only by factory methods.
- * All factory methods may cache values, though caching is not guaranteed.
- * Some factory methods are static, while others are virtual methods which
- * modify precursor method types, e.g., by changing a selected parameter.
- * <p>
- * Factory methods which operate on groups of parameter types
- * are systematically presented in two versions, so that both Java arrays and
- * Java lists can be used to work with groups of parameter types.
- * The query methods {@code parameterArray} and {@code parameterList}
- * also provide a choice between arrays and lists.
- * <p>
- * {@code MethodType} objects are sometimes derived from bytecode instructions
- * such as {@code invokedynamic}, specifically from the type descriptor strings associated
- * with the instructions in a class file's constant pool.
- * <p>
- * Like classes and strings, method types can also be represented directly
- * in a class file's constant pool as constants.
- * A method type may be loaded by an {@code ldc} instruction which refers
- * to a suitable {@code CONSTANT_MethodType} constant pool entry.
- * The entry refers to a {@code CONSTANT_Utf8} spelling for the descriptor string.
- * (For full details on method type constants,
- * see sections 4.4.8 and 5.4.3.5 of the Java Virtual Machine Specification.)
- * <p>
- * When the JVM materializes a {@code MethodType} from a descriptor string,
- * all classes named in the descriptor must be accessible, and will be loaded.
- * (But the classes need not be initialized, as is the case with a {@code CONSTANT_Class}.)
- * This loading may occur at any time before the {@code MethodType} object is first derived.
- * @author John Rose, JSR 292 EG
- */
 public final
 class MethodType implements java.io.Serializable {
-    private static final long serialVersionUID = 292L;  // {rtype, {ptype...}}
 
-    // The rtype and ptypes fields define the structural identity of the method type:
-    private final Class<?>   rtype;
-    private final Class<?>[] ptypes;
-
-    // The remaining fields are caches of various sorts:
-    private @Stable MethodTypeForm form; // erased form, plus cached data about primitives
-    private @Stable MethodType wrapAlt;  // alternative wrapped/unwrapped version
-    // Android-changed: Remove adapter cache. We're not dynamically generating any
-    // adapters at this point.
-    // private @Stable Invokers invokers;   // cache of handy higher-order adapters
-    private @Stable String methodDescriptor;  // cache for toMethodDescriptorString
-
-    /**
-     * Check the given parameters for validity and store them into the final fields.
-     */
-    private MethodType(Class<?> rtype, Class<?>[] ptypes, boolean trusted) {
-        checkRtype(rtype);
-        checkPtypes(ptypes);
-        this.rtype = rtype;
-        // defensively copy the array passed in by the user
-        this.ptypes = trusted ? ptypes : Arrays.copyOf(ptypes, ptypes.length);
-    }
-
-    /**
-     * Construct a temporary unchecked instance of MethodType for use only as a key to the intern table.
-     * Does not check the given parameters for validity, and must be discarded after it is used as a searching key.
-     * The parameters are reversed for this constructor, so that is is not accidentally used.
-     */
-    private MethodType(Class<?>[] ptypes, Class<?> rtype) {
-        this.rtype = rtype;
-        this.ptypes = ptypes;
-    }
-
-    /*trusted*/ MethodTypeForm form() { return form; }
-    /*trusted*/ /** @hide */ public Class<?> rtype() { return rtype; }
-    /*trusted*/ /** @hide */ public Class<?>[] ptypes() { return ptypes; }
-
-    // Android-changed: Removed method setForm. It's unused in the JDK and there's no
-    // good reason to allow the form to be set externally.
-    //
-    // void setForm(MethodTypeForm f) { form = f; }
-
-    /** This number, mandated by the JVM spec as 255,
-     *  is the maximum number of <em>slots</em>
-     *  that any Java method can receive in its argument list.
-     *  It limits both JVM signatures and method type objects.
-     *  The longest possible invocation will look like
-     *  {@code staticMethod(arg1, arg2, ..., arg255)} or
-     *  {@code x.virtualMethod(arg1, arg2, ..., arg254)}.
-     */
-    /*non-public*/ static final int MAX_JVM_ARITY = 255;  // this is mandated by the JVM spec.
-
-    /** This number is the maximum arity of a method handle, 254.
-     *  It is derived from the absolute JVM-imposed arity by subtracting one,
-     *  which is the slot occupied by the method handle itself at the
-     *  beginning of the argument list used to invoke the method handle.
-     *  The longest possible invocation will look like
-     *  {@code mh.invoke(arg1, arg2, ..., arg254)}.
-     */
-    // Issue:  Should we allow MH.invokeWithArguments to go to the full 255?
-    /*non-public*/ static final int MAX_MH_ARITY = MAX_JVM_ARITY-1;  // deduct one for mh receiver
-
-    /** This number is the maximum arity of a method handle invoker, 253.
-     *  It is derived from the absolute JVM-imposed arity by subtracting two,
-     *  which are the slots occupied by invoke method handle, and the
-     *  target method handle, which are both at the beginning of the argument
-     *  list used to invoke the target method handle.
-     *  The longest possible invocation will look like
-     *  {@code invokermh.invoke(targetmh, arg1, arg2, ..., arg253)}.
-     */
-    /*non-public*/ static final int MAX_MH_INVOKER_ARITY = MAX_MH_ARITY-1;  // deduct one more for invoker
-
-    private static void checkRtype(Class<?> rtype) {
-        Objects.requireNonNull(rtype);
-    }
-    private static void checkPtype(Class<?> ptype) {
-        Objects.requireNonNull(ptype);
-        if (ptype == void.class)
-            throw newIllegalArgumentException("parameter type cannot be void");
-    }
-    /** Return number of extra slots (count of long/double args). */
-    private static int checkPtypes(Class<?>[] ptypes) {
-        int slots = 0;
-        for (Class<?> ptype : ptypes) {
-            checkPtype(ptype);
-            if (ptype == double.class || ptype == long.class) {
-                slots++;
-            }
-        }
-        checkSlotCount(ptypes.length + slots);
-        return slots;
-    }
-    static void checkSlotCount(int count) {
-        assert((MAX_JVM_ARITY & (MAX_JVM_ARITY+1)) == 0);
-        // MAX_JVM_ARITY must be power of 2 minus 1 for following code trick to work:
-        if ((count & MAX_JVM_ARITY) != count)
-            throw newIllegalArgumentException("bad parameter count "+count);
-    }
-    private static IndexOutOfBoundsException newIndexOutOfBoundsException(Object num) {
-        if (num instanceof Integer)  num = "bad index: "+num;
-        return new IndexOutOfBoundsException(num.toString());
-    }
-
-    static final ConcurrentWeakInternSet<MethodType> internTable = new ConcurrentWeakInternSet<>();
-
-    static final Class<?>[] NO_PTYPES = {};
-
-    /**
-     * Finds or creates an instance of the given method type.
-     * @param rtype  the return type
-     * @param ptypes the parameter types
-     * @return a method type with the given components
-     * @throws NullPointerException if {@code rtype} or {@code ptypes} or any element of {@code ptypes} is null
-     * @throws IllegalArgumentException if any element of {@code ptypes} is {@code void.class}
-     */
     public static
     MethodType methodType(Class<?> rtype, Class<?>[] ptypes) {
-        return makeImpl(rtype, ptypes, false);
+        return null;
     }
 
-    /**
-     * Finds or creates a method type with the given components.
-     * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}.
-     * @param rtype  the return type
-     * @param ptypes the parameter types
-     * @return a method type with the given components
-     * @throws NullPointerException if {@code rtype} or {@code ptypes} or any element of {@code ptypes} is null
-     * @throws IllegalArgumentException if any element of {@code ptypes} is {@code void.class}
-     */
     public static
     MethodType methodType(Class<?> rtype, List<Class<?>> ptypes) {
-        boolean notrust = false;  // random List impl. could return evil ptypes array
-        return makeImpl(rtype, listToArray(ptypes), notrust);
+        return null;
     }
 
-    private static Class<?>[] listToArray(List<Class<?>> ptypes) {
-        // sanity check the size before the toArray call, since size might be huge
-        checkSlotCount(ptypes.size());
-        return ptypes.toArray(NO_PTYPES);
-    }
-
-    /**
-     * Finds or creates a method type with the given components.
-     * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}.
-     * The leading parameter type is prepended to the remaining array.
-     * @param rtype  the return type
-     * @param ptype0 the first parameter type
-     * @param ptypes the remaining parameter types
-     * @return a method type with the given components
-     * @throws NullPointerException if {@code rtype} or {@code ptype0} or {@code ptypes} or any element of {@code ptypes} is null
-     * @throws IllegalArgumentException if {@code ptype0} or {@code ptypes} or any element of {@code ptypes} is {@code void.class}
-     */
     public static
-    MethodType methodType(Class<?> rtype, Class<?> ptype0, Class<?>... ptypes) {
-        Class<?>[] ptypes1 = new Class<?>[1+ptypes.length];
-        ptypes1[0] = ptype0;
-        System.arraycopy(ptypes, 0, ptypes1, 1, ptypes.length);
-        return makeImpl(rtype, ptypes1, true);
-    }
+    MethodType methodType(Class<?> rtype, Class<?> ptype0, Class<?>... ptypes) { return null; }
 
-    /**
-     * Finds or creates a method type with the given components.
-     * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}.
-     * The resulting method has no parameter types.
-     * @param rtype  the return type
-     * @return a method type with the given return value
-     * @throws NullPointerException if {@code rtype} is null
-     */
     public static
-    MethodType methodType(Class<?> rtype) {
-        return makeImpl(rtype, NO_PTYPES, true);
-    }
+    MethodType methodType(Class<?> rtype) { return null; }
 
-    /**
-     * Finds or creates a method type with the given components.
-     * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}.
-     * The resulting method has the single given parameter type.
-     * @param rtype  the return type
-     * @param ptype0 the parameter type
-     * @return a method type with the given return value and parameter type
-     * @throws NullPointerException if {@code rtype} or {@code ptype0} is null
-     * @throws IllegalArgumentException if {@code ptype0} is {@code void.class}
-     */
     public static
-    MethodType methodType(Class<?> rtype, Class<?> ptype0) {
-        return makeImpl(rtype, new Class<?>[]{ ptype0 }, true);
-    }
+    MethodType methodType(Class<?> rtype, Class<?> ptype0) { return null; }
 
-    /**
-     * Finds or creates a method type with the given components.
-     * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}.
-     * The resulting method has the same parameter types as {@code ptypes},
-     * and the specified return type.
-     * @param rtype  the return type
-     * @param ptypes the method type which supplies the parameter types
-     * @return a method type with the given components
-     * @throws NullPointerException if {@code rtype} or {@code ptypes} is null
-     */
     public static
-    MethodType methodType(Class<?> rtype, MethodType ptypes) {
-        return makeImpl(rtype, ptypes.ptypes, true);
-    }
+    MethodType methodType(Class<?> rtype, MethodType ptypes) { return null; }
 
-    /**
-     * Sole factory method to find or create an interned method type.
-     * @param rtype desired return type
-     * @param ptypes desired parameter types
-     * @param trusted whether the ptypes can be used without cloning
-     * @return the unique method type of the desired structure
-     */
-    /*trusted*/ static
-    MethodType makeImpl(Class<?> rtype, Class<?>[] ptypes, boolean trusted) {
-        MethodType mt = internTable.get(new MethodType(ptypes, rtype));
-        if (mt != null)
-            return mt;
-        if (ptypes.length == 0) {
-            ptypes = NO_PTYPES; trusted = true;
-        }
-        mt = new MethodType(rtype, ptypes, trusted);
-        // promote the object to the Real Thing, and reprobe
-        mt.form = MethodTypeForm.findForm(mt);
-        return internTable.add(mt);
-    }
-    private static final MethodType[] objectOnlyTypes = new MethodType[20];
-
-    /**
-     * Finds or creates a method type whose components are {@code Object} with an optional trailing {@code Object[]} array.
-     * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}.
-     * All parameters and the return type will be {@code Object},
-     * except the final array parameter if any, which will be {@code Object[]}.
-     * @param objectArgCount number of parameters (excluding the final array parameter if any)
-     * @param finalArray whether there will be a trailing array parameter, of type {@code Object[]}
-     * @return a generally applicable method type, for all calls of the given fixed argument count and a collected array of further arguments
-     * @throws IllegalArgumentException if {@code objectArgCount} is negative or greater than 255 (or 254, if {@code finalArray} is true)
-     * @see #genericMethodType(int)
-     */
     public static
-    MethodType genericMethodType(int objectArgCount, boolean finalArray) {
-        MethodType mt;
-        checkSlotCount(objectArgCount);
-        int ivarargs = (!finalArray ? 0 : 1);
-        int ootIndex = objectArgCount*2 + ivarargs;
-        if (ootIndex < objectOnlyTypes.length) {
-            mt = objectOnlyTypes[ootIndex];
-            if (mt != null)  return mt;
-        }
-        Class<?>[] ptypes = new Class<?>[objectArgCount + ivarargs];
-        Arrays.fill(ptypes, Object.class);
-        if (ivarargs != 0)  ptypes[objectArgCount] = Object[].class;
-        mt = makeImpl(Object.class, ptypes, true);
-        if (ootIndex < objectOnlyTypes.length) {
-            objectOnlyTypes[ootIndex] = mt;     // cache it here also!
-        }
-        return mt;
-    }
+    MethodType genericMethodType(int objectArgCount, boolean finalArray) { return null; }
 
-    /**
-     * Finds or creates a method type whose components are all {@code Object}.
-     * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}.
-     * All parameters and the return type will be Object.
-     * @param objectArgCount number of parameters
-     * @return a generally applicable method type, for all calls of the given argument count
-     * @throws IllegalArgumentException if {@code objectArgCount} is negative or greater than 255
-     * @see #genericMethodType(int, boolean)
-     */
     public static
-    MethodType genericMethodType(int objectArgCount) {
-        return genericMethodType(objectArgCount, false);
-    }
+    MethodType genericMethodType(int objectArgCount) { return null; }
 
-    /**
-     * Finds or creates a method type with a single different parameter type.
-     * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}.
-     * @param num    the index (zero-based) of the parameter type to change
-     * @param nptype a new parameter type to replace the old one with
-     * @return the same type, except with the selected parameter changed
-     * @throws IndexOutOfBoundsException if {@code num} is not a valid index into {@code parameterArray()}
-     * @throws IllegalArgumentException if {@code nptype} is {@code void.class}
-     * @throws NullPointerException if {@code nptype} is null
-     */
-    public MethodType changeParameterType(int num, Class<?> nptype) {
-        if (parameterType(num) == nptype)  return this;
-        checkPtype(nptype);
-        Class<?>[] nptypes = ptypes.clone();
-        nptypes[num] = nptype;
-        return makeImpl(rtype, nptypes, true);
-    }
+    public MethodType changeParameterType(int num, Class<?> nptype) { return null; }
 
-    /**
-     * Finds or creates a method type with additional parameter types.
-     * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}.
-     * @param num    the position (zero-based) of the inserted parameter type(s)
-     * @param ptypesToInsert zero or more new parameter types to insert into the parameter list
-     * @return the same type, except with the selected parameter(s) inserted
-     * @throws IndexOutOfBoundsException if {@code num} is negative or greater than {@code parameterCount()}
-     * @throws IllegalArgumentException if any element of {@code ptypesToInsert} is {@code void.class}
-     *                                  or if the resulting method type would have more than 255 parameter slots
-     * @throws NullPointerException if {@code ptypesToInsert} or any of its elements is null
-     */
-    public MethodType insertParameterTypes(int num, Class<?>... ptypesToInsert) {
-        int len = ptypes.length;
-        if (num < 0 || num > len)
-            throw newIndexOutOfBoundsException(num);
-        int ins = checkPtypes(ptypesToInsert);
-        checkSlotCount(parameterSlotCount() + ptypesToInsert.length + ins);
-        int ilen = ptypesToInsert.length;
-        if (ilen == 0)  return this;
-        Class<?>[] nptypes = Arrays.copyOfRange(ptypes, 0, len+ilen);
-        System.arraycopy(nptypes, num, nptypes, num+ilen, len-num);
-        System.arraycopy(ptypesToInsert, 0, nptypes, num, ilen);
-        return makeImpl(rtype, nptypes, true);
-    }
+    public MethodType insertParameterTypes(int num, Class<?>... ptypesToInsert) { return null; }
 
-    /**
-     * Finds or creates a method type with additional parameter types.
-     * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}.
-     * @param ptypesToInsert zero or more new parameter types to insert after the end of the parameter list
-     * @return the same type, except with the selected parameter(s) appended
-     * @throws IllegalArgumentException if any element of {@code ptypesToInsert} is {@code void.class}
-     *                                  or if the resulting method type would have more than 255 parameter slots
-     * @throws NullPointerException if {@code ptypesToInsert} or any of its elements is null
-     */
-    public MethodType appendParameterTypes(Class<?>... ptypesToInsert) {
-        return insertParameterTypes(parameterCount(), ptypesToInsert);
-    }
+    public MethodType appendParameterTypes(Class<?>... ptypesToInsert) { return null; }
 
-    /**
-     * Finds or creates a method type with additional parameter types.
-     * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}.
-     * @param num    the position (zero-based) of the inserted parameter type(s)
-     * @param ptypesToInsert zero or more new parameter types to insert into the parameter list
-     * @return the same type, except with the selected parameter(s) inserted
-     * @throws IndexOutOfBoundsException if {@code num} is negative or greater than {@code parameterCount()}
-     * @throws IllegalArgumentException if any element of {@code ptypesToInsert} is {@code void.class}
-     *                                  or if the resulting method type would have more than 255 parameter slots
-     * @throws NullPointerException if {@code ptypesToInsert} or any of its elements is null
-     */
-    public MethodType insertParameterTypes(int num, List<Class<?>> ptypesToInsert) {
-        return insertParameterTypes(num, listToArray(ptypesToInsert));
-    }
+    public MethodType insertParameterTypes(int num, List<Class<?>> ptypesToInsert) { return null; }
 
-    /**
-     * Finds or creates a method type with additional parameter types.
-     * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}.
-     * @param ptypesToInsert zero or more new parameter types to insert after the end of the parameter list
-     * @return the same type, except with the selected parameter(s) appended
-     * @throws IllegalArgumentException if any element of {@code ptypesToInsert} is {@code void.class}
-     *                                  or if the resulting method type would have more than 255 parameter slots
-     * @throws NullPointerException if {@code ptypesToInsert} or any of its elements is null
-     */
-    public MethodType appendParameterTypes(List<Class<?>> ptypesToInsert) {
-        return insertParameterTypes(parameterCount(), ptypesToInsert);
-    }
+    public MethodType appendParameterTypes(List<Class<?>> ptypesToInsert) { return null; }
 
-     /**
-     * Finds or creates a method type with modified parameter types.
-     * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}.
-     * @param start  the position (zero-based) of the first replaced parameter type(s)
-     * @param end    the position (zero-based) after the last replaced parameter type(s)
-     * @param ptypesToInsert zero or more new parameter types to insert into the parameter list
-     * @return the same type, except with the selected parameter(s) replaced
-     * @throws IndexOutOfBoundsException if {@code start} is negative or greater than {@code parameterCount()}
-     *                                  or if {@code end} is negative or greater than {@code parameterCount()}
-     *                                  or if {@code start} is greater than {@code end}
-     * @throws IllegalArgumentException if any element of {@code ptypesToInsert} is {@code void.class}
-     *                                  or if the resulting method type would have more than 255 parameter slots
-     * @throws NullPointerException if {@code ptypesToInsert} or any of its elements is null
-     */
-    /*non-public*/ MethodType replaceParameterTypes(int start, int end, Class<?>... ptypesToInsert) {
-        if (start == end)
-            return insertParameterTypes(start, ptypesToInsert);
-        int len = ptypes.length;
-        if (!(0 <= start && start <= end && end <= len))
-            throw newIndexOutOfBoundsException("start="+start+" end="+end);
-        int ilen = ptypesToInsert.length;
-        if (ilen == 0)
-            return dropParameterTypes(start, end);
-        return dropParameterTypes(start, end).insertParameterTypes(start, ptypesToInsert);
-    }
+    public MethodType dropParameterTypes(int start, int end) { return null; }
 
-    /** Replace the last arrayLength parameter types with the component type of arrayType.
-     * @param arrayType any array type
-     * @param arrayLength the number of parameter types to change
-     * @return the resulting type
-     */
-    /*non-public*/ MethodType asSpreaderType(Class<?> arrayType, int arrayLength) {
-        assert(parameterCount() >= arrayLength);
-        int spreadPos = ptypes.length - arrayLength;
-        if (arrayLength == 0)  return this;  // nothing to change
-        if (arrayType == Object[].class) {
-            if (isGeneric())  return this;  // nothing to change
-            if (spreadPos == 0) {
-                // no leading arguments to preserve; go generic
-                MethodType res = genericMethodType(arrayLength);
-                if (rtype != Object.class) {
-                    res = res.changeReturnType(rtype);
-                }
-                return res;
-            }
-        }
-        Class<?> elemType = arrayType.getComponentType();
-        assert(elemType != null);
-        for (int i = spreadPos; i < ptypes.length; i++) {
-            if (ptypes[i] != elemType) {
-                Class<?>[] fixedPtypes = ptypes.clone();
-                Arrays.fill(fixedPtypes, i, ptypes.length, elemType);
-                return methodType(rtype, fixedPtypes);
-            }
-        }
-        return this;  // arguments check out; no change
-    }
+    public MethodType changeReturnType(Class<?> nrtype) { return null; }
 
-    /** Return the leading parameter type, which must exist and be a reference.
-     *  @return the leading parameter type, after error checks
-     */
-    /*non-public*/ Class<?> leadingReferenceParameter() {
-        Class<?> ptype;
-        if (ptypes.length == 0 ||
-            (ptype = ptypes[0]).isPrimitive())
-            throw newIllegalArgumentException("no leading reference parameter");
-        return ptype;
-    }
+    public boolean hasPrimitives() { return false; }
 
-    /** Delete the last parameter type and replace it with arrayLength copies of the component type of arrayType.
-     * @param arrayType any array type
-     * @param arrayLength the number of parameter types to insert
-     * @return the resulting type
-     */
-    /*non-public*/ MethodType asCollectorType(Class<?> arrayType, int arrayLength) {
-        assert(parameterCount() >= 1);
-        assert(lastParameterType().isAssignableFrom(arrayType));
-        MethodType res;
-        if (arrayType == Object[].class) {
-            res = genericMethodType(arrayLength);
-            if (rtype != Object.class) {
-                res = res.changeReturnType(rtype);
-            }
-        } else {
-            Class<?> elemType = arrayType.getComponentType();
-            assert(elemType != null);
-            res = methodType(rtype, Collections.nCopies(arrayLength, elemType));
-        }
-        if (ptypes.length == 1) {
-            return res;
-        } else {
-            return res.insertParameterTypes(0, parameterList().subList(0, ptypes.length-1));
-        }
-    }
+    public boolean hasWrappers() { return false; }
 
-    /**
-     * Finds or creates a method type with some parameter types omitted.
-     * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}.
-     * @param start  the index (zero-based) of the first parameter type to remove
-     * @param end    the index (greater than {@code start}) of the first parameter type after not to remove
-     * @return the same type, except with the selected parameter(s) removed
-     * @throws IndexOutOfBoundsException if {@code start} is negative or greater than {@code parameterCount()}
-     *                                  or if {@code end} is negative or greater than {@code parameterCount()}
-     *                                  or if {@code start} is greater than {@code end}
-     */
-    public MethodType dropParameterTypes(int start, int end) {
-        int len = ptypes.length;
-        if (!(0 <= start && start <= end && end <= len))
-            throw newIndexOutOfBoundsException("start="+start+" end="+end);
-        if (start == end)  return this;
-        Class<?>[] nptypes;
-        if (start == 0) {
-            if (end == len) {
-                // drop all parameters
-                nptypes = NO_PTYPES;
-            } else {
-                // drop initial parameter(s)
-                nptypes = Arrays.copyOfRange(ptypes, end, len);
-            }
-        } else {
-            if (end == len) {
-                // drop trailing parameter(s)
-                nptypes = Arrays.copyOfRange(ptypes, 0, start);
-            } else {
-                int tail = len - end;
-                nptypes = Arrays.copyOfRange(ptypes, 0, start + tail);
-                System.arraycopy(ptypes, end, nptypes, start, tail);
-            }
-        }
-        return makeImpl(rtype, nptypes, true);
-    }
+    public MethodType erase() { return null; }
 
-    /**
-     * Finds or creates a method type with a different return type.
-     * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}.
-     * @param nrtype a return parameter type to replace the old one with
-     * @return the same type, except with the return type change
-     * @throws NullPointerException if {@code nrtype} is null
-     */
-    public MethodType changeReturnType(Class<?> nrtype) {
-        if (returnType() == nrtype)  return this;
-        return makeImpl(nrtype, ptypes, true);
-    }
+    public MethodType generic() { return null; }
 
-    /**
-     * Reports if this type contains a primitive argument or return value.
-     * The return type {@code void} counts as a primitive.
-     * @return true if any of the types are primitives
-     */
-    public boolean hasPrimitives() {
-        return form.hasPrimitives();
-    }
+    public MethodType wrap() { return null; }
 
-    /**
-     * Reports if this type contains a wrapper argument or return value.
-     * Wrappers are types which box primitive values, such as {@link Integer}.
-     * The reference type {@code java.lang.Void} counts as a wrapper,
-     * if it occurs as a return type.
-     * @return true if any of the types are wrappers
-     */
-    public boolean hasWrappers() {
-        return unwrap() != this;
-    }
+    public MethodType unwrap() { return null; }
 
-    /**
-     * Erases all reference types to {@code Object}.
-     * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}.
-     * All primitive types (including {@code void}) will remain unchanged.
-     * @return a version of the original type with all reference types replaced
-     */
-    public MethodType erase() {
-        return form.erasedType();
-    }
+    public Class<?> parameterType(int num) { return null; }
 
-    /**
-     * Erases all reference types to {@code Object}, and all subword types to {@code int}.
-     * This is the reduced type polymorphism used by private methods
-     * such as {@link MethodHandle#invokeBasic invokeBasic}.
-     * @return a version of the original type with all reference and subword types replaced
-     */
-    /*non-public*/ MethodType basicType() {
-        return form.basicType();
-    }
+    public int parameterCount() { return 0; }
 
-    /**
-     * @return a version of the original type with MethodHandle prepended as the first argument
-     */
-    /*non-public*/ MethodType invokerType() {
-        return insertParameterTypes(0, MethodHandle.class);
-    }
+    public Class<?> returnType() { return null; }
 
-    /**
-     * Converts all types, both reference and primitive, to {@code Object}.
-     * Convenience method for {@link #genericMethodType(int) genericMethodType}.
-     * The expression {@code type.wrap().erase()} produces the same value
-     * as {@code type.generic()}.
-     * @return a version of the original type with all types replaced
-     */
-    public MethodType generic() {
-        return genericMethodType(parameterCount());
-    }
+    public List<Class<?>> parameterList() { return null; }
 
-    /*non-public*/ boolean isGeneric() {
-        return this == erase() && !hasPrimitives();
-    }
+    public Class<?>[] parameterArray() { return null; }
 
-    /**
-     * Converts all primitive types to their corresponding wrapper types.
-     * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}.
-     * All reference types (including wrapper types) will remain unchanged.
-     * A {@code void} return type is changed to the type {@code java.lang.Void}.
-     * The expression {@code type.wrap().erase()} produces the same value
-     * as {@code type.generic()}.
-     * @return a version of the original type with all primitive types replaced
-     */
-    public MethodType wrap() {
-        return hasPrimitives() ? wrapWithPrims(this) : this;
-    }
-
-    /**
-     * Converts all wrapper types to their corresponding primitive types.
-     * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}.
-     * All primitive types (including {@code void}) will remain unchanged.
-     * A return type of {@code java.lang.Void} is changed to {@code void}.
-     * @return a version of the original type with all wrapper types replaced
-     */
-    public MethodType unwrap() {
-        MethodType noprims = !hasPrimitives() ? this : wrapWithPrims(this);
-        return unwrapWithNoPrims(noprims);
-    }
-
-    private static MethodType wrapWithPrims(MethodType pt) {
-        assert(pt.hasPrimitives());
-        MethodType wt = pt.wrapAlt;
-        if (wt == null) {
-            // fill in lazily
-            wt = MethodTypeForm.canonicalize(pt, MethodTypeForm.WRAP, MethodTypeForm.WRAP);
-            assert(wt != null);
-            pt.wrapAlt = wt;
-        }
-        return wt;
-    }
-
-    private static MethodType unwrapWithNoPrims(MethodType wt) {
-        assert(!wt.hasPrimitives());
-        MethodType uwt = wt.wrapAlt;
-        if (uwt == null) {
-            // fill in lazily
-            uwt = MethodTypeForm.canonicalize(wt, MethodTypeForm.UNWRAP, MethodTypeForm.UNWRAP);
-            if (uwt == null)
-                uwt = wt;    // type has no wrappers or prims at all
-            wt.wrapAlt = uwt;
-        }
-        return uwt;
-    }
-
-    /**
-     * Returns the parameter type at the specified index, within this method type.
-     * @param num the index (zero-based) of the desired parameter type
-     * @return the selected parameter type
-     * @throws IndexOutOfBoundsException if {@code num} is not a valid index into {@code parameterArray()}
-     */
-    public Class<?> parameterType(int num) {
-        return ptypes[num];
-    }
-    /**
-     * Returns the number of parameter types in this method type.
-     * @return the number of parameter types
-     */
-    public int parameterCount() {
-        return ptypes.length;
-    }
-    /**
-     * Returns the return type of this method type.
-     * @return the return type
-     */
-    public Class<?> returnType() {
-        return rtype;
-    }
-
-    /**
-     * Presents the parameter types as a list (a convenience method).
-     * The list will be immutable.
-     * @return the parameter types (as an immutable list)
-     */
-    public List<Class<?>> parameterList() {
-        return Collections.unmodifiableList(Arrays.asList(ptypes.clone()));
-    }
-
-    /*non-public*/ Class<?> lastParameterType() {
-        int len = ptypes.length;
-        return len == 0 ? void.class : ptypes[len-1];
-    }
-
-    /**
-     * Presents the parameter types as an array (a convenience method).
-     * Changes to the array will not result in changes to the type.
-     * @return the parameter types (as a fresh copy if necessary)
-     */
-    public Class<?>[] parameterArray() {
-        return ptypes.clone();
-    }
-
-    /**
-     * Compares the specified object with this type for equality.
-     * That is, it returns <tt>true</tt> if and only if the specified object
-     * is also a method type with exactly the same parameters and return type.
-     * @param x object to compare
-     * @see Object#equals(Object)
-     */
-    @Override
-    public boolean equals(Object x) {
-        return this == x || x instanceof MethodType && equals((MethodType)x);
-    }
-
-    private boolean equals(MethodType that) {
-        return this.rtype == that.rtype
-            && Arrays.equals(this.ptypes, that.ptypes);
-    }
-
-    /**
-     * Returns the hash code value for this method type.
-     * It is defined to be the same as the hashcode of a List
-     * whose elements are the return type followed by the
-     * parameter types.
-     * @return the hash code value for this method type
-     * @see Object#hashCode()
-     * @see #equals(Object)
-     * @see List#hashCode()
-     */
-    @Override
-    public int hashCode() {
-      int hashCode = 31 + rtype.hashCode();
-      for (Class<?> ptype : ptypes)
-          hashCode = 31*hashCode + ptype.hashCode();
-      return hashCode;
-    }
-
-    /**
-     * Returns a string representation of the method type,
-     * of the form {@code "(PT0,PT1...)RT"}.
-     * The string representation of a method type is a
-     * parenthesis enclosed, comma separated list of type names,
-     * followed immediately by the return type.
-     * <p>
-     * Each type is represented by its
-     * {@link java.lang.Class#getSimpleName simple name}.
-     */
-    @Override
-    public String toString() {
-        StringBuilder sb = new StringBuilder();
-        sb.append("(");
-        for (int i = 0; i < ptypes.length; i++) {
-            if (i > 0)  sb.append(",");
-            sb.append(ptypes[i].getSimpleName());
-        }
-        sb.append(")");
-        sb.append(rtype.getSimpleName());
-        return sb.toString();
-    }
-
-    /** True if the old return type can always be viewed (w/o casting) under new return type,
-     *  and the new parameters can be viewed (w/o casting) under the old parameter types.
-     */
-    // Android-changed: Removed implementation details.
-    // boolean isViewableAs(MethodType newType, boolean keepInterfaces);
-    // boolean parametersAreViewableAs(MethodType newType, boolean keepInterfaces);
-    /*non-public*/
-    boolean isConvertibleTo(MethodType newType) {
-        MethodTypeForm oldForm = this.form();
-        MethodTypeForm newForm = newType.form();
-        if (oldForm == newForm)
-            // same parameter count, same primitive/object mix
-            return true;
-        if (!canConvert(returnType(), newType.returnType()))
-            return false;
-        Class<?>[] srcTypes = newType.ptypes;
-        Class<?>[] dstTypes = ptypes;
-        if (srcTypes == dstTypes)
-            return true;
-        int argc;
-        if ((argc = srcTypes.length) != dstTypes.length)
-            return false;
-        if (argc <= 1) {
-            if (argc == 1 && !canConvert(srcTypes[0], dstTypes[0]))
-                return false;
-            return true;
-        }
-        if ((oldForm.primitiveParameterCount() == 0 && oldForm.erasedType == this) ||
-            (newForm.primitiveParameterCount() == 0 && newForm.erasedType == newType)) {
-            // Somewhat complicated test to avoid a loop of 2 or more trips.
-            // If either type has only Object parameters, we know we can convert.
-            assert(canConvertParameters(srcTypes, dstTypes));
-            return true;
-        }
-        return canConvertParameters(srcTypes, dstTypes);
-    }
-
-    /** Returns true if MHs.explicitCastArguments produces the same result as MH.asType.
-     *  If the type conversion is impossible for either, the result should be false.
-     */
-    /*non-public*/
-    boolean explicitCastEquivalentToAsType(MethodType newType) {
-        if (this == newType)  return true;
-        if (!explicitCastEquivalentToAsType(rtype, newType.rtype)) {
-            return false;
-        }
-        Class<?>[] srcTypes = newType.ptypes;
-        Class<?>[] dstTypes = ptypes;
-        if (dstTypes == srcTypes) {
-            return true;
-        }
-        assert(dstTypes.length == srcTypes.length);
-        for (int i = 0; i < dstTypes.length; i++) {
-            if (!explicitCastEquivalentToAsType(srcTypes[i], dstTypes[i])) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    /** Reports true if the src can be converted to the dst, by both asType and MHs.eCE,
-     *  and with the same effect.
-     *  MHs.eCA has the following "upgrades" to MH.asType:
-     *  1. interfaces are unchecked (that is, treated as if aliased to Object)
-     *     Therefore, {@code Object->CharSequence} is possible in both cases but has different semantics
-     *  2a. the full matrix of primitive-to-primitive conversions is supported
-     *      Narrowing like {@code long->byte} and basic-typing like {@code boolean->int}
-     *      are not supported by asType, but anything supported by asType is equivalent
-     *      with MHs.eCE.
-     *  2b. conversion of void->primitive means explicit cast has to insert zero/false/null.
-     *  3a. unboxing conversions can be followed by the full matrix of primitive conversions
-     *  3b. unboxing of null is permitted (creates a zero primitive value)
-     * Other than interfaces, reference-to-reference conversions are the same.
-     * Boxing primitives to references is the same for both operators.
-     */
-    private static boolean explicitCastEquivalentToAsType(Class<?> src, Class<?> dst) {
-        if (src == dst || dst == Object.class || dst == void.class) {
-            return true;
-        } else if (src.isPrimitive() && src != void.class) {
-            // Could be a prim/prim conversion, where casting is a strict superset.
-            // Or a boxing conversion, which is always to an exact wrapper class.
-            return canConvert(src, dst);
-        } else if (dst.isPrimitive()) {
-            // Unboxing behavior is different between MHs.eCA & MH.asType (see 3b).
-            return false;
-        } else {
-            // R->R always works, but we have to avoid a check-cast to an interface.
-            return !dst.isInterface() || dst.isAssignableFrom(src);
-        }
-    }
-
-    private boolean canConvertParameters(Class<?>[] srcTypes, Class<?>[] dstTypes) {
-        for (int i = 0; i < srcTypes.length; i++) {
-            if (!canConvert(srcTypes[i], dstTypes[i])) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    /*non-public*/
-    static boolean canConvert(Class<?> src, Class<?> dst) {
-        // short-circuit a few cases:
-        if (src == dst || src == Object.class || dst == Object.class)  return true;
-        // the remainder of this logic is documented in MethodHandle.asType
-        if (src.isPrimitive()) {
-            // can force void to an explicit null, a la reflect.Method.invoke
-            // can also force void to a primitive zero, by analogy
-            if (src == void.class)  return true;  //or !dst.isPrimitive()?
-            Wrapper sw = Wrapper.forPrimitiveType(src);
-            if (dst.isPrimitive()) {
-                // P->P must widen
-                return Wrapper.forPrimitiveType(dst).isConvertibleFrom(sw);
-            } else {
-                // P->R must box and widen
-                return dst.isAssignableFrom(sw.wrapperType());
-            }
-        } else if (dst.isPrimitive()) {
-            // any value can be dropped
-            if (dst == void.class)  return true;
-            Wrapper dw = Wrapper.forPrimitiveType(dst);
-            // R->P must be able to unbox (from a dynamically chosen type) and widen
-            // For example:
-            //   Byte/Number/Comparable/Object -> dw:Byte -> byte.
-            //   Character/Comparable/Object -> dw:Character -> char
-            //   Boolean/Comparable/Object -> dw:Boolean -> boolean
-            // This means that dw must be cast-compatible with src.
-            if (src.isAssignableFrom(dw.wrapperType())) {
-                return true;
-            }
-            // The above does not work if the source reference is strongly typed
-            // to a wrapper whose primitive must be widened.  For example:
-            //   Byte -> unbox:byte -> short/int/long/float/double
-            //   Character -> unbox:char -> int/long/float/double
-            if (Wrapper.isWrapperType(src) &&
-                dw.isConvertibleFrom(Wrapper.forWrapperType(src))) {
-                // can unbox from src and then widen to dst
-                return true;
-            }
-            // We have already covered cases which arise due to runtime unboxing
-            // of a reference type which covers several wrapper types:
-            //   Object -> cast:Integer -> unbox:int -> long/float/double
-            //   Serializable -> cast:Byte -> unbox:byte -> byte/short/int/long/float/double
-            // An marginal case is Number -> dw:Character -> char, which would be OK if there were a
-            // subclass of Number which wraps a value that can convert to char.
-            // Since there is none, we don't need an extra check here to cover char or boolean.
-            return false;
-        } else {
-            // R->R always works, since null is always valid dynamically
-            return true;
-        }
-    }
-
-    /** Reports the number of JVM stack slots required to invoke a method
-     * of this type.  Note that (for historical reasons) the JVM requires
-     * a second stack slot to pass long and double arguments.
-     * So this method returns {@link #parameterCount() parameterCount} plus the
-     * number of long and double parameters (if any).
-     * <p>
-     * This method is included for the benefit of applications that must
-     * generate bytecodes that process method handles and invokedynamic.
-     * @return the number of JVM stack slots for this type's parameters
-     */
-    /*non-public*/ int parameterSlotCount() {
-        return form.parameterSlotCount();
-    }
-
-    /// Queries which have to do with the bytecode architecture
-
-    // Android-changed: These methods aren't needed on Android and are unused within the JDK.
-    //
-    // int parameterSlotDepth(int num);
-    // int returnSlotCount();
-    //
-    // Android-changed: Removed cache of higher order adapters.
-    //
-    // Invokers invokers();
-
-    /**
-     * Finds or creates an instance of a method type, given the spelling of its bytecode descriptor.
-     * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}.
-     * Any class or interface name embedded in the descriptor string
-     * will be resolved by calling {@link ClassLoader#loadClass(java.lang.String)}
-     * on the given loader (or if it is null, on the system class loader).
-     * <p>
-     * Note that it is possible to encounter method types which cannot be
-     * constructed by this method, because their component types are
-     * not all reachable from a common class loader.
-     * <p>
-     * This method is included for the benefit of applications that must
-     * generate bytecodes that process method handles and {@code invokedynamic}.
-     * @param descriptor a bytecode-level type descriptor string "(T...)T"
-     * @param loader the class loader in which to look up the types
-     * @return a method type matching the bytecode-level type descriptor
-     * @throws NullPointerException if the string is null
-     * @throws IllegalArgumentException if the string is not well-formed
-     * @throws TypeNotPresentException if a named type cannot be found
-     */
     public static MethodType fromMethodDescriptorString(String descriptor, ClassLoader loader)
-        throws IllegalArgumentException, TypeNotPresentException
-    {
-        if (!descriptor.startsWith("(") ||  // also generates NPE if needed
-            descriptor.indexOf(')') < 0 ||
-            descriptor.indexOf('.') >= 0)
-            throw newIllegalArgumentException("not a method descriptor: "+descriptor);
-        List<Class<?>> types = BytecodeDescriptor.parseMethod(descriptor, loader);
-        Class<?> rtype = types.remove(types.size() - 1);
-        checkSlotCount(types.size());
-        Class<?>[] ptypes = listToArray(types);
-        return makeImpl(rtype, ptypes, true);
-    }
+        throws IllegalArgumentException, TypeNotPresentException { return null; }
 
-    /**
-     * Produces a bytecode descriptor representation of the method type.
-     * <p>
-     * Note that this is not a strict inverse of {@link #fromMethodDescriptorString fromMethodDescriptorString}.
-     * Two distinct classes which share a common name but have different class loaders
-     * will appear identical when viewed within descriptor strings.
-     * <p>
-     * This method is included for the benefit of applications that must
-     * generate bytecodes that process method handles and {@code invokedynamic}.
-     * {@link #fromMethodDescriptorString(java.lang.String, java.lang.ClassLoader) fromMethodDescriptorString},
-     * because the latter requires a suitable class loader argument.
-     * @return the bytecode type descriptor representation
-     */
-    public String toMethodDescriptorString() {
-        String desc = methodDescriptor;
-        if (desc == null) {
-            desc = BytecodeDescriptor.unparse(this);
-            methodDescriptor = desc;
-        }
-        return desc;
-    }
-
-    /*non-public*/ static String toFieldDescriptorString(Class<?> cls) {
-        return BytecodeDescriptor.unparse(cls);
-    }
-
-    /// Serialization.
-
-    /**
-     * There are no serializable fields for {@code MethodType}.
-     */
-    private static final java.io.ObjectStreamField[] serialPersistentFields = { };
-
-    /**
-     * Save the {@code MethodType} instance to a stream.
-     *
-     * @serialData
-     * For portability, the serialized format does not refer to named fields.
-     * Instead, the return type and parameter type arrays are written directly
-     * from the {@code writeObject} method, using two calls to {@code s.writeObject}
-     * as follows:
-     * <blockquote><pre>{@code
-s.writeObject(this.returnType());
-s.writeObject(this.parameterArray());
-     * }</pre></blockquote>
-     * <p>
-     * The deserialized field values are checked as if they were
-     * provided to the factory method {@link #methodType(Class,Class[]) methodType}.
-     * For example, null values, or {@code void} parameter types,
-     * will lead to exceptions during deserialization.
-     * @param s the stream to write the object to
-     * @throws java.io.IOException if there is a problem writing the object
-     */
-    private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException {
-        s.defaultWriteObject();  // requires serialPersistentFields to be an empty array
-        s.writeObject(returnType());
-        s.writeObject(parameterArray());
-    }
-
-    /**
-     * Reconstitute the {@code MethodType} instance from a stream (that is,
-     * deserialize it).
-     * This instance is a scratch object with bogus final fields.
-     * It provides the parameters to the factory method called by
-     * {@link #readResolve readResolve}.
-     * After that call it is discarded.
-     * @param s the stream to read the object from
-     * @throws java.io.IOException if there is a problem reading the object
-     * @throws ClassNotFoundException if one of the component classes cannot be resolved
-     * @see #MethodType()
-     * @see #readResolve
-     * @see #writeObject
-     */
-    private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException {
-        s.defaultReadObject();  // requires serialPersistentFields to be an empty array
-
-        Class<?>   returnType     = (Class<?>)   s.readObject();
-        Class<?>[] parameterArray = (Class<?>[]) s.readObject();
-
-        // Probably this object will never escape, but let's check
-        // the field values now, just to be sure.
-        checkRtype(returnType);
-        checkPtypes(parameterArray);
-
-        parameterArray = parameterArray.clone();  // make sure it is unshared
-        MethodType_init(returnType, parameterArray);
-    }
-
-    /**
-     * For serialization only.
-     * Sets the final fields to null, pending {@code Unsafe.putObject}.
-     */
-    private MethodType() {
-        this.rtype = null;
-        this.ptypes = null;
-    }
-    private void MethodType_init(Class<?> rtype, Class<?>[] ptypes) {
-        // In order to communicate these values to readResolve, we must
-        // store them into the implementation-specific final fields.
-        checkRtype(rtype);
-        checkPtypes(ptypes);
-        UNSAFE.putObject(this, rtypeOffset, rtype);
-        UNSAFE.putObject(this, ptypesOffset, ptypes);
-    }
-
-    // Support for resetting final fields while deserializing
-    private static final long rtypeOffset, ptypesOffset;
-    static {
-        try {
-            rtypeOffset = UNSAFE.objectFieldOffset
-                (MethodType.class.getDeclaredField("rtype"));
-            ptypesOffset = UNSAFE.objectFieldOffset
-                (MethodType.class.getDeclaredField("ptypes"));
-        } catch (Exception ex) {
-            throw new Error(ex);
-        }
-    }
-
-    /**
-     * Resolves and initializes a {@code MethodType} object
-     * after serialization.
-     * @return the fully initialized {@code MethodType} object
-     */
-    private Object readResolve() {
-        // Do not use a trusted path for deserialization:
-        //return makeImpl(rtype, ptypes, true);
-        // Verify all operands, and make sure ptypes is unshared:
-        return methodType(rtype, ptypes);
-    }
-
-    /**
-     * Simple implementation of weak concurrent intern set.
-     *
-     * @param <T> interned type
-     */
-    private static class ConcurrentWeakInternSet<T> {
-
-        private final ConcurrentMap<WeakEntry<T>, WeakEntry<T>> map;
-        private final ReferenceQueue<T> stale;
-
-        public ConcurrentWeakInternSet() {
-            this.map = new ConcurrentHashMap<>();
-            this.stale = new ReferenceQueue<>();
-        }
-
-        /**
-         * Get the existing interned element.
-         * This method returns null if no element is interned.
-         *
-         * @param elem element to look up
-         * @return the interned element
-         */
-        public T get(T elem) {
-            if (elem == null) throw new NullPointerException();
-            expungeStaleElements();
-
-            WeakEntry<T> value = map.get(new WeakEntry<>(elem));
-            if (value != null) {
-                T res = value.get();
-                if (res != null) {
-                    return res;
-                }
-            }
-            return null;
-        }
-
-        /**
-         * Interns the element.
-         * Always returns non-null element, matching the one in the intern set.
-         * Under the race against another add(), it can return <i>different</i>
-         * element, if another thread beats us to interning it.
-         *
-         * @param elem element to add
-         * @return element that was actually added
-         */
-        public T add(T elem) {
-            if (elem == null) throw new NullPointerException();
-
-            // Playing double race here, and so spinloop is required.
-            // First race is with two concurrent updaters.
-            // Second race is with GC purging weak ref under our feet.
-            // Hopefully, we almost always end up with a single pass.
-            T interned;
-            WeakEntry<T> e = new WeakEntry<>(elem, stale);
-            do {
-                expungeStaleElements();
-                WeakEntry<T> exist = map.putIfAbsent(e, e);
-                interned = (exist == null) ? elem : exist.get();
-            } while (interned == null);
-            return interned;
-        }
-
-        private void expungeStaleElements() {
-            Reference<? extends T> reference;
-            while ((reference = stale.poll()) != null) {
-                map.remove(reference);
-            }
-        }
-
-        private static class WeakEntry<T> extends WeakReference<T> {
-
-            public final int hashcode;
-
-            public WeakEntry(T key, ReferenceQueue<T> queue) {
-                super(key, queue);
-                hashcode = key.hashCode();
-            }
-
-            public WeakEntry(T key) {
-                super(key);
-                hashcode = key.hashCode();
-            }
-
-            @Override
-            public boolean equals(Object obj) {
-                if (obj instanceof WeakEntry) {
-                    Object that = ((WeakEntry) obj).get();
-                    Object mine = get();
-                    return (that == null || mine == null) ? (this == obj) : mine.equals(that);
-                }
-                return false;
-            }
-
-            @Override
-            public int hashCode() {
-                return hashcode;
-            }
-
-        }
-    }
+    public String toMethodDescriptorString() { return null; }
 
 }
diff --git a/java/lang/invoke/VarHandle.java b/java/lang/invoke/VarHandle.java
index bb93fcf..562efb6 100644
--- a/java/lang/invoke/VarHandle.java
+++ b/java/lang/invoke/VarHandle.java
@@ -25,6 +25,10 @@
 
 package java.lang.invoke;
 
+import dalvik.system.VMRuntime;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.EnumSet;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -442,9 +446,16 @@
     */
     // END Android-removed: No VarForm in Android implementation.
 
-    RuntimeException unsupported() {
-        return new UnsupportedOperationException();
-    }
+    // BEGIN Android-added: fields for common metadata.
+    /** The target type for accesses. */
+    private final Class<?> varType;
+
+    /** The coordinate types of a VarHandle instance. */
+    private final List<Class<?>> coordinateTypes;
+
+    /** BitMask of supported access mode indexed by AccessMode.ordinal(). */
+    private final int accessModesBitMask;
+    // END Android-added: fields for common metadata.
 
     // Plain accessors
 
@@ -474,8 +485,8 @@
      * symbolic type descriptor, but a reference cast fails.
      */
     public final native
-    // Android-removed: unsupported annotations.
-    // @MethodHandle.PolymorphicSignature
+    @MethodHandle.PolymorphicSignature
+    // Android-removed: unsupported annotation.
     // @HotSpotIntrinsicCandidate
     Object get(Object... args);
 
@@ -501,8 +512,8 @@
      * symbolic type descriptor, but a reference cast fails.
      */
     public final native
-    // Android-removed: unsupported annotations.
-    // @MethodHandle.PolymorphicSignature
+    @MethodHandle.PolymorphicSignature
+    // Android-removed: unsupported annotation.
     // @HotSpotIntrinsicCandidate
     void set(Object... args);
 
@@ -534,8 +545,8 @@
      * symbolic type descriptor, but a reference cast fails.
      */
     public final native
-    // Android-removed: unsupported annotations.
-    // @MethodHandle.PolymorphicSignature
+    @MethodHandle.PolymorphicSignature
+    // Android-removed: unsupported annotation.
     // @HotSpotIntrinsicCandidate
     Object getVolatile(Object... args);
 
@@ -565,8 +576,8 @@
      * symbolic type descriptor, but a reference cast fails.
      */
     public final native
-    // Android-removed: unsupported annotations.
-    // @MethodHandle.PolymorphicSignature
+    @MethodHandle.PolymorphicSignature
+    // Android-removed: unsupported annotation.
     // @HotSpotIntrinsicCandidate
     void setVolatile(Object... args);
 
@@ -596,8 +607,8 @@
      * symbolic type descriptor, but a reference cast fails.
      */
     public final native
-    // Android-removed: unsupported annotations.
-    // @MethodHandle.PolymorphicSignature
+    @MethodHandle.PolymorphicSignature
+    // Android-removed: unsupported annotation.
     // @HotSpotIntrinsicCandidate
     Object getOpaque(Object... args);
 
@@ -624,8 +635,8 @@
      * symbolic type descriptor, but a reference cast fails.
      */
     public final native
-    // Android-removed: unsupported annotations.
-    // @MethodHandle.PolymorphicSignature
+    @MethodHandle.PolymorphicSignature
+    // Android-removed: unsupported annotation.
     // @HotSpotIntrinsicCandidate
     void setOpaque(Object... args);
 
@@ -662,8 +673,8 @@
      * symbolic type descriptor, but a reference cast fails.
      */
     public final native
-    // Android-removed: unsupported annotations.
-    // @MethodHandle.PolymorphicSignature
+    @MethodHandle.PolymorphicSignature
+    // Android-removed: unsupported annotation.
     // @HotSpotIntrinsicCandidate
     Object getAcquire(Object... args);
 
@@ -694,8 +705,8 @@
      * symbolic type descriptor, but a reference cast fails.
      */
     public final native
-    // Android-removed: unsupported annotations.
-    // @MethodHandle.PolymorphicSignature
+    @MethodHandle.PolymorphicSignature
+    // Android-removed: unsupported annotation.
     // @HotSpotIntrinsicCandidate
     void setRelease(Object... args);
 
@@ -731,8 +742,8 @@
      * @see #getVolatile(Object...)
      */
     public final native
-    // Android-removed: unsupported annotations.
-    // @MethodHandle.PolymorphicSignature
+    @MethodHandle.PolymorphicSignature
+    // Android-removed: unsupported annotation.
     // @HotSpotIntrinsicCandidate
     boolean compareAndSet(Object... args);
 
@@ -767,8 +778,8 @@
      * @see #getVolatile(Object...)
      */
     public final native
-    // Android-removed: unsupported annotations.
-    // @MethodHandle.PolymorphicSignature
+    @MethodHandle.PolymorphicSignature
+    // Android-removed: unsupported annotation.
     // @HotSpotIntrinsicCandidate
     Object compareAndExchange(Object... args);
 
@@ -803,8 +814,8 @@
      * @see #getAcquire(Object...)
      */
     public final native
-    // Android-removed: unsupported annotations.
-    // @MethodHandle.PolymorphicSignature
+    @MethodHandle.PolymorphicSignature
+    // Android-removed: unsupported annotation.
     // @HotSpotIntrinsicCandidate
     Object compareAndExchangeAcquire(Object... args);
 
@@ -839,8 +850,8 @@
      * @see #get(Object...)
      */
     public final native
-    // Android-removed: unsupported annotations.
-    // @MethodHandle.PolymorphicSignature
+    @MethodHandle.PolymorphicSignature
+    // Android-removed: unsupported annotation.
     // @HotSpotIntrinsicCandidate
     Object compareAndExchangeRelease(Object... args);
 
@@ -879,8 +890,8 @@
      * @see #get(Object...)
      */
     public final native
-    // Android-removed: unsupported annotations.
-    // @MethodHandle.PolymorphicSignature
+    @MethodHandle.PolymorphicSignature
+    // Android-removed: unsupported annotation.
     // @HotSpotIntrinsicCandidate
     boolean weakCompareAndSetPlain(Object... args);
 
@@ -917,8 +928,8 @@
      * @see #getVolatile(Object...)
      */
     public final native
-    // Android-removed: unsupported annotations.
-    // @MethodHandle.PolymorphicSignature
+    @MethodHandle.PolymorphicSignature
+    // Android-removed: unsupported annotation.
     // @HotSpotIntrinsicCandidate
     boolean weakCompareAndSet(Object... args);
 
@@ -956,8 +967,8 @@
      * @see #getAcquire(Object...)
      */
     public final native
-    // Android-removed: unsupported annotations.
-    // @MethodHandle.PolymorphicSignature
+    @MethodHandle.PolymorphicSignature
+    // Android-removed: unsupported annotation.
     // @HotSpotIntrinsicCandidate
     boolean weakCompareAndSetAcquire(Object... args);
 
@@ -995,8 +1006,8 @@
      * @see #get(Object...)
      */
     public final native
-    // Android-removed: unsupported annotations.
-    // @MethodHandle.PolymorphicSignature
+    @MethodHandle.PolymorphicSignature
+    // Android-removed: unsupported annotation.
     // @HotSpotIntrinsicCandidate
     boolean weakCompareAndSetRelease(Object... args);
 
@@ -1029,8 +1040,8 @@
      * @see #getVolatile(Object...)
      */
     public final native
-    // Android-removed: unsupported annotations.
-    // @MethodHandle.PolymorphicSignature
+    @MethodHandle.PolymorphicSignature
+    // Android-removed: unsupported annotation.
     // @HotSpotIntrinsicCandidate
     Object getAndSet(Object... args);
 
@@ -1063,8 +1074,8 @@
      * @see #getVolatile(Object...)
      */
     public final native
-    // Android-removed: unsupported annotations.
-    // @MethodHandle.PolymorphicSignature
+    @MethodHandle.PolymorphicSignature
+    // Android-removed: unsupported annotation.
     // @HotSpotIntrinsicCandidate
     Object getAndSetAcquire(Object... args);
 
@@ -1097,8 +1108,8 @@
      * @see #getVolatile(Object...)
      */
     public final native
-    // Android-removed: unsupported annotations.
-    // @MethodHandle.PolymorphicSignature
+    @MethodHandle.PolymorphicSignature
+    // Android-removed: unsupported annotation.
     // @HotSpotIntrinsicCandidate
     Object getAndSetRelease(Object... args);
 
@@ -1134,8 +1145,8 @@
      * @see #getVolatile(Object...)
      */
     public final native
-    // Android-removed: unsupported annotations.
-    // @MethodHandle.PolymorphicSignature
+    @MethodHandle.PolymorphicSignature
+    // Android-removed: unsupported annotation.
     // @HotSpotIntrinsicCandidate
     Object getAndAdd(Object... args);
 
@@ -1168,8 +1179,8 @@
      * @see #getVolatile(Object...)
      */
     public final native
-    // Android-removed: unsupported annotations.
-    // @MethodHandle.PolymorphicSignature
+    @MethodHandle.PolymorphicSignature
+    // Android-removed: unsupported annotation.
     // @HotSpotIntrinsicCandidate
     Object getAndAddAcquire(Object... args);
 
@@ -1202,8 +1213,8 @@
      * @see #getVolatile(Object...)
      */
     public final native
-    // Android-removed: unsupported annotations.
-    // @MethodHandle.PolymorphicSignature
+    @MethodHandle.PolymorphicSignature
+    // Android-removed: unsupported annotation.
     // @HotSpotIntrinsicCandidate
     Object getAndAddRelease(Object... args);
 
@@ -1244,8 +1255,8 @@
      * @see #getVolatile(Object...)
      */
     public final native
-    // Android-removed: unsupported annotations.
-    // @MethodHandle.PolymorphicSignature
+    @MethodHandle.PolymorphicSignature
+    // Android-removed: unsupported annotation.
     // @HotSpotIntrinsicCandidate
     Object getAndBitwiseOr(Object... args);
 
@@ -1282,8 +1293,8 @@
      * @see #getAcquire(Object...)
      */
     public final native
-    // Android-removed: unsupported annotations.
-    // @MethodHandle.PolymorphicSignature
+    @MethodHandle.PolymorphicSignature
+    // Android-removed: unsupported annotation.
     // @HotSpotIntrinsicCandidate
     Object getAndBitwiseOrAcquire(Object... args);
 
@@ -1320,8 +1331,8 @@
      * @see #get(Object...)
      */
     public final native
-    // Android-removed: unsupported annotations.
-    // @MethodHandle.PolymorphicSignature
+    @MethodHandle.PolymorphicSignature
+    // Android-removed: unsupported annotation.
     // @HotSpotIntrinsicCandidate
     Object getAndBitwiseOrRelease(Object... args);
 
@@ -1358,8 +1369,8 @@
      * @see #getVolatile(Object...)
      */
     public final native
-    // Android-removed: unsupported annotations.
-    // @MethodHandle.PolymorphicSignature
+    @MethodHandle.PolymorphicSignature
+    // Android-removed: unsupported annotation.
     // @HotSpotIntrinsicCandidate
     Object getAndBitwiseAnd(Object... args);
 
@@ -1396,8 +1407,8 @@
      * @see #getAcquire(Object...)
      */
     public final native
-    // Android-removed: unsupported annotations.
-    // @MethodHandle.PolymorphicSignature
+    @MethodHandle.PolymorphicSignature
+    // Android-removed: unsupported annotation.
     // @HotSpotIntrinsicCandidate
     Object getAndBitwiseAndAcquire(Object... args);
 
@@ -1434,8 +1445,8 @@
      * @see #get(Object...)
      */
     public final native
-    // Android-removed: unsupported annotations.
-    // @MethodHandle.PolymorphicSignature
+    @MethodHandle.PolymorphicSignature
+    // Android-removed: unsupported annotation.
     // @HotSpotIntrinsicCandidate
     Object getAndBitwiseAndRelease(Object... args);
 
@@ -1472,8 +1483,8 @@
      * @see #getVolatile(Object...)
      */
     public final native
-    // Android-removed: unsupported annotations.
-    // @MethodHandle.PolymorphicSignature
+    @MethodHandle.PolymorphicSignature
+    // Android-removed: unsupported annotation.
     // @HotSpotIntrinsicCandidate
     Object getAndBitwiseXor(Object... args);
 
@@ -1510,8 +1521,8 @@
      * @see #getAcquire(Object...)
      */
     public final native
-    // Android-removed: unsupported annotations.
-    // @MethodHandle.PolymorphicSignature
+    @MethodHandle.PolymorphicSignature
+    // Android-removed: unsupported annotation.
     // @HotSpotIntrinsicCandidate
     Object getAndBitwiseXorAcquire(Object... args);
 
@@ -1548,8 +1559,8 @@
      * @see #get(Object...)
      */
     public final native
-    // Android-removed: unsupported annotations.
-    // @MethodHandle.PolymorphicSignature
+    @MethodHandle.PolymorphicSignature
+    // Android-removed: unsupported annotation.
     // @HotSpotIntrinsicCandidate
     Object getAndBitwiseXorRelease(Object... args);
 
@@ -1559,7 +1570,11 @@
         SET(void.class),
         COMPARE_AND_SWAP(boolean.class),
         COMPARE_AND_EXCHANGE(Object.class),
-        GET_AND_UPDATE(Object.class);
+        GET_AND_UPDATE(Object.class),
+        // Android-added: Finer grained access types.
+        // These are used to help categorize the access modes that a VarHandle supports.
+        GET_AND_UPDATE_BITWISE(Object.class),
+        GET_AND_UPDATE_NUMERIC(Object.class);
 
         final Class<?> returnType;
         final boolean isMonomorphicInReturnType;
@@ -1596,6 +1611,8 @@
                     ps[i] = value;
                     return MethodType.methodType(value, ps);
                 case GET_AND_UPDATE:
+                case GET_AND_UPDATE_BITWISE:
+                case GET_AND_UPDATE_NUMERIC:
                     ps = allocateParameters(1, receiver, intermediate);
                     i = fillParameters(ps, receiver, intermediate);
                     ps[i] = value;
@@ -1746,73 +1763,73 @@
          * method
          * {@link VarHandle#getAndAdd VarHandle.getAndAdd}
          */
-        GET_AND_ADD("getAndAdd", AccessType.GET_AND_UPDATE),
+        GET_AND_ADD("getAndAdd", AccessType.GET_AND_UPDATE_NUMERIC),
         /**
          * The access mode whose access is specified by the corresponding
          * method
          * {@link VarHandle#getAndAddAcquire VarHandle.getAndAddAcquire}
          */
-        GET_AND_ADD_ACQUIRE("getAndAddAcquire", AccessType.GET_AND_UPDATE),
+        GET_AND_ADD_ACQUIRE("getAndAddAcquire", AccessType.GET_AND_UPDATE_NUMERIC),
         /**
          * The access mode whose access is specified by the corresponding
          * method
          * {@link VarHandle#getAndAddRelease VarHandle.getAndAddRelease}
          */
-        GET_AND_ADD_RELEASE("getAndAddRelease", AccessType.GET_AND_UPDATE),
+        GET_AND_ADD_RELEASE("getAndAddRelease", AccessType.GET_AND_UPDATE_NUMERIC),
         /**
          * The access mode whose access is specified by the corresponding
          * method
          * {@link VarHandle#getAndBitwiseOr VarHandle.getAndBitwiseOr}
          */
-        GET_AND_BITWISE_OR("getAndBitwiseOr", AccessType.GET_AND_UPDATE),
+        GET_AND_BITWISE_OR("getAndBitwiseOr", AccessType.GET_AND_UPDATE_BITWISE),
         /**
          * The access mode whose access is specified by the corresponding
          * method
          * {@link VarHandle#getAndBitwiseOrRelease VarHandle.getAndBitwiseOrRelease}
          */
-        GET_AND_BITWISE_OR_RELEASE("getAndBitwiseOrRelease", AccessType.GET_AND_UPDATE),
+        GET_AND_BITWISE_OR_RELEASE("getAndBitwiseOrRelease", AccessType.GET_AND_UPDATE_BITWISE),
         /**
          * The access mode whose access is specified by the corresponding
          * method
          * {@link VarHandle#getAndBitwiseOrAcquire VarHandle.getAndBitwiseOrAcquire}
          */
-        GET_AND_BITWISE_OR_ACQUIRE("getAndBitwiseOrAcquire", AccessType.GET_AND_UPDATE),
+        GET_AND_BITWISE_OR_ACQUIRE("getAndBitwiseOrAcquire", AccessType.GET_AND_UPDATE_BITWISE),
         /**
          * The access mode whose access is specified by the corresponding
          * method
          * {@link VarHandle#getAndBitwiseAnd VarHandle.getAndBitwiseAnd}
          */
-        GET_AND_BITWISE_AND("getAndBitwiseAnd", AccessType.GET_AND_UPDATE),
+        GET_AND_BITWISE_AND("getAndBitwiseAnd", AccessType.GET_AND_UPDATE_BITWISE),
         /**
          * The access mode whose access is specified by the corresponding
          * method
          * {@link VarHandle#getAndBitwiseAndRelease VarHandle.getAndBitwiseAndRelease}
          */
-        GET_AND_BITWISE_AND_RELEASE("getAndBitwiseAndRelease", AccessType.GET_AND_UPDATE),
+        GET_AND_BITWISE_AND_RELEASE("getAndBitwiseAndRelease", AccessType.GET_AND_UPDATE_BITWISE),
         /**
          * The access mode whose access is specified by the corresponding
          * method
          * {@link VarHandle#getAndBitwiseAndAcquire VarHandle.getAndBitwiseAndAcquire}
          */
-        GET_AND_BITWISE_AND_ACQUIRE("getAndBitwiseAndAcquire", AccessType.GET_AND_UPDATE),
+        GET_AND_BITWISE_AND_ACQUIRE("getAndBitwiseAndAcquire", AccessType.GET_AND_UPDATE_BITWISE),
         /**
          * The access mode whose access is specified by the corresponding
          * method
          * {@link VarHandle#getAndBitwiseXor VarHandle.getAndBitwiseXor}
          */
-        GET_AND_BITWISE_XOR("getAndBitwiseXor", AccessType.GET_AND_UPDATE),
+        GET_AND_BITWISE_XOR("getAndBitwiseXor", AccessType.GET_AND_UPDATE_BITWISE),
         /**
          * The access mode whose access is specified by the corresponding
          * method
          * {@link VarHandle#getAndBitwiseXorRelease VarHandle.getAndBitwiseXorRelease}
          */
-        GET_AND_BITWISE_XOR_RELEASE("getAndBitwiseXorRelease", AccessType.GET_AND_UPDATE),
+        GET_AND_BITWISE_XOR_RELEASE("getAndBitwiseXorRelease", AccessType.GET_AND_UPDATE_BITWISE),
         /**
          * The access mode whose access is specified by the corresponding
          * method
          * {@link VarHandle#getAndBitwiseXorAcquire VarHandle.getAndBitwiseXorAcquire}
          */
-        GET_AND_BITWISE_XOR_ACQUIRE("getAndBitwiseXorAcquire", AccessType.GET_AND_UPDATE),
+        GET_AND_BITWISE_XOR_ACQUIRE("getAndBitwiseXorAcquire", AccessType.GET_AND_UPDATE_BITWISE),
         ;
 
         static final Map<String, AccessMode> methodNameToAccessMode;
@@ -1898,8 +1915,11 @@
      * @return the variable type of variables referenced by this VarHandle
      */
     public final Class<?> varType() {
-        MethodType typeSet = accessModeType(AccessMode.SET);
-        return typeSet.parameterType(typeSet.parameterCount() - 1);
+        // Android-removed: existing implementation.
+        // MethodType typeSet = accessModeType(AccessMode.SET);
+        // return typeSet.parameterType(typeSet.parameterCount() - 1)
+        // Android-added: return instance field.
+        return varType;
     }
 
     /**
@@ -1909,8 +1929,11 @@
      * list is unmodifiable
      */
     public final List<Class<?>> coordinateTypes() {
-        MethodType typeGet = accessModeType(AccessMode.GET);
-        return typeGet.parameterList();
+        // Android-removed: existing implementation.
+        // MethodType typeGet = accessModeType(AccessMode.GET);
+        // return typeGet.parameterList();
+        // Android-added: return instance field.
+        return coordinateTypes;
     }
 
     /**
@@ -1940,9 +1963,18 @@
         */
         // END Android-removed: Relies on internal class that is not part of the
         // Android implementation.
-        // Android-added: Throw an exception until implemented.
-        unsupported();  // TODO(b/65872996)
-        return null;
+        // Android-added: alternative implementation.
+        switch (coordinateTypes.size()) {
+            case 0:
+                return accessMode.at.accessModeType(null, varType);
+            case 1:
+                return accessMode.at.accessModeType(coordinateTypes.get(0), varType);
+            case 2:
+                return accessMode.at.accessModeType(coordinateTypes.get(0), varType,
+                                                    coordinateTypes.get(1));
+            default:
+                throw new InternalError("bad coordinateTypes: " + coordinateTypes);
+        }
     }
 
     // Android-removed: Not part of the Android implementation.
@@ -1964,9 +1996,9 @@
     public final boolean isAccessModeSupported(AccessMode accessMode) {
         // Android-removed: Refers to unused field vform.
         // return AccessMode.getMemberName(accessMode.ordinal(), vform) != null;
-        // Android-added: Throw an exception until implemented.
-        unsupported();  // TODO(b/65872996)
-        return false;
+        // Android-added: use accessModesBitsMask field.
+        final int testBit = 1 << accessMode.ordinal();
+        return (accessModesBitMask & testBit) == testBit;
     }
 
     /**
@@ -2002,9 +2034,10 @@
                     bindTo(this);
         }
         */
-        // Android-added: Throw an exception until implemented.
-        unsupported();  // TODO(b/65872996)
-        return null;
+        // END Android-removed: no vform field in Android implementation.
+
+        // Android-added: basic implementation following description in javadoc for this method.
+        return MethodHandles.varHandleInvoker(accessMode, accessModeType(accessMode)).bindTo(this);
     }
 
     // BEGIN Android-removed: Not used in Android implementation.
@@ -2055,8 +2088,8 @@
     */
     // END Android-removed: Not used in Android implementation.
 
-    /*non-public*/
     // BEGIN Android-removed: No VarForm in Android implementation.
+    /*non-public*/
     /*
     final void updateVarForm(VarForm newVForm) {
         if (vform == newVForm) return;
@@ -2158,4 +2191,197 @@
         // NB The compiler recognizes all the fences here as intrinsics.
         UNSAFE.storeFence();
     }
+
+    // BEGIN Android-added: package private constructors.
+    /**
+     * Constructor for VarHandle with no coordinates.
+     *
+     * @param varType the variable type of variables to be referenced
+     * @param isFinal whether the target variables are final (non-modifiable)
+     * @hide
+     */
+    VarHandle(Class<?> varType, boolean isFinal) {
+        this.varType = varType;
+        this.coordinateTypes = Collections.EMPTY_LIST;
+        this.accessModesBitMask = alignedAccessModesBitMask(varType, isFinal);
+    }
+
+    /**
+     * Constructor for VarHandle with one coordinate.
+     *
+     * @param varType the variable type of variables to be referenced
+     * @param isFinal  whether the target variables are final (non-modifiable)
+     * @param coordinate the coordinate
+     * @hide
+     */
+    VarHandle(Class<?> varType, boolean isFinal, Class<?> coordinate) {
+        this.varType = varType;
+        this.coordinateTypes = Collections.singletonList(coordinate);
+        this.accessModesBitMask = alignedAccessModesBitMask(varType, isFinal);
+    }
+
+    /**
+     * Constructor for VarHandle with two coordinates.
+     *
+     * @param varType the variable type of variables to be referenced
+     * @param backingArrayType the type of the array accesses will be performed on
+     * @param isFinal whether the target variables are final (non-modifiable)
+     * @param coordinate0 the first coordinate
+     * @param coordinate1 the second coordinate
+     * @hide
+     */
+    VarHandle(Class<?> varType, Class<?> backingArrayType,  boolean isFinal,
+              Class<?> coordinate0, Class<?> coordinate1) {
+        this.varType = varType;
+        this.coordinateTypes = Collections.unmodifiableList(
+            Arrays.asList(coordinate0, coordinate1));
+        Class<?> backingArrayComponentType = backingArrayType.getComponentType();
+        if (backingArrayComponentType != varType && backingArrayComponentType != byte.class) {
+            throw new InternalError("Unsupported backingArrayType: " + backingArrayType);
+        }
+
+        if (backingArrayType.getComponentType() == varType) {
+            this.accessModesBitMask = alignedAccessModesBitMask(varType, isFinal);
+        } else {
+            this.accessModesBitMask = unalignedAccessModesBitMask(varType);
+        }
+    }
+    // END Android-changed: package private constructors.
+
+    // BEGIN Android-added: helper state for VarHandle properties.
+
+    /** BitMask of access modes that do not change the memory referenced by a VarHandle.
+     * An example being a read of a variable with volatile ordering effects. */
+    private final static int READ_ACCESS_MODES_BIT_MASK;
+
+    /** BitMask of access modes that write to the memory referenced by
+     * a VarHandle.  This does not include any compare and update
+     * access modes, nor any bitwise or numeric access modes. An
+     * example being a write to variable with release ordering
+     * effects.
+     */
+    private final static int WRITE_ACCESS_MODES_BIT_MASK;
+
+    /** BitMask of access modes that are applicable to types
+     * supporting for atomic updates.  This includes access modes that
+     * both read and write a variable such as compare-and-set.
+     */
+    private final static int ATOMIC_UPDATE_ACCESS_MODES_BIT_MASK;
+
+    /** BitMask of access modes that are applicable to types
+     * supporting numeric atomic update operations. */
+    private final static int NUMERIC_ATOMIC_UPDATE_ACCESS_MODES_BIT_MASK;
+
+    /** BitMask of access modes that are applicable to types
+     * supporting bitwise atomic update operations. */
+    private final static int BITWISE_ATOMIC_UPDATE_ACCESS_MODES_BIT_MASK;
+
+    /** BitMask of all access modes. */
+    private final static int ALL_MODES_BIT_MASK;
+
+    /** Indicator of machine word size. */
+    private final static boolean RUNNING_ON_64BIT = VMRuntime.getRuntime().is64Bit();
+
+    static {
+        // Check we're not about to overflow the storage of the
+        // bitmasks here and in the accessModesBitMask field.
+        if (AccessMode.values().length > Integer.SIZE) {
+            throw new InternalError("accessModes overflow");
+        }
+
+        // Access modes bit mask declarations and initialization order
+        // follows the presentation order in JEP193.
+        READ_ACCESS_MODES_BIT_MASK = accessTypesToBitMask(EnumSet.of(AccessType.GET));
+
+        WRITE_ACCESS_MODES_BIT_MASK = accessTypesToBitMask(EnumSet.of(AccessType.SET));
+
+        ATOMIC_UPDATE_ACCESS_MODES_BIT_MASK =
+                accessTypesToBitMask(EnumSet.of(AccessType.COMPARE_AND_EXCHANGE,
+                                                AccessType.COMPARE_AND_SWAP,
+                                                AccessType.GET_AND_UPDATE));
+
+        NUMERIC_ATOMIC_UPDATE_ACCESS_MODES_BIT_MASK =
+                accessTypesToBitMask(EnumSet.of(AccessType.GET_AND_UPDATE_NUMERIC));
+
+        BITWISE_ATOMIC_UPDATE_ACCESS_MODES_BIT_MASK =
+                accessTypesToBitMask(EnumSet.of(AccessType.GET_AND_UPDATE_BITWISE));
+
+        ALL_MODES_BIT_MASK = (READ_ACCESS_MODES_BIT_MASK |
+                              WRITE_ACCESS_MODES_BIT_MASK |
+                              ATOMIC_UPDATE_ACCESS_MODES_BIT_MASK |
+                              NUMERIC_ATOMIC_UPDATE_ACCESS_MODES_BIT_MASK |
+                              BITWISE_ATOMIC_UPDATE_ACCESS_MODES_BIT_MASK);
+    }
+
+    static int accessTypesToBitMask(final EnumSet<AccessType> accessTypes) {
+        int m = 0;
+        for (AccessMode accessMode : AccessMode.values()) {
+            if (accessTypes.contains(accessMode.at)) {
+                m |= 1 << accessMode.ordinal();
+            }
+        }
+        return m;
+    }
+
+    static int alignedAccessModesBitMask(Class<?> varType, boolean isFinal) {
+        // For aligned accesses, the supported access modes are described in:
+        // @see java.lang.invoke.MethodHandles.Lookup#findVarHandle
+        int bitMask = ALL_MODES_BIT_MASK;
+
+        // If the field is declared final, keep only the read access modes.
+        if (isFinal) {
+            bitMask &= READ_ACCESS_MODES_BIT_MASK;
+        }
+
+        // If the field is anything other than byte, short, char, int,
+        // long, float, double then remove the numeric atomic update
+        // access modes.
+        if (varType != byte.class && varType != short.class && varType != char.class &&
+            varType != int.class && varType != long.class
+            && varType != float.class && varType != double.class) {
+            bitMask &= ~NUMERIC_ATOMIC_UPDATE_ACCESS_MODES_BIT_MASK;
+        }
+
+        // If the field is not integral, remove the bitwise atomic update access modes.
+        if (varType != boolean.class && varType != byte.class && varType != short.class &&
+            varType != char.class && varType != int.class && varType != long.class) {
+            bitMask &= ~BITWISE_ATOMIC_UPDATE_ACCESS_MODES_BIT_MASK;
+        }
+        return bitMask;
+    }
+
+    static int unalignedAccessModesBitMask(Class<?> varType) {
+        // The VarHandle refers to a view of byte array or a
+        // view of a byte buffer.  The corresponding accesses
+        // maybe unaligned so the access modes are more
+        // restrictive than field or array element accesses.
+        //
+        // The supported access modes are described in:
+        // @see java.lang.invoke.MethodHandles#byteArrayViewVarHandle
+        int bitMask = 0;
+
+        // Read/write access modes supported for all types except for
+        // long and double on 32-bit platforms.
+        if (RUNNING_ON_64BIT || (varType != long.class && varType != double.class)) {
+            bitMask |= READ_ACCESS_MODES_BIT_MASK | WRITE_ACCESS_MODES_BIT_MASK;
+        }
+
+        // int, long, float, double support atomic update modes per documentation.
+        if (varType == int.class || varType == long.class ||
+            varType == float.class || varType == double.class) {
+            bitMask |= ATOMIC_UPDATE_ACCESS_MODES_BIT_MASK;
+        }
+
+        // int and long support numeric updates per documentation.
+        if (varType == int.class || varType == long.class) {
+            bitMask |= NUMERIC_ATOMIC_UPDATE_ACCESS_MODES_BIT_MASK;
+        }
+
+        // int and long support bitwise updates per documentation.
+        if (varType == int.class || varType == long.class) {
+            bitMask |= BITWISE_ATOMIC_UPDATE_ACCESS_MODES_BIT_MASK;
+        }
+        return bitMask;
+    }
+    // END Android-added: helper class for VarHandle properties.
 }
diff --git a/java/net/Socket.java b/java/net/Socket.java
index 03e2b71..e36d15b 100644
--- a/java/net/Socket.java
+++ b/java/net/Socket.java
@@ -122,7 +122,7 @@
         Proxy p = proxy == Proxy.NO_PROXY ? Proxy.NO_PROXY
                                           : sun.net.ApplicationProxy.create(proxy);
         Proxy.Type type = p.type();
-        // Android-changed: Removed HTTP proxy suppport.
+        // Android-changed: Removed HTTP proxy support.
         // if (type == Proxy.Type.SOCKS || type == Proxy.Type.HTTP) {
         if (type == Proxy.Type.SOCKS) {
             SecurityManager security = System.getSecurityManager();
@@ -214,6 +214,7 @@
     public Socket(String host, int port)
         throws UnknownHostException, IOException
     {
+        // Android-changed: App compat. Socket ctor should try all addresses. http://b/30007735
         this(InetAddress.getAllByName(host), port, (SocketAddress) null, true);
     }
 
@@ -245,6 +246,7 @@
      * @see        SecurityManager#checkConnect
      */
     public Socket(InetAddress address, int port) throws IOException {
+        // Android-changed: App compat. Socket ctor should try all addresses. http://b/30007735
         this(nonNullAddress(address), port, (SocketAddress) null, true);
     }
 
@@ -286,6 +288,7 @@
      */
     public Socket(String host, int port, InetAddress localAddr,
                   int localPort) throws IOException {
+        // Android-changed: App compat. Socket ctor should try all addresses. http://b/30007735
         this(InetAddress.getAllByName(host), port,
              new InetSocketAddress(localAddr, localPort), true);
     }
@@ -327,6 +330,7 @@
      */
     public Socket(InetAddress address, int port, InetAddress localAddr,
                   int localPort) throws IOException {
+        // Android-changed: App compat. Socket ctor should try all addresses. http://b/30007735
         this(nonNullAddress(address), port,
              new InetSocketAddress(localAddr, localPort), true);
     }
@@ -374,6 +378,7 @@
      */
     @Deprecated
     public Socket(String host, int port, boolean stream) throws IOException {
+        // Android-changed: App compat. Socket ctor should try all addresses. http://b/30007735
         this(InetAddress.getAllByName(host), port, (SocketAddress) null, stream);
     }
 
@@ -415,9 +420,11 @@
      */
     @Deprecated
     public Socket(InetAddress host, int port, boolean stream) throws IOException {
+        // Android-changed: App compat. Socket ctor should try all addresses. http://b/30007735
         this(nonNullAddress(host), port, new InetSocketAddress(0), stream);
     }
 
+    // BEGIN Android-changed: App compat. Socket ctor should try all addresses. http://b/30007735
     private static InetAddress[] nonNullAddress(InetAddress address) {
         // backward compatibility
         if (address == null)
@@ -426,8 +433,6 @@
         return new InetAddress[] { address };
     }
 
-    // Android-changed: Socket ctor should try all addresses
-    // b/30007735
     private Socket(InetAddress[] addresses, int port, SocketAddress localAddr,
             boolean stream) throws IOException {
         if (addresses == null || addresses.length == 0) {
@@ -446,9 +451,8 @@
                 break;
             } catch (IOException | IllegalArgumentException | SecurityException e) {
                 try {
-                    // Android-changed:
-                    // Do not call #close, classes that extend this class may do not expect a call
-                    // to #close coming from the superclass constructor.
+                    // Android-changed: Let ctor call impl.close() instead of overridable close().
+                    // Subclasses may not expect a call to close() coming from this constructor.
                     impl.close();
                     closed = true;
                 } catch (IOException ce) {
@@ -468,6 +472,7 @@
             closed = false;
         }
     }
+    // END Android-changed: App compat. Socket ctor should try all addresses. http://b/30007735
 
     /**
      * Creates the socket implementation.
@@ -1060,7 +1065,7 @@
      *
      * The setting only affects socket close.
      *
-     * @return the setting for SO_LINGER.
+     * @return the setting for {@link SocketOptions#SO_LINGER SO_LINGER}.
      * @exception SocketException if there is an error
      * in the underlying protocol, such as a TCP error.
      * @since   JDK1.1
@@ -1768,7 +1773,7 @@
         /* Not implemented yet */
     }
 
-    // Android-added: for testing and internal use.
+    // Android-added: getFileDescriptor$() method for testing and internal use.
     /**
      * @hide internal use only
      */
diff --git a/java/net/SocketException.java b/java/net/SocketException.java
index 286bc42..64ae771 100644
--- a/java/net/SocketException.java
+++ b/java/net/SocketException.java
@@ -54,6 +54,7 @@
     public SocketException() {
     }
 
+    // BEGIN Android-added: SocketException ctor with cause for internal use.
     /** @hide */
     public SocketException(Throwable cause) {
         super(cause);
@@ -63,4 +64,5 @@
     public SocketException(String msg, Throwable cause) {
         super(msg, cause);
     }
+    // END Android-added: SocketException ctor with cause for internal use.
 }
diff --git a/java/net/SocketImpl.java b/java/net/SocketImpl.java
index c0db070..ade2630 100644
--- a/java/net/SocketImpl.java
+++ b/java/net/SocketImpl.java
@@ -227,6 +227,7 @@
         return fd;
     }
 
+    // Android-added: getFD$() for testing.
     /**
      * @hide used by java.nio tests
      */
diff --git a/java/net/SocketInputStream.java b/java/net/SocketInputStream.java
index f5c4c8f..8d0e0c5 100644
--- a/java/net/SocketInputStream.java
+++ b/java/net/SocketInputStream.java
@@ -44,6 +44,11 @@
  */
 class SocketInputStream extends FileInputStream
 {
+    // Android-removed: Android doesn't need to call native init.
+    // static {
+    //    init();
+    //}
+
     private boolean eof;
     private AbstractPlainSocketImpl impl = null;
     private byte temp[];
@@ -166,6 +171,7 @@
         // acquire file descriptor and do the read
         FileDescriptor fd = impl.acquireFD();
         try {
+            // Android-added: Check BlockGuard policy in read().
             BlockGuard.getThreadPolicy().onNetwork();
             n = socketRead(fd, b, off, length, timeout);
             if (n > 0) {
@@ -289,4 +295,11 @@
      * Overrides finalize, the fd is closed by the Socket.
      */
     protected void finalize() {}
+
+    // Android-removed: Android doesn't need native init.
+    /*
+     * Perform class load-time initializations.
+     *
+    private native static void init();
+    */
 }
diff --git a/java/net/SocketOutputStream.java b/java/net/SocketOutputStream.java
index c0173f0..9e8e792 100644
--- a/java/net/SocketOutputStream.java
+++ b/java/net/SocketOutputStream.java
@@ -43,6 +43,11 @@
  */
 class SocketOutputStream extends FileOutputStream
 {
+    // Android-removed: Android doesn't need to call native init.
+    // static {
+    //    init();
+    //}
+
     private AbstractPlainSocketImpl impl = null;
     private byte temp[] = new byte[1];
     private Socket socket = null;
@@ -95,6 +100,8 @@
      * @exception IOException If an I/O error has occurred.
      */
     private void socketWrite(byte b[], int off, int len) throws IOException {
+
+
         if (len <= 0 || off < 0 || len > b.length - off) {
             if (len == 0) {
                 return;
@@ -105,6 +112,7 @@
 
         FileDescriptor fd = impl.acquireFD();
         try {
+            // Android-added: Check BlockGuard policy in socketWrite.
             BlockGuard.getThreadPolicy().onNetwork();
             socketWrite0(fd, b, off, len);
         } catch (SocketException se) {
@@ -174,4 +182,11 @@
      * Overrides finalize, the fd is closed by the Socket.
      */
     protected void finalize() {}
+
+    // Android-removed: Android doesn't need native init.
+    /*
+     * Perform class load-time initializations.
+     *
+    private native static void init();
+    */
 }
diff --git a/java/net/SocksSocketImpl.java b/java/net/SocksSocketImpl.java
index a81e219..0d9d8f5 100644
--- a/java/net/SocksSocketImpl.java
+++ b/java/net/SocksSocketImpl.java
@@ -347,12 +347,91 @@
                                       epoint.getPort());
         }
         if (server == null) {
-            // Android-removed: Logic to establish proxy connection based on default ProxySelector
+            // Android-removed: Logic to establish proxy connection based on default ProxySelector.
+            // Removed code that tried to establish proxy connection if ProxySelector#getDefault()
+            // is not null. This was never the case in previous Android releases, was causing
+            // issues and therefore was removed.
             /*
-             * Removed code that tried to establish proxy connection if
-             * ProxySelector#getDefault() is not null.
-             * This was never the case in previous android releases, was causing
-             * issues and therefore was removed.
+            // This is the general case
+            // server is not null only when the socket was created with a
+            // specified proxy in which case it does bypass the ProxySelector
+            ProxySelector sel = java.security.AccessController.doPrivileged(
+                new java.security.PrivilegedAction<ProxySelector>() {
+                    public ProxySelector run() {
+                            return ProxySelector.getDefault();
+                        }
+                    });
+            if (sel == null) {
+                /*
+                 * No default proxySelector --> direct connection
+                 *
+                super.connect(epoint, remainingMillis(deadlineMillis));
+                return;
+            }
+            URI uri;
+            // Use getHostString() to avoid reverse lookups
+            String host = epoint.getHostString();
+            // IPv6 litteral?
+            if (epoint.getAddress() instanceof Inet6Address &&
+                (!host.startsWith("[")) && (host.indexOf(":") >= 0)) {
+                host = "[" + host + "]";
+            }
+            try {
+                uri = new URI("socket://" + ParseUtil.encodePath(host) + ":"+ epoint.getPort());
+            } catch (URISyntaxException e) {
+                // This shouldn't happen
+                assert false : e;
+                uri = null;
+            }
+            Proxy p = null;
+            IOException savedExc = null;
+            java.util.Iterator<Proxy> iProxy = null;
+            iProxy = sel.select(uri).iterator();
+            if (iProxy == null || !(iProxy.hasNext())) {
+                super.connect(epoint, remainingMillis(deadlineMillis));
+                return;
+            }
+            while (iProxy.hasNext()) {
+                p = iProxy.next();
+                if (p == null || p.type() != Proxy.Type.SOCKS) {
+                    super.connect(epoint, remainingMillis(deadlineMillis));
+                    return;
+                }
+
+                if (!(p.address() instanceof InetSocketAddress))
+                    throw new SocketException("Unknown address type for proxy: " + p);
+                // Use getHostString() to avoid reverse lookups
+                server = ((InetSocketAddress) p.address()).getHostString();
+                serverPort = ((InetSocketAddress) p.address()).getPort();
+                if (p instanceof SocksProxy) {
+                    if (((SocksProxy)p).protocolVersion() == 4) {
+                        useV4 = true;
+                    }
+                }
+
+                // Connects to the SOCKS server
+                try {
+                    privilegedConnect(server, serverPort, remainingMillis(deadlineMillis));
+                    // Worked, let's get outta here
+                    break;
+                } catch (IOException e) {
+                    // Ooops, let's notify the ProxySelector
+                    sel.connectFailed(uri,p.address(),e);
+                    server = null;
+                    serverPort = -1;
+                    savedExc = e;
+                    // Will continue the while loop and try the next proxy
+                }
+            }
+
+            /*
+             * If server is still null at this point, none of the proxy
+             * worked
+             *
+            if (server == null) {
+                throw new SocketException("Can't connect to SOCKS proxy:"
+                                          + savedExc.getMessage());
+            }
              */
             super.connect(epoint, remainingMillis(deadlineMillis));
             return;
@@ -508,6 +587,458 @@
         external_address = epoint;
     }
 
+    // Android-removed: Dead code. bindV4, socksBind, acceptFrom methods.
+    /*
+    private void bindV4(InputStream in, OutputStream out,
+                        InetAddress baddr,
+                        int lport) throws IOException {
+        if (!(baddr instanceof Inet4Address)) {
+            throw new SocketException("SOCKS V4 requires IPv4 only addresses");
+        }
+        super.bind(baddr, lport);
+        byte[] addr1 = baddr.getAddress();
+        /* Test for AnyLocal *
+        InetAddress naddr = baddr;
+        if (naddr.isAnyLocalAddress()) {
+            naddr = AccessController.doPrivileged(
+                        new PrivilegedAction<InetAddress>() {
+                            public InetAddress run() {
+                                return cmdsock.getLocalAddress();
+
+                            }
+                        });
+            addr1 = naddr.getAddress();
+        }
+        out.write(PROTO_VERS4);
+        out.write(BIND);
+        out.write((super.getLocalPort() >> 8) & 0xff);
+        out.write((super.getLocalPort() >> 0) & 0xff);
+        out.write(addr1);
+        String userName = getUserName();
+        try {
+            out.write(userName.getBytes("ISO-8859-1"));
+        } catch (java.io.UnsupportedEncodingException uee) {
+            assert false;
+        }
+        out.write(0);
+        out.flush();
+        byte[] data = new byte[8];
+        int n = readSocksReply(in, data);
+        if (n != 8)
+            throw new SocketException("Reply from SOCKS server has bad length: " + n);
+        if (data[0] != 0 && data[0] != 4)
+            throw new SocketException("Reply from SOCKS server has bad version");
+        SocketException ex = null;
+        switch (data[1]) {
+        case 90:
+            // Success!
+            external_address = new InetSocketAddress(baddr, lport);
+            break;
+        case 91:
+            ex = new SocketException("SOCKS request rejected");
+            break;
+        case 92:
+            ex = new SocketException("SOCKS server couldn't reach destination");
+            break;
+        case 93:
+            ex = new SocketException("SOCKS authentication failed");
+            break;
+        default:
+            ex = new SocketException("Reply from SOCKS server contains bad status");
+            break;
+        }
+        if (ex != null) {
+            in.close();
+            out.close();
+            throw ex;
+        }
+
+    }
+
+    /**
+     * Sends the Bind request to the SOCKS proxy. In the SOCKS protocol, bind
+     * means "accept incoming connection from", so the SocketAddress is the
+     * the one of the host we do accept connection from.
+     *
+     * @param      saddr   the Socket address of the remote host.
+     * @exception  IOException  if an I/O error occurs when binding this socket.
+     *
+    protected synchronized void socksBind(InetSocketAddress saddr) throws IOException {
+        if (socket != null) {
+            // this is a client socket, not a server socket, don't
+            // call the SOCKS proxy for a bind!
+            return;
+        }
+
+        // Connects to the SOCKS server
+
+        if (server == null) {
+            // This is the general case
+            // server is not null only when the socket was created with a
+            // specified proxy in which case it does bypass the ProxySelector
+            ProxySelector sel = java.security.AccessController.doPrivileged(
+                new java.security.PrivilegedAction<ProxySelector>() {
+                    public ProxySelector run() {
+                            return ProxySelector.getDefault();
+                        }
+                    });
+            if (sel == null) {
+                /*
+                 * No default proxySelector --> direct connection
+                 *
+                return;
+            }
+            URI uri;
+            // Use getHostString() to avoid reverse lookups
+            String host = saddr.getHostString();
+            // IPv6 litteral?
+            if (saddr.getAddress() instanceof Inet6Address &&
+                (!host.startsWith("[")) && (host.indexOf(":") >= 0)) {
+                host = "[" + host + "]";
+            }
+            try {
+                uri = new URI("serversocket://" + ParseUtil.encodePath(host) + ":"+ saddr.getPort());
+            } catch (URISyntaxException e) {
+                // This shouldn't happen
+                assert false : e;
+                uri = null;
+            }
+            Proxy p = null;
+            Exception savedExc = null;
+            java.util.Iterator<Proxy> iProxy = null;
+            iProxy = sel.select(uri).iterator();
+            if (iProxy == null || !(iProxy.hasNext())) {
+                return;
+            }
+            while (iProxy.hasNext()) {
+                p = iProxy.next();
+                if (p == null || p.type() != Proxy.Type.SOCKS) {
+                    return;
+                }
+
+                if (!(p.address() instanceof InetSocketAddress))
+                    throw new SocketException("Unknown address type for proxy: " + p);
+                // Use getHostString() to avoid reverse lookups
+                server = ((InetSocketAddress) p.address()).getHostString();
+                serverPort = ((InetSocketAddress) p.address()).getPort();
+                if (p instanceof SocksProxy) {
+                    if (((SocksProxy)p).protocolVersion() == 4) {
+                        useV4 = true;
+                    }
+                }
+
+                // Connects to the SOCKS server
+                try {
+                    AccessController.doPrivileged(
+                        new PrivilegedExceptionAction<Void>() {
+                            public Void run() throws Exception {
+                                cmdsock = new Socket(new PlainSocketImpl());
+                                cmdsock.connect(new InetSocketAddress(server, serverPort));
+                                cmdIn = cmdsock.getInputStream();
+                                cmdOut = cmdsock.getOutputStream();
+                                return null;
+                            }
+                        });
+                } catch (Exception e) {
+                    // Ooops, let's notify the ProxySelector
+                    sel.connectFailed(uri,p.address(),new SocketException(e.getMessage()));
+                    server = null;
+                    serverPort = -1;
+                    cmdsock = null;
+                    savedExc = e;
+                    // Will continue the while loop and try the next proxy
+                }
+            }
+
+            /*
+             * If server is still null at this point, none of the proxy
+             * worked
+             *
+            if (server == null || cmdsock == null) {
+                throw new SocketException("Can't connect to SOCKS proxy:"
+                                          + savedExc.getMessage());
+            }
+        } else {
+            try {
+                AccessController.doPrivileged(
+                    new PrivilegedExceptionAction<Void>() {
+                        public Void run() throws Exception {
+                            cmdsock = new Socket(new PlainSocketImpl());
+                            cmdsock.connect(new InetSocketAddress(server, serverPort));
+                            cmdIn = cmdsock.getInputStream();
+                            cmdOut = cmdsock.getOutputStream();
+                            return null;
+                        }
+                    });
+            } catch (Exception e) {
+                throw new SocketException(e.getMessage());
+            }
+        }
+        BufferedOutputStream out = new BufferedOutputStream(cmdOut, 512);
+        InputStream in = cmdIn;
+        if (useV4) {
+            bindV4(in, out, saddr.getAddress(), saddr.getPort());
+            return;
+        }
+        out.write(PROTO_VERS);
+        out.write(2);
+        out.write(NO_AUTH);
+        out.write(USER_PASSW);
+        out.flush();
+        byte[] data = new byte[2];
+        int i = readSocksReply(in, data);
+        if (i != 2 || ((int)data[0]) != PROTO_VERS) {
+            // Maybe it's not a V5 sever after all
+            // Let's try V4 before we give up
+            bindV4(in, out, saddr.getAddress(), saddr.getPort());
+            return;
+        }
+        if (((int)data[1]) == NO_METHODS)
+            throw new SocketException("SOCKS : No acceptable methods");
+        if (!authenticate(data[1], in, out)) {
+            throw new SocketException("SOCKS : authentication failed");
+        }
+        // We're OK. Let's issue the BIND command.
+        out.write(PROTO_VERS);
+        out.write(BIND);
+        out.write(0);
+        int lport = saddr.getPort();
+        if (saddr.isUnresolved()) {
+            out.write(DOMAIN_NAME);
+            out.write(saddr.getHostName().length());
+            try {
+                out.write(saddr.getHostName().getBytes("ISO-8859-1"));
+            } catch (java.io.UnsupportedEncodingException uee) {
+                assert false;
+            }
+            out.write((lport >> 8) & 0xff);
+            out.write((lport >> 0) & 0xff);
+        } else if (saddr.getAddress() instanceof Inet4Address) {
+            byte[] addr1 = saddr.getAddress().getAddress();
+            out.write(IPV4);
+            out.write(addr1);
+            out.write((lport >> 8) & 0xff);
+            out.write((lport >> 0) & 0xff);
+            out.flush();
+        } else if (saddr.getAddress() instanceof Inet6Address) {
+            byte[] addr1 = saddr.getAddress().getAddress();
+            out.write(IPV6);
+            out.write(addr1);
+            out.write((lport >> 8) & 0xff);
+            out.write((lport >> 0) & 0xff);
+            out.flush();
+        } else {
+            cmdsock.close();
+            throw new SocketException("unsupported address type : " + saddr);
+        }
+        data = new byte[4];
+        i = readSocksReply(in, data);
+        SocketException ex = null;
+        int len, nport;
+        byte[] addr;
+        switch (data[1]) {
+        case REQUEST_OK:
+            // success!
+            switch(data[3]) {
+            case IPV4:
+                addr = new byte[4];
+                i = readSocksReply(in, addr);
+                if (i != 4)
+                    throw new SocketException("Reply from SOCKS server badly formatted");
+                data = new byte[2];
+                i = readSocksReply(in, data);
+                if (i != 2)
+                    throw new SocketException("Reply from SOCKS server badly formatted");
+                nport = ((int)data[0] & 0xff) << 8;
+                nport += ((int)data[1] & 0xff);
+                external_address =
+                    new InetSocketAddress(new Inet4Address("", addr) , nport);
+                break;
+            case DOMAIN_NAME:
+                len = data[1];
+                byte[] host = new byte[len];
+                i = readSocksReply(in, host);
+                if (i != len)
+                    throw new SocketException("Reply from SOCKS server badly formatted");
+                data = new byte[2];
+                i = readSocksReply(in, data);
+                if (i != 2)
+                    throw new SocketException("Reply from SOCKS server badly formatted");
+                nport = ((int)data[0] & 0xff) << 8;
+                nport += ((int)data[1] & 0xff);
+                external_address = new InetSocketAddress(new String(host), nport);
+                break;
+            case IPV6:
+                len = data[1];
+                addr = new byte[len];
+                i = readSocksReply(in, addr);
+                if (i != len)
+                    throw new SocketException("Reply from SOCKS server badly formatted");
+                data = new byte[2];
+                i = readSocksReply(in, data);
+                if (i != 2)
+                    throw new SocketException("Reply from SOCKS server badly formatted");
+                nport = ((int)data[0] & 0xff) << 8;
+                nport += ((int)data[1] & 0xff);
+                external_address =
+                    new InetSocketAddress(new Inet6Address("", addr), nport);
+                break;
+            }
+            break;
+        case GENERAL_FAILURE:
+            ex = new SocketException("SOCKS server general failure");
+            break;
+        case NOT_ALLOWED:
+            ex = new SocketException("SOCKS: Bind not allowed by ruleset");
+            break;
+        case NET_UNREACHABLE:
+            ex = new SocketException("SOCKS: Network unreachable");
+            break;
+        case HOST_UNREACHABLE:
+            ex = new SocketException("SOCKS: Host unreachable");
+            break;
+        case CONN_REFUSED:
+            ex = new SocketException("SOCKS: Connection refused");
+            break;
+        case TTL_EXPIRED:
+            ex =  new SocketException("SOCKS: TTL expired");
+            break;
+        case CMD_NOT_SUPPORTED:
+            ex = new SocketException("SOCKS: Command not supported");
+            break;
+        case ADDR_TYPE_NOT_SUP:
+            ex = new SocketException("SOCKS: address type not supported");
+            break;
+        }
+        if (ex != null) {
+            in.close();
+            out.close();
+            cmdsock.close();
+            cmdsock = null;
+            throw ex;
+        }
+        cmdIn = in;
+        cmdOut = out;
+    }
+
+    /**
+     * Accepts a connection from a specific host.
+     *
+     * @param      s   the accepted connection.
+     * @param      saddr the socket address of the host we do accept
+     *               connection from
+     * @exception  IOException  if an I/O error occurs when accepting the
+     *               connection.
+     *
+    protected void acceptFrom(SocketImpl s, InetSocketAddress saddr) throws IOException {
+        if (cmdsock == null) {
+            // Not a Socks ServerSocket.
+            return;
+        }
+        InputStream in = cmdIn;
+        // Sends the "SOCKS BIND" request.
+        socksBind(saddr);
+        in.read();
+        int i = in.read();
+        in.read();
+        SocketException ex = null;
+        int nport;
+        byte[] addr;
+        InetSocketAddress real_end = null;
+        switch (i) {
+        case REQUEST_OK:
+            // success!
+            i = in.read();
+            switch(i) {
+            case IPV4:
+                addr = new byte[4];
+                readSocksReply(in, addr);
+                nport = in.read() << 8;
+                nport += in.read();
+                real_end =
+                    new InetSocketAddress(new Inet4Address("", addr) , nport);
+                break;
+            case DOMAIN_NAME:
+                int len = in.read();
+                addr = new byte[len];
+                readSocksReply(in, addr);
+                nport = in.read() << 8;
+                nport += in.read();
+                real_end = new InetSocketAddress(new String(addr), nport);
+                break;
+            case IPV6:
+                addr = new byte[16];
+                readSocksReply(in, addr);
+                nport = in.read() << 8;
+                nport += in.read();
+                real_end =
+                    new InetSocketAddress(new Inet6Address("", addr), nport);
+                break;
+            }
+            break;
+        case GENERAL_FAILURE:
+            ex = new SocketException("SOCKS server general failure");
+            break;
+        case NOT_ALLOWED:
+            ex = new SocketException("SOCKS: Accept not allowed by ruleset");
+            break;
+        case NET_UNREACHABLE:
+            ex = new SocketException("SOCKS: Network unreachable");
+            break;
+        case HOST_UNREACHABLE:
+            ex = new SocketException("SOCKS: Host unreachable");
+            break;
+        case CONN_REFUSED:
+            ex = new SocketException("SOCKS: Connection refused");
+            break;
+        case TTL_EXPIRED:
+            ex =  new SocketException("SOCKS: TTL expired");
+            break;
+        case CMD_NOT_SUPPORTED:
+            ex = new SocketException("SOCKS: Command not supported");
+            break;
+        case ADDR_TYPE_NOT_SUP:
+            ex = new SocketException("SOCKS: address type not supported");
+            break;
+        }
+        if (ex != null) {
+            cmdIn.close();
+            cmdOut.close();
+            cmdsock.close();
+            cmdsock = null;
+            throw ex;
+        }
+
+        /**
+         * This is where we have to do some fancy stuff.
+         * The datastream from the socket "accepted" by the proxy will
+         * come through the cmdSocket. So we have to swap the socketImpls
+         *
+        if (s instanceof SocksSocketImpl) {
+            ((SocksSocketImpl)s).external_address = real_end;
+        }
+        if (s instanceof PlainSocketImpl) {
+            PlainSocketImpl psi = (PlainSocketImpl) s;
+            psi.setInputStream((SocketInputStream) in);
+            psi.setFileDescriptor(cmdsock.getImpl().getFileDescriptor());
+            psi.setAddress(cmdsock.getImpl().getInetAddress());
+            psi.setPort(cmdsock.getImpl().getPort());
+            psi.setLocalPort(cmdsock.getImpl().getLocalPort());
+        } else {
+            s.fd = cmdsock.getImpl().fd;
+            s.address = cmdsock.getImpl().address;
+            s.port = cmdsock.getImpl().port;
+            s.localport = cmdsock.getImpl().localport;
+        }
+
+        // Need to do that so that the socket won't be closed
+        // when the ServerSocket is closed by the user.
+        // It kinds of detaches the Socket because it is now
+        // used elsewhere.
+        cmdsock = null;
+    }
+    */
+
     /**
      * Returns the value of this socket's {@code address} field.
      *
diff --git a/java/net/URLClassLoader.java b/java/net/URLClassLoader.java
index edb9b88..6e8acb1 100644
--- a/java/net/URLClassLoader.java
+++ b/java/net/URLClassLoader.java
@@ -103,8 +103,8 @@
         if (security != null) {
             security.checkCreateClassLoader();
         }
-        ucp = new URLClassPath(urls);
         this.acc = AccessController.getContext();
+        ucp = new URLClassPath(urls, acc);
     }
 
     URLClassLoader(URL[] urls, ClassLoader parent,
@@ -115,8 +115,8 @@
         if (security != null) {
             security.checkCreateClassLoader();
         }
-        ucp = new URLClassPath(urls);
         this.acc = acc;
+        ucp = new URLClassPath(urls, acc);
     }
 
     /**
@@ -147,8 +147,8 @@
         if (security != null) {
             security.checkCreateClassLoader();
         }
-        ucp = new URLClassPath(urls);
         this.acc = AccessController.getContext();
+        ucp = new URLClassPath(urls, acc);
     }
 
     URLClassLoader(URL[] urls, AccessControlContext acc) {
@@ -158,8 +158,8 @@
         if (security != null) {
             security.checkCreateClassLoader();
         }
-        ucp = new URLClassPath(urls);
         this.acc = acc;
+        ucp = new URLClassPath(urls, acc);
     }
 
     /**
@@ -180,6 +180,7 @@
      * @exception  SecurityException  if a security manager exists and its
      *             {@code checkCreateClassLoader} method doesn't allow
      *             creation of a class loader.
+     * @exception  NullPointerException if {@code urls} is {@code null}.
      * @see SecurityManager#checkCreateClassLoader
      */
     public URLClassLoader(URL[] urls, ClassLoader parent,
@@ -271,13 +272,13 @@
     * and errors are not caught. Calling close on an already closed
     * loader has no effect.
     * <p>
-    * @throws IOException if closing any file opened by this class loader
+    * @exception IOException if closing any file opened by this class loader
     * resulted in an IOException. Any such exceptions are caught internally.
     * If only one is caught, then it is re-thrown. If more than one exception
     * is caught, then the second and following exceptions are added
     * as suppressed exceptions of the first one caught, which is then re-thrown.
     *
-    * @throws SecurityException if a security manager is set, and it denies
+    * @exception SecurityException if a security manager is set, and it denies
     *   {@link RuntimePermission}{@code ("closeClassLoader")}
     *
     * @since 1.7
@@ -455,12 +456,16 @@
             // Use (direct) ByteBuffer:
             CodeSigner[] signers = res.getCodeSigners();
             CodeSource cs = new CodeSource(url, signers);
+            // Android-removed: Android doesn't use sun.misc.PerfCounter.
+            // sun.misc.PerfCounter.getReadClassBytesTime().addElapsedTimeFrom(t0);
             return defineClass(name, bb, cs);
         } else {
             byte[] b = res.getBytes();
             // must read certificates AFTER reading bytes.
             CodeSigner[] signers = res.getCodeSigners();
             CodeSource cs = new CodeSource(url, signers);
+            // Android-removed: Android doesn't use sun.misc.PerfCounter.
+            // sun.misc.PerfCounter.getReadClassBytesTime().addElapsedTimeFrom(t0);
             return defineClass(name, b, 0, b.length, cs);
         }
     }
@@ -766,6 +771,7 @@
     }
 
     static {
+        // Android-removed: SharedSecrets.setJavaNetAccess call. Android doesn't use it.
         /*sun.misc.SharedSecrets.setJavaNetAccess (
             new sun.misc.JavaNetAccess() {
                 public URLClassPath getURLClassPath (URLClassLoader u) {
diff --git a/java/security/AlgorithmParameters.java b/java/security/AlgorithmParameters.java
index 36bb3ee..864866e 100644
--- a/java/security/AlgorithmParameters.java
+++ b/java/security/AlgorithmParameters.java
@@ -29,6 +29,8 @@
 import java.security.spec.AlgorithmParameterSpec;
 import java.security.spec.InvalidParameterSpecException;
 
+import sun.security.jca.Providers;
+
 /**
  * This class is used as an opaque representation of cryptographic parameters.
  *
@@ -285,6 +287,8 @@
     {
         if (provider == null || provider.length() == 0)
             throw new IllegalArgumentException("missing provider");
+        // Android-added: Check for Bouncy Castle deprecation
+        Providers.checkBouncyCastleDeprecation(provider, "AlgorithmParameters", algorithm);
         Object[] objs = Security.getImpl(algorithm, "AlgorithmParameters",
                                          provider);
         return new AlgorithmParameters((AlgorithmParametersSpi)objs[0],
@@ -330,6 +334,8 @@
     {
         if (provider == null)
             throw new IllegalArgumentException("missing provider");
+        // Android-added: Check for Bouncy Castle deprecation
+        Providers.checkBouncyCastleDeprecation(provider, "AlgorithmParameters", algorithm);
         Object[] objs = Security.getImpl(algorithm, "AlgorithmParameters",
                                          provider);
         return new AlgorithmParameters((AlgorithmParametersSpi)objs[0],
diff --git a/java/security/KeyFactory.java b/java/security/KeyFactory.java
index a6b912c..d01ce16 100644
--- a/java/security/KeyFactory.java
+++ b/java/security/KeyFactory.java
@@ -231,6 +231,8 @@
      */
     public static KeyFactory getInstance(String algorithm, String provider)
             throws NoSuchAlgorithmException, NoSuchProviderException {
+        // Android-added: Check for Bouncy Castle deprecation
+        Providers.checkBouncyCastleDeprecation(provider, "KeyFactory", algorithm);
         Instance instance = GetInstance.getInstance("KeyFactory",
             KeyFactorySpi.class, algorithm, provider);
         return new KeyFactory((KeyFactorySpi)instance.impl,
@@ -268,6 +270,8 @@
      */
     public static KeyFactory getInstance(String algorithm, Provider provider)
             throws NoSuchAlgorithmException {
+        // Android-added: Check for Bouncy Castle deprecation
+        Providers.checkBouncyCastleDeprecation(provider, "KeyFactory", algorithm);
         Instance instance = GetInstance.getInstance("KeyFactory",
             KeyFactorySpi.class, algorithm, provider);
         return new KeyFactory((KeyFactorySpi)instance.impl,
diff --git a/java/security/KeyPairGenerator.java b/java/security/KeyPairGenerator.java
index 68ab5e9..51a0ec9 100644
--- a/java/security/KeyPairGenerator.java
+++ b/java/security/KeyPairGenerator.java
@@ -299,6 +299,8 @@
     public static KeyPairGenerator getInstance(String algorithm,
             String provider)
             throws NoSuchAlgorithmException, NoSuchProviderException {
+        // Android-added: Check for Bouncy Castle deprecation
+        Providers.checkBouncyCastleDeprecation(provider, "KeyPairGenerator", algorithm);
         Instance instance = GetInstance.getInstance("KeyPairGenerator",
                 KeyPairGeneratorSpi.class, algorithm, provider);
         return getInstance(instance, algorithm);
@@ -335,6 +337,8 @@
      */
     public static KeyPairGenerator getInstance(String algorithm,
             Provider provider) throws NoSuchAlgorithmException {
+        // Android-added: Check for Bouncy Castle deprecation
+        Providers.checkBouncyCastleDeprecation(provider, "KeyPairGenerator", algorithm);
         Instance instance = GetInstance.getInstance("KeyPairGenerator",
                 KeyPairGeneratorSpi.class, algorithm, provider);
         return getInstance(instance, algorithm);
diff --git a/java/security/MessageDigest.java b/java/security/MessageDigest.java
index 8e5dab1..ab2614a 100644
--- a/java/security/MessageDigest.java
+++ b/java/security/MessageDigest.java
@@ -35,6 +35,8 @@
 
 import java.nio.ByteBuffer;
 
+import sun.security.jca.Providers;
+
 /**
  * This MessageDigest class provides applications the functionality of a
  * message digest algorithm, such as SHA-1 or SHA-256.
@@ -255,6 +257,8 @@
     {
         if (provider == null || provider.length() == 0)
             throw new IllegalArgumentException("missing provider");
+        // Android-added: Check for Bouncy Castle deprecation
+        Providers.checkBouncyCastleDeprecation(provider, "MessageDigest", algorithm);
         Object[] objs = Security.getImpl(algorithm, "MessageDigest", provider);
         if (objs[0] instanceof MessageDigest) {
             MessageDigest md = (MessageDigest)objs[0];
@@ -303,6 +307,8 @@
     {
         if (provider == null)
             throw new IllegalArgumentException("missing provider");
+        // Android-added: Check for Bouncy Castle deprecation
+        Providers.checkBouncyCastleDeprecation(provider, "MessageDigest", algorithm);
         Object[] objs = Security.getImpl(algorithm, "MessageDigest", provider);
         if (objs[0] instanceof MessageDigest) {
             MessageDigest md = (MessageDigest)objs[0];
diff --git a/java/security/Signature.java b/java/security/Signature.java
index 5a0e6a8..9deaf56 100644
--- a/java/security/Signature.java
+++ b/java/security/Signature.java
@@ -498,6 +498,8 @@
             }
             return getInstanceRSA(p);
         }
+        // Android-added: Check for Bouncy Castle deprecation
+        Providers.checkBouncyCastleDeprecation(provider, "Signature", algorithm);
         Instance instance = GetInstance.getInstance
                 ("Signature", SignatureSpi.class, algorithm, provider);
         return getInstance(instance, algorithm);
@@ -541,6 +543,8 @@
             }
             return getInstanceRSA(provider);
         }
+        // Android-added: Check for Bouncy Castle deprecation
+        Providers.checkBouncyCastleDeprecation(provider, "Signature", algorithm);
         Instance instance = GetInstance.getInstance
                 ("Signature", SignatureSpi.class, algorithm, provider);
         return getInstance(instance, algorithm);
diff --git a/java/security/cert/CertificateFactory.java b/java/security/cert/CertificateFactory.java
index e74ff0a..5ccbb33 100644
--- a/java/security/cert/CertificateFactory.java
+++ b/java/security/cert/CertificateFactory.java
@@ -250,6 +250,8 @@
             String provider) throws CertificateException,
             NoSuchProviderException {
         try {
+            // Android-added: Check for Bouncy Castle deprecation
+            Providers.checkBouncyCastleDeprecation(provider, "CertificateFactory", type);
             Instance instance = GetInstance.getInstance("CertificateFactory",
                 CertificateFactorySpi.class, type, provider);
             return new CertificateFactory((CertificateFactorySpi)instance.impl,
@@ -291,6 +293,8 @@
     public static final CertificateFactory getInstance(String type,
             Provider provider) throws CertificateException {
         try {
+            // Android-added: Check for Bouncy Castle deprecation
+            Providers.checkBouncyCastleDeprecation(provider, "CertificateFactory", type);
             Instance instance = GetInstance.getInstance("CertificateFactory",
                 CertificateFactorySpi.class, type, provider);
             return new CertificateFactory((CertificateFactorySpi)instance.impl,
diff --git a/javax/crypto/KeyAgreement.java b/javax/crypto/KeyAgreement.java
index ffa18b1..ce42de8 100644
--- a/javax/crypto/KeyAgreement.java
+++ b/javax/crypto/KeyAgreement.java
@@ -252,6 +252,8 @@
     public static final KeyAgreement getInstance(String algorithm,
             String provider) throws NoSuchAlgorithmException,
             NoSuchProviderException {
+        // Android-added: Check for Bouncy Castle deprecation
+        Providers.checkBouncyCastleDeprecation(provider, "KeyAgreement", algorithm);
         Instance instance = JceSecurity.getInstance
                 ("KeyAgreement", KeyAgreementSpi.class, algorithm, provider);
         return new KeyAgreement((KeyAgreementSpi)instance.impl,
@@ -292,6 +294,8 @@
      */
     public static final KeyAgreement getInstance(String algorithm,
             Provider provider) throws NoSuchAlgorithmException {
+        // Android-added: Check for Bouncy Castle deprecation
+        Providers.checkBouncyCastleDeprecation(provider, "KeyAgreement", algorithm);
         Instance instance = JceSecurity.getInstance
                 ("KeyAgreement", KeyAgreementSpi.class, algorithm, provider);
         return new KeyAgreement((KeyAgreementSpi)instance.impl,
diff --git a/javax/crypto/KeyGenerator.java b/javax/crypto/KeyGenerator.java
index 5dfde97..b0977f0 100644
--- a/javax/crypto/KeyGenerator.java
+++ b/javax/crypto/KeyGenerator.java
@@ -326,6 +326,8 @@
     public static final KeyGenerator getInstance(String algorithm,
             String provider) throws NoSuchAlgorithmException,
             NoSuchProviderException {
+        // Android-added: Check for Bouncy Castle deprecation
+        Providers.checkBouncyCastleDeprecation(provider, "KeyGenerator", algorithm);
         Instance instance = JceSecurity.getInstance("KeyGenerator",
                 KeyGeneratorSpi.class, algorithm, provider);
         return new KeyGenerator((KeyGeneratorSpi)instance.impl,
@@ -364,6 +366,8 @@
      */
     public static final KeyGenerator getInstance(String algorithm,
             Provider provider) throws NoSuchAlgorithmException {
+        // Android-added: Check for Bouncy Castle deprecation
+        Providers.checkBouncyCastleDeprecation(provider, "KeyGenerator", algorithm);
         Instance instance = JceSecurity.getInstance("KeyGenerator",
                 KeyGeneratorSpi.class, algorithm, provider);
         return new KeyGenerator((KeyGeneratorSpi)instance.impl,
diff --git a/javax/crypto/Mac.java b/javax/crypto/Mac.java
index c3d99fb..dab6971 100644
--- a/javax/crypto/Mac.java
+++ b/javax/crypto/Mac.java
@@ -309,6 +309,8 @@
      */
     public static final Mac getInstance(String algorithm, String provider)
             throws NoSuchAlgorithmException, NoSuchProviderException {
+        // Android-added: Check for Bouncy Castle deprecation
+        Providers.checkBouncyCastleDeprecation(provider, "Mac", algorithm);
         Instance instance = JceSecurity.getInstance
                 ("Mac", MacSpi.class, algorithm, provider);
         return new Mac((MacSpi)instance.impl, instance.provider, algorithm);
@@ -344,6 +346,8 @@
      */
     public static final Mac getInstance(String algorithm, Provider provider)
             throws NoSuchAlgorithmException {
+        // Android-added: Check for Bouncy Castle deprecation
+        Providers.checkBouncyCastleDeprecation(provider, "Mac", algorithm);
         Instance instance = JceSecurity.getInstance
                 ("Mac", MacSpi.class, algorithm, provider);
         return new Mac((MacSpi)instance.impl, instance.provider, algorithm);
diff --git a/javax/crypto/SecretKeyFactory.java b/javax/crypto/SecretKeyFactory.java
index c1358c2..be1cef4 100644
--- a/javax/crypto/SecretKeyFactory.java
+++ b/javax/crypto/SecretKeyFactory.java
@@ -385,6 +385,8 @@
     public static final SecretKeyFactory getInstance(String algorithm,
             String provider) throws NoSuchAlgorithmException,
             NoSuchProviderException {
+        // Android-added: Check for Bouncy Castle deprecation
+        Providers.checkBouncyCastleDeprecation(provider, "SecretKeyFactory", algorithm);
         Instance instance = JceSecurity.getInstance("SecretKeyFactory",
                 SecretKeyFactorySpi.class, algorithm, provider);
         return new SecretKeyFactory((SecretKeyFactorySpi)instance.impl,
@@ -425,6 +427,8 @@
      */
     public static final SecretKeyFactory getInstance(String algorithm,
             Provider provider) throws NoSuchAlgorithmException {
+        // Android-added: Check for Bouncy Castle deprecation
+        Providers.checkBouncyCastleDeprecation(provider, "SecretKeyFactory", algorithm);
         Instance instance = JceSecurity.getInstance("SecretKeyFactory",
                 SecretKeyFactorySpi.class, algorithm, provider);
         return new SecretKeyFactory((SecretKeyFactorySpi)instance.impl,